В прошлый раз мы переместили тесты для нашей факториальной функции, управляемой тестами, из базовых операторов assert в модуль unittest Python:

import unittest
from factorial import factorial
class TestFactorial(unittest.TestCase):
    def test_base_case(self):
        self.assertEqual(factorial(0), 1)
    def test_first_recursive_case(self):
        self.assertEqual(factorial(2), 2)
    def test_recursing_further(self):
        self.assertEqual(factorial(5), 120)
unittest.main()

Мы увидели, что написание наших тестов таким образом помогло нам увидеть, где именно мы допустили ошибки, когда ввели регрессии.

Сегодня появились еще две важные функции запуска тестов: возможность выбрать один или несколько тестов из большого набора тестов и помощники для надежного тестирования ошибок.

Целевые тесты

Если один из многих тестов терпит неудачу, unittest не только показывает нам, какой именно тест не прошел, но и позволяет нам повторно запустить только один неудачный тест. Таким образом, мы можем проверить наши исправления быстрее, чем время, необходимое для запуска всех тестов. Это не имеет значения, когда все наши тесты выполняются за тысячные доли секунды, но на более крупных и медленных наборах тестов, необходимых для реальных приложений, мы можем сэкономить много времени, выполняя только те тесты, которые нам нужны для той части код, который мы редактируем.

Работая таким образом, unittest импортирует наш тестовый файл как модуль, поэтому мы защищаем вызов unittest.main():

class TestFactorial(unittest.TestCase):
...
if __name__ == '__main__':
    unittest.main()

Теперь мы можем запустить его либо как скрипт, как мы это делали, либо вызвав модуль unittest. Вызов модуля unittest позволяет нам запустить определенный тестовый модуль, тестовый класс или отдельный тест:

$ python -m unittest test_factorial.TestFactorial.test_base_case
.
--------------------------------------------------------------------
Ran 1 test in 0.000s
OK

По мере роста нашей программы мы получаем много тестовых файлов, и когда мы хотим запустить их все, unittest может обнаружить их для нас:

python -m unittest
...
--------------------------------------------------------------------
Ran 3 tests in 0.001s
OK

Проверка на ошибки

Библиотеки тестирования, такие как unittest, также дают нам удобные способы проверки на наличие ошибок.

Вспомните, как мы ранее проверяли, что наша факториальная функция выдает ошибку, когда ей дается отрицательное число. Это был ненадежный способ написать тест, потому что, например, мы могли забыть строку «assert False» и случайно написать тест, который вообще ничего не проверяет:

try:
    factorial(-1)
    # Forgot to assert False here
except ValueError as e:
    pass

Если бы мы сначала писали тесты, мы должны были бы заметить ошибку, когда мы ее сделали, потому что тест прошел, когда мы ожидали, что он провалится. Но мы также можем надежно защититься от трюков, которые играет с нами наш собственный мозг, поэтому мы полагаемся на unittest, который дает нам более простой и надежный способ проверки того, когда наши программы должны выдавать ошибки:

class TestFactorial(unittest.TestCase):
    def test_lt_zero_error(self):
        with self.assertRaises(ValueError):
            factorial(-1)

Работает автоматически

Теперь мы полностью переписали наши факториальные тесты для использования библиотеки unittest и отделили их от кода, вычисляющего функцию.

Такое разделение необходимо для больших приложений, но имеет существенный недостаток по сравнению с нашими исходными операторами assert: ничто не гарантирует выполнение тестов. Когда мы вставляли операторы assert в код нашего приложения, любой, кто импортировал наш модуль factorial, обязательно запускал наши тесты, но когда мы вставляли наши тесты в другое место, их стало легко игнорировать. Поэтому в большинстве проектов нам также нужен робот для автоматического запуска наших тестов и выявления ошибок: это большая часть того, для чего предназначены инструменты «непрерывной интеграции». Но, как всегда, когда мы передаем работу роботам, есть ловушки. Мы рассмотрим это и другие сложности реального мира в следующий раз.