Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testare cu pytest

Testare cu pytest

O introducere în pytest și de ce este mai bun decât nose sau unittest.

HTML5 version: http://blog.ionelmc.ro/presentations/pytest/
Presented at: http://www.meetup.com/RoPython-Cluj/events/222506306/

Tweet

More Decks by Ionel Cristian Mărieș

Other Decks in Programming

Transcript

  1. Un pic de istorie Am 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
  2. 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
  3. 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): ...
  4. 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]
  5. Inspecție aserții def 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 != 3 E Full diff:
  6. 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']
  7. E At index 0 diff: "<?xml version='1.0' encoding='UTF‐8'?>\n" != 'f

    E 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="3 E ‐ 'xmlns="http://www.w3.org/2000/svg"><path d="M 10 32 L 10 33 L E ‐ '32 z M 23 8 L 23 9 L 24 9 L 24 8 z M 21 24 L 21 25 L 22 25 L E ‐ '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.fixture def myfixture(request):
  8. 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_fixture def mydbfixture(request): conn = sqlite3.connect(":memory:")
  9. 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):
  10. ... 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 foarte convenabil. Fixturi în fixturi O fixtură poate să depindă de alte fixturi. Reluând exemplul anterior: @pytest.yield_fixture def mydb(request):
  11. 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_fixture
  12. 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')]
  13. Markers Sunt 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!
  14. Parametrizare @pytest.mark.parametrize(['a', 'b'], [ (1, 2), (2, 1), ]) def

    test_param(a, b): assert a + b == 3 collected 2 items tests/test_param.py::test_param[1‐2] PASSED tests/test_param.py::test_param[2‐1] PASSED
  15. Parametrizare în fixturi @pytest.fixture(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] PASSED tests/test_param.py::test_func[func1‐numbers0] PASSED tests/test_param.py::test_func[func1‐numbers1] PASSED tests/test_param.py::test_func[func2‐numbers0] PASSED tests/test_param.py::test_func[func2‐numbers1] PASSED tests/test_param.py::test_func[func3‐numbers0] PASSED
  16. tests/test_param.py::test_func[func3‐numbers1] PASSED Parametrizare cu etichete @pytest.fixture(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] PASSED tests/test_param.py::test_func[sum‐neagra] PASSED tests/test_param.py::test_func[len‐alba] PASSED tests/test_param.py::test_func[len‐neagra] PASSED tests/test_param.py::test_func[max‐alba] PASSED tests/test_param.py::test_func[max‐neagra] PASSED tests/test_param.py::test_func[min‐alba] PASSED
  17. tests/test_param.py::test_func[min‐neagra] PASSED Selecție parametrii Putem selecta pe parametrii: $ py.test

    ‐k alba ‐v ========================= test session starts ========================= platform win32 ‐‐ Python 3.4.3 ‐‐ py‐1.4.27 ‐‐ pytest‐2.7.1 collected 14 items tests/test_param.py::test_func[sum‐alba] PASSED tests/test_param.py::test_func[len‐alba] PASSED tests/test_param.py::test_func[max‐alba] PASSED tests/test_param.py::test_func[min‐alba] PASSED =================== 10 tests deselected by '‐kalba' =================== =============== 4 passed, 10 deselected in 0.06 seconds =============== _______________________________ summary _______________________________
  18. Hooks Pentru 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 probabil altele.
  19. Plugins Foarte 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)
  20. 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
  21. pytest‑splinter ‑ testare cu Splinter (teste UI cu webdriver Chrome/Firefox

    sau PhantomJS) pytest‐splinter cu pytest‐django Splinter 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)