testare cu pytest
Embed Size (px)
TRANSCRIPT

Testare cu pytest

Testare cu pytest
Ionel Cristian Mărieș — Python / OSS
blog.ionelmc.rogithub.com/ionelmc

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

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

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): ...

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]

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]

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

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]

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:

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): ...

...
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):

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):

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')]

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!

[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

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

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

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 _______________________________

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.

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)

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

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)

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

