Ionel Cristian Mărieș — Python / OSS
[1] | http://pytest.org/latest/faq.html#what-s-this-magic-with-pytest-historic-notes |
În loc de:
class MyTest(unittest.TestCase): def test_stuff(self): ...
Avem doar:
def test_stuff(self): ...
Î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]
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: E - [1, 2, 4] E ? ^ E + [1, 2, 3] E ? ^
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 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 ...
Î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): 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) |
Finalizatorul e echivalentul tearDown din unittest.
Cel mai simplu e cu pytest.yield_fixture:
@pytest.yield_fixture def mydbfixture(request): conn = sqlite3.connect(":memory:") 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')]
@pytest.yield_fixture( scope="function", autouse=False ) def myfixture(request): ...
scope: „durata de viață” a fixturii
autouse: activare automată
O fixtură poate să depindă de alte fixturi. Reluând exemplul anterior:
@pytest.yield_fixture 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()
@pytest.yield_fixture 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')]
Sunt niște simpli decoratori la funcțiile de test sau fixturi:
[1] | Când lenea e mare dar ești nostalgic și nu vrei să ștergi testul. Poate repari problema cândva! |
@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
@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 tests/test_param.py::test_func[func3-numbers1] PASSED
@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 tests/test_param.py::test_func[min-neagra] PASSED
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 _______________________________
Pentru customizarea framework-ului de testare (pytest):
Referință: https://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.
Foarte multe sunt „builtin”, cele mai interesante:
Mai sunt multe alte pluginuri care defapt implementează nucleul pytest (colectare, rulare, output etc).
Pluginuri externe:
Splinter poate folosi Selenium și alte backend-uri (PhantomJS).
Exemplu:
def test_login(browser, db, live_server): User.objects.create_superuser('user', email='user@example.com', 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
Table of Contents | t |
---|---|
Exposé | ESC |
Full screen slides | e |
Presenter View | p |
Source Files | s |
Slide Numbers | n |
Toggle screen blanking | b |
Show/hide slide context | c |
Notes | 2 |
Help | h |