testare cu pytest

26
Testare cu pytest

Upload: ionel-marie-cristian

Post on 06-Aug-2015

54 views

Category:

Software


0 download

TRANSCRIPT

Page 1: Testare cu pytest

Testare cu pytest

Page 2: Testare cu pytest

Testare cu pytest

Ionel Cristian Mărieș — Python / OSS

blog.ionelmc.rogithub.com/ionelmc

Page 3: Testare cu pytest

Un pic de istorieAm folosit Nose și uni昊est/uni昊est2, dar au tot felul de lacune (explicații pe parcurs)

Nose a pornit de la o versiune antica de pytest (0.8) [1]_

Pytest s‑a schimbat mult, merită revăzut dacă te‑ai uitat la el acu câțiva ani.

[1] h昊p://pytest.org/latest/faq.html#what‑s‑this‑magic‑with‑pytest‑historic‑notes

Page 4: Testare cu pytest

Ce e așa special la pytest?Testele sunt scrise și organizate diferit:

Teste doar cu simple funcții și aserții

Fixtures, markers, hooks

Multe facilități builtin (care există ca și plugin‑uri în Nose)

Suportă teste scrise în stil vechi, cu nose (suport partial) și uni昊est

Page 5: Testare cu pytest

Funcții simple în loc de metode și claseÎn loc de:class MyTest(unittest.TestCase):    def test_stuff(self):        ...

Avem doar:def test_stuff(self):    ...

Page 6: Testare cu pytest

Aserții simpleÎn loc de:self.assertIarăAmUitatCumÎiZice(variabilă)  # și mesaju de eroare

Putem avea ceva de genul:def test_assert():    var = [1, 2, 4]

    assert var == [1, 2, 4]

Page 7: Testare cu pytest

Inspecție aserțiidef test_assert():    var = [1, 2, 4]

    assert var == [1, 2, 4]============================== FAILURES ===============================_____________________________ test_assert _____________________________tests\test_assert.py:6: in test_assert    assert var == [1, 2, 3]

E   assert [1, 2, 4] == [1, 2, 3]E     At index 2 diff: 4 != 3E     Full diff:E     ‐ [1, 2, 4]

Page 8: Testare cu pytest

E     ‐ [1, 2, 4]E     ?        ^E     + [1, 2, 3]E     ?        ^

Inspecție aserții ﴾2﴿def test_assert_2():

    with open("qr.svg") as fh:        assert fh.readlines() == ["foobar"]============================== FAILURES ===============================

____________________________ test_assert_2 ____________________________tests\test_assert.py:17: in test_assert_2    assert fh.readlines() == ["foobar"]E   assert ["<?xml versi...ne" /></svg>'] == ['foobar']E     At index 0 diff: "<?xml version='1.0' encoding='UTF‐8'?>\n" != 'f

Page 9: Testare cu pytest

E     At index 0 diff: "<?xml version='1.0' encoding='UTF‐8'?>\n" != 'fE     Left contains more items, first extra item: '<svg height="37mm" v="fill:#000000;fill‐opacity:1;fill‐rule:nonzero;stroke:none" /></svg>'E     Full diff:E     + ['foobar']E     ‐ ["<?xml version='1.0' encoding='UTF‐8'?>\n",E     ‐  '<svg height="37mm" version="1.1" viewBox="0 0 37 37" width="3E     ‐  'xmlns="http://www.w3.org/2000/svg"><path d="M 10 32 L 10 33 LE     ‐  '32 z M 23 8 L 23 9 L 24 9 L 24 8 z M 21 24 L 21 25 L 22 25 LE     ‐  '23 L 26 24 L 27 24 L 27 23 z M 27 30 L 27 31 L 28 31 L 28 30...

Fixturi [1]În loc de  setUp  și  tearDown  sau pentru orice fel de dependință

Injecție automata („dependency injection”)

Și nu, ʺfixtureʺ nu înseamnă ce înseamnă în Django („data fixtures”)

Exemplu:@pytest.fixturedef myfixture(request):    return [1, 2, 3]

Page 10: Testare cu pytest

    return [1, 2, 3]

def test_fixture(myfixture):    assert myfixture == [1, 2, 3]

[1] La țară mere, în zece ani o să fie în DEX ca și „adídas” (DEX ʹ09)

Fixturi ﴾finalizatoare﴿Finalizatorul e echivalentul  tearDown  din  unittest .

Cel mai simplu e cu  pytest.yield_fixture :@pytest.yield_fixturedef mydbfixture(request):    conn = sqlite3.connect(":memory:")    try:

Page 11: Testare cu pytest

    try:        conn.execute("CREATE TABLE person "                     "(id INTEGER PRIMARY KEY, name VARCHAR UNIQUE)")        conn.execute("INSERT INTO person(name) VALUES (?)",                     ("Gheorghe",))        yield conn    finally:        # poate un DROP sau ceva ...        conn.close()

def test_dbfixture(mydbfixture):    assert list(mydbfixture.execute("select * from person")) == [        (1, 'Gheorghe')]

Fixturi ﴾scope și autouse﴿@pytest.yield_fixture(    scope="function",    autouse=False)def myfixture(request):    ...

Page 12: Testare cu pytest

    ...

scope: „durata de viață” a fixturii

scope="function"  ‑ implicit, fixtura este apelată la fiecare funcție de test.

scope="module"  ‑ fixtura este apelată o singură dată per modul.

scope="session"  ‑ fixtura este apelată o singură dată (sesiunea de test doar una e).

autouse: activare automată

Dacă toate testele folosesc acelasi fixture, si nu au nevoie de rezultat  autouse  devine foarteconvenabil.

Fixturi în fixturiO fixtură poate să depindă de alte fixturi. Reluând exemplul anterior:@pytest.yield_fixturedef mydb(request):

Page 13: Testare cu pytest

def mydb(request):    conn = sqlite3.connect(":memory:")    try:        conn.execute("CREATE TABLE person "                     "(id INTEGER PRIMARY KEY, name VARCHAR UNIQUE)")        yield conn    finally:        conn.close()

Fixturi în fixturi ﴾cont.﴿@pytest.yield_fixturedef myfixture(request, mydb):

Page 14: Testare cu pytest

def myfixture(request, mydb):    try:        mydb.execute("INSERT INTO person(name) VALUES (?)",                     ("Gheorghe",))        yield    finally:        mydb.execute("DELETE FROM person")

def test_fixture(mydb, myfixture):

    assert list(mydb.execute("select * from person")) == [        (1, 'Gheorghe')]

Page 15: Testare cu pytest

MarkersSunt niște simpli decoratori la funcțiile de test sau fixturi:

Etichete arbitare

Parametrizare:  pytest.mark.parametrize(argnames, argvalues)

Skip condițional:  pytest.mark.skipif(condition)

Fail „așteptat”: 

pytest.mark.xfail(condition, reason=None, run=True, raises=None)  [1]

[1] Când lenea e mare dar ești nostalgic și nu vrei să ștergi testul. Poate repari problema cândva!

Page 16: Testare cu pytest

[email protected](['a', 'b'], [    (1, 2),    (2, 1),])

def test_param(a, b):    assert a + b == 3collected 2 items

tests/test_param.py::test_param[1‐2] PASSEDtests/test_param.py::test_param[2‐1] PASSED

Page 17: Testare cu pytest

Parametrizare în [email protected](params=[sum, len, max, min])

def func(request):    return request.param

@pytest.mark.parametrize('numbers', [    (1, 2),    (2, 1),])def test_func(numbers, func):    assert func(numbers)tests/test_param.py::test_func[func0‐numbers0] PASSED

tests/test_param.py::test_func[func0‐numbers1] PASSEDtests/test_param.py::test_func[func1‐numbers0] PASSEDtests/test_param.py::test_func[func1‐numbers1] PASSEDtests/test_param.py::test_func[func2‐numbers0] PASSEDtests/test_param.py::test_func[func2‐numbers1] PASSEDtests/test_param.py::test_func[func3‐numbers0] PASSEDtests/test_param.py::test_func[func3‐numbers1] PASSED

Page 18: Testare cu pytest

tests/test_param.py::test_func[func3‐numbers1] PASSED

Parametrizare cu [email protected](params=[sum, len, max, min],                ids=['sum', 'len', 'max', 'min'])def func(request):    return request.param

@pytest.mark.parametrize('numbers', [    (1, 2),    (2, 1),], ids=["alba", "neagra"])def test_func(numbers, func):    assert func(numbers)tests/test_param.py::test_func[sum‐alba] PASSEDtests/test_param.py::test_func[sum‐neagra] PASSEDtests/test_param.py::test_func[len‐alba] PASSEDtests/test_param.py::test_func[len‐neagra] PASSEDtests/test_param.py::test_func[max‐alba] PASSEDtests/test_param.py::test_func[max‐neagra] PASSEDtests/test_param.py::test_func[min‐alba] PASSEDtests/test_param.py::test_func[min‐neagra] PASSED

Page 19: Testare cu pytest

tests/test_param.py::test_func[min‐neagra] PASSED

Selecție parametriiPutem selecta pe parametrii:$ py.test ‐k alba ‐v========================= test session starts =========================platform win32 ‐‐ Python 3.4.3 ‐‐ py‐1.4.27 ‐‐ pytest‐2.7.1collected 14 items

tests/test_param.py::test_func[sum‐alba] PASSEDtests/test_param.py::test_func[len‐alba] PASSEDtests/test_param.py::test_func[max‐alba] PASSEDtests/test_param.py::test_func[min‐alba] PASSED

=================== 10 tests deselected by '‐kalba' ================================== 4 passed, 10 deselected in 0.06 seconds ===============_______________________________ summary _______________________________

Page 20: Testare cu pytest

HooksPentru customizarea framework‑ului de testare (pytest):

Plugins

Un fișier  conftest.py  („plugin local”)

Referință: h昊ps://pytest.org/latest/plugins.html#pytest‑hook‑reference

Se pot modifica multe lucruri: colectare, rulare, output (rapoarte, detalii aserții,progres etc), opțiuni noi la linia de comandă.

Există plugin‑uri care au hook‑uri custom: pytest‑xdist, pytest‑bdd și probabilaltele.

Page 21: Testare cu pytest

PluginsFoarte multe sunt „builtin”, cele mai interesante:

doctest

junitxml

monkeypatch  (fixtură pentru monkey‑patching)

recwarn  (fixtură pentru aserții la warnings)

tmpdir  (fixtură pentru fișiere temporare)

capture  (captură stdout/stderr)

Page 22: Testare cu pytest

capture  (captură stdout/stderr)

Mai sunt multe alte pluginuri care defapt implementează nucleul pytest (colectare,rulare, output etc).

Plugins ﴾PyPI﴿Pluginuri externe:

pytest‑cov ‑ coverage

pytest‑benchmark ‑ benchmarks

pytest‑xdist ‑ rulare teste in paralel

pytest‑django ‑ testare aplicații Django

pytest‑capturelog ‑ captura logging

Page 23: Testare cu pytest

pytest‑capturelog ‑ captura logging

pytest‑splinter ‑ testare cu Splinter (teste UI cu webdriver Chrome/Firefox sau PhantomJS)

pytest‐splinter cu pytest‐djangoSplinter poate folosi Selenium și alte backend‑uri (PhantomJS).

Exemplu:def test_login(browser, db, live_server):    User.objects.create_superuser('user',                                  email='[email protected]',                                  password='abc')    browser.visit(live_server.url)

Page 24: Testare cu pytest

    form = browser.find_by_tag('form')

    form.find_by_name('username').fill('user')    form.find_by_name('password').fill('abc')    form.find_by_css('input[type=submit]').click()

    assert 'success' in browser.html

Link prezentare

Page 25: Testare cu pytest
Page 26: Testare cu pytest