В моем текущем проекте я использую unittest , minimock , nose . В прошлом я интенсивно использовал doctests, но в больших проектах некоторые тесты могут быть немного громоздкими, поэтому я склонен ограничивать использование doctests для более простых функций.
Если вы используете setuptools или распространять (вы должны перейти к распространению), вы можете настроить нос в качестве сборщика тестов по умолчанию, чтобы вы могли запускать свои тесты с помощью " python setup.py test "
setup(name='foo',
...
test_suite='nose.collector',
...
Теперь запущенный «python setup.py test» вызовет нос, который будет сканировать ваш проект на предмет вещей, похожих на тесты, и запускать их, накапливая результаты. Если у вас также есть doctest в вашем проекте, вы можете запустить тестирование носа с опцией --with-doctest, чтобы включить плагин doctest.
нос также имеет интеграцию с покрытием
nosetests --with-coverage.
Вы также можете использовать опции - cover-html --cover-html-dir , чтобы сгенерировать отчет о покрытии HTML для каждого модуля с выделением каждой строки кода, которая не тестируется. Я не стал бы слишком одержим получением покрытия, чтобы сообщить о 100% тестировании покрытия для всех модулей. Некоторый код лучше оставить для интеграционных тестов, о которых я расскажу в конце.
Я стал большим поклонником minimock, так как это делает тестирование кода с большим количеством внешних зависимостей действительно простым. Хотя он работает действительно хорошо в паре с doctest, его можно использовать с любой средой тестирования, использующей класс unittest.TraceTracker . Я бы посоветовал вам не использовать его для тестирования всего вашего кода, поскольку вы все равно должны попытаться написать свой код, чтобы каждая единица перевода могла тестироваться изолированно, без насмешек. Хотя иногда это невозможно.
Вот (непроверенный) пример такого теста с использованием minimock и unittest:
# tests/test_foo.py
import minimock
import unittest
import foo
class FooTest(unittest2.TestCase):
def setUp(self):
# Track all calls into our mock objects. If we don't use a TraceTracker
# then all output will go to stdout, but we want to capture it.
self.tracker = minimock.TraceTracker()
def tearDown(self):
# Restore all objects in global module state that minimock had
# replaced.
minimock.restore()
def test_bar(self):
# foo.bar invokes urllib2.urlopen, and then calls read() on the
# resultin file object, so we'll use minimock to create a mocked
# urllib2.
urlopen_result = minimock.Mock('urlobject', tracker=self.tracker)
urlopen_result.read = minimock.Mock(
'urlobj.read', tracker=self.tracker, returns='OMG')
foo.urllib2.urlopen = minimock.Mock(
'urllib2.urlopen', tracker=self.tracker, returns=urlopen_result)
# Now when we call foo.bar(URL) and it invokes
# *urllib2.urlopen(URL).read()*, it will not actually send a request
# to URL, but will instead give us back the dummy response body 'OMG',
# which it then returns.
self.assertEquals(foo.bar('http://example.com/foo'), 'OMG')
# Now we can get trace info from minimock to verify that our mocked
# urllib2 was used as intended. self.tracker has traced our calls to
# *urllib2.urlopen()*
minimock.assert_same_trace(self.tracker, """\
Called urllib2.urlopen('http://example.com/foo)
Called urlobj.read()
Called urlobj.close()""")
Юнит-тесты не должны быть единственными видами тестов, которые вы пишете. Они, безусловно, полезны, и IMO чрезвычайно важны, если вы планируете поддерживать этот код в течение длительного периода времени. Они упрощают рефакторинг и помогают улавливать регрессии, но на самом деле они не проверяют взаимодействие между различными компонентами и то, как они взаимодействуют (если вы все делаете правильно).
Когда я начинаю приближаться к тому моменту, когда у меня есть в основном готовый продукт с достойным тестовым покрытием, который я собираюсь выпустить, я хочу написать хотя бы один интеграционный тест, который запускает всю программу в изолированной среде.
У меня был большой успех в моем текущем проекте. У меня было около 80% покрытия модульных тестов, а остальной код был таким, как разбор аргументов, диспетчеризация команд и состояние приложения верхнего уровня, что трудно охватить в модульных тестах. Эта программа имеет множество внешних зависимостей, работает около десятка различных веб-сервисов и взаимодействует с около 6000 работающих компьютеров, поэтому запускать ее изолированно оказалось довольно сложно.
Я закончил тем, что написал интеграционный тест, который порождает сервер WSGI, написанный с eventlet и webob , который имитирует все сервисы, с которыми моя программа взаимодействует в работе. Затем обезьяна тестирования интеграции исправляет нашу клиентскую библиотеку веб-службы , чтобы перехватить все HTTP-запросы и отправить их в приложение WSGI. После этого он загружает файл состояния, который содержит сериализованный снимок состояния кластера, и вызывает приложение, вызывая его функцию main (). Теперь все внешние службы, с которыми взаимодействует моя программа, смоделированы, так что я могу запускать свою программу так, как она будет выполняться в производственном режиме повторяемым образом.