Код в тестах обычно простой, т.к. выполняет довольно тривиальные операции проверки, сравнения и т.д. Когда много похожего кода, то логично подумать о его краткости.
Note
nose у меня давно вошел в набор обязательных инструментов для тестирования, так что в примерах есть его влияние.
from unittest import TestCase answer = 42 class TestAnswer(TestCase): def setUp(self): print('setup') def test(self): self.assertEquals(answer, 42) def tearDown(self): print('teardown')
Исторически сложилось, что unittest использует верблюжью нотацию для setUp, tearDown, assert* (assertTrue, assertEquals…) методов. Но в python есть PEP 8, в котором принято использовать подчеркивание в названиях функций (методов), и в nose.tools можно найти аналогичные функции, но с подчеркиванием (assert_true, assert_equals) для любителей PEP 8.
from nose.tools import assert_equal, with_setup answer = 42 def setup_func(): print('setup') def teardown_func(): print('teardown') @with_setup(setup_func, teardown_func) def test_answer(): assert_equal(answer, 42)
В последнее время второй подход компоновки тестов мне все больше нравится. Почему?
и в первом и во втором подходе приходится давать имя модулю, который будет содержать наши тесты. Но в первом нужно еще придумывать имя классу-контейнеру, а т.к. мне нравятся небольшие модули (в них проще ориентироваться), то в большинстве случаев название класса - тавтология:
test_auth.py:TestAuth.test_login test_auth.py.test_login
в первом подходе вроде лучше выглядят SetUp, TearDown методы, во втором приходится импортировать декоратор with_setup. Но и тут можно выделить плюс, обычно название класса подбираю по содержимым тестам
class TestAuth(TestCase): def test_login() ... def test_logout() ...
но когда для test_login нужен setUp метод, а для test_logout нет, то тут приходится класс-контейнеры компоновать в зависимости от используемых SetUp, TearDown методов. В общем присутствует неоднозначность и это не очень хорошо :)
в классе-контейнере забирается один отступ, а отступы ценны, когда соблюдаешь ограничение в 80 символов;
в первом подходе класс наследуется от unittest.TestCase, при вызове каждого assert* метода логично обращаться к self и тут опять у нас крадут символы:
self.assertEqual assert_equal...4
1 answer = 42 2 3 4 def test_answer(): 5 ''' 6 >>> answer 7 42 8 ''' 9 assert False
Выглядит кратко, хотя конечно такой формат тестов не всегда подходит…
Note
Если запускать через nose ($ nosetests –with-doctest), то строка 9 не вызывается.
answer = 43 def test_answer(): assert answer == 42
После запуска, вывод:
$ nosetest ====================================================================== FAIL: test.test_answer ---------------------------------------------------------------------- Traceback (most recent call last): ... assert answer == 42 AssertionError
Очень заманчиво: не нужен дополнительный импорт, лаконично. Но вот при выводе не известно какое значение содержит переменная answer. Правда тут может порадовать nose и даже двумя вариантами:
$ nosetests --pdb-failures
...
-> assert answer == 42
(Pdb) answer
43
приходится вводить answer - лишние телодвижения :).
Следующий вариант еще красивее:
$ nosetest -d ====================================================================== FAIL: test.test_answer ---------------------------------------------------------------------- Traceback (most recent call last): ... assert answer == 42 AssertionError: >> assert 43 == 42
так что, в принципе, тесты можно писать через assert без потери информативности вывода, нужно только использовать правильные “пускальщики”.
from nose.tools import eq_ answer = 43 def test_answer(): eq_(answer, 42)
После запуска, вывод:
FAIL: test.test_answer ---------------------------------------------------------------------- Traceback (most recent call last): ... eq_(answer, 42) AssertionError: 43 != 42
Заменили assert_equal на более короткий вариант eq_, вывод ошибки будет полностью аналогичен. Т.е. при выводе увидим, что answer на самом деле 43 и пойдем сразу искать ошибку в коде. Один нюанс, что тесты не заканчиваются проверкой на eq_ и ok_, которые есть в nose.tools, набор методов нужен более обширный…
pytest - это аналог nose, со своими “плюшками”, он умеет запускать большинство тестов написанных для nose.
attest - интересный подход (python way) от известной команды Pocoo. Пример из документации:
from attest import Tests math = Tests() @math.test def arithmetics(): """Ensure that the laws of physics are in check.""" assert 1 + 1 == 2 if __name__ == '__main__': math.run()
Oktest для лаконичности - идея прикольная. Пример из документации:
from oktest import ok ok (x) > 0 # same as assert_(x > 0) ok (s) == 'foo' # same as assertEqual(s, 'foo') ok (s) != 'foo' # same as assertNotEqual(s, 'foo')
В python есть множество способов для написания и запуска тестов, в статье упоминаются не все. Если задаться целью, то можно писать красивые и лаконичные тесты.