- Введение
- О doctest
- Doctest для функций
- Debug doctest
- Тесты классов
- Много тестов - много проблем?
- Выводы
Введение
Инструменты для тестирования в Python обширны. Какие-то из них легко внедряются в код, какие-то нет. Сегодня мы познакомимся с новым инструментом - doctest. Этот с натяжкой попадает под категорию - модульное тестирование. Можно применять для тестирования как функций, так и классов.
О doctest
doctest интересен тем, что использование выглядит, как будто пишем код в REPL.
Функция factorial - для вычисления факториала. Использование функции такое:
>>> factorial(5)
В результате вызова:
>>> factorial(5)
120
И здесь на арену выходит doctest. Модуль ищет фрагменты текста, которые выглядят как интерактивные python сессии. Далее выполняет сеансы и проверяет, совпадает ли с тем что указано в docstring.
В принципе на этом можно и закончить урок - пишем в докстроках код из REPL и тест готов. Doctest прямо в чистую выполняет код из докстрок, а значит можно городить сложную логику.
Несколько распространенных способов использования doctest:
- Проверить, что документация к функциям отражает суть и не устарела.
- Для выполнения регрессионного тестирования, убедившись, что интерактивные примеры из тестового файла или тест-объект работает, как ожидалось.
- Писать учебник документации для пакета, обильно иллюстрированный примерами ввода-вывода.
Стоит рассмотреть несколько примеров использования.
Doctest для функций
Для примера рассмотрим такой код:
"""
This is the "example" module.
The example module supplies one function, factorial(). For example,
>>> factorial(5)
120
"""
def factorial(n):
"""Return the factorial of n, an exact integer >= 0.
If the result is small enough to fit in an int, return an int.
Else return a long.
>>> [factorial(n) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> [factorial(long(n)) for n in range(6)]
[1, 1, 2, 6, 24, 120]
>>> factorial(30)
265252859812191058636308480000000L
>>> factorial(30L)
265252859812191058636308480000000L
>>> factorial(-1)
Traceback (most recent call last):
...
ValueError: n must be >= 0
Factorials of floats are OK, but the float must be an exact integer:
>>> factorial(30.1)
Traceback (most recent call last):
...
ValueError: n must be exact integer
>>> factorial(30.0)
265252859812191058636308480000000L
It must also not be ridiculously large:
>>> factorial(1e100)
Traceback (most recent call last):
...
OverflowError: n too large
"""
import math
if not n >= 0:
raise ValueError("n must be >= 0")
if math.floor(n) != n:
raise ValueError("n must be exact integer")
if n+1 == n: # catch a value like 1e300
raise OverflowError("n too large")
result = 1
factor = 2
while factor <= n:
result *= factor
factor += 1
return result
if __name__ == "__main__":
import doctest
doctest.testmod()
Функция вычисляет факториал в цикле. В блоке с docstring заметно и описание функции, и конструкции похожие на строки кода из REPL. Даже не похожие, а прямо они! Это основная особенность doctest - прямо в документации пишешь тесты. Что может быть проще? А, ну да, надо документацию писать.
В примере простые вызовы функций да и получение исключений.
Честно и без обмана. Чтобы убедиться что тесты работают, выполняем:
python example.py -v
Trying:
factorial(5)
Expecting:
120
ok
Trying:
[factorial(n) for n in range(6)]
Expecting:
[1, 1, 2, 6, 24, 120]
ok
Trying:
[factorial(long(n)) for n in range(6)]
Expecting:
[1, 1, 2, 6, 24, 120]
ok
Trying:
factorial(30)
Expecting:
265252859812191058636308480000000L
ok
Trying:
factorial(30L)
Expecting:
265252859812191058636308480000000L
ok
Trying:
factorial(-1)
Expecting:
Traceback (most recent call last):
...
ValueError: n must be >= 0
ok
Trying:
factorial(30.1)
Expecting:
Traceback (most recent call last):
...
ValueError: n must be exact integer
ok
Trying:
factorial(30.0)
Expecting:
265252859812191058636308480000000L
ok
Trying:
factorial(1e100)
Expecting:
Traceback (most recent call last):
...
OverflowError: n too large
ok
2 items passed all tests:
1 tests in __main__
8 tests in __main__.factorial
9 tests in 2 items.
9 passed and 0 failed.
Test passed.
Тесты пройдены. Функция работает! Использование doctest крайне легкое и это определяет простоту внедрения.
Debug doctest
Дебажить такие тесты совсем просто и делается двумя путями:
- писать код в REPL, что самое лучшее - сразу копируешь код в строки документации.
- воспользоваться функцией doctest.script_from_examples:
import doctest
print doctest.script_from_examples(r"""
Set x and y to 1 and 2.
>>> x, y = 1, 2
Print their sum:
>>> print x+y
3
""")
Тесты классов
Тестировать функции легче и редко вызывает сложности. А вот с классами... Рассмотрим класс:
class Test(object):
def __init__(self, number):
self.number = number
def multiply_by_2(self):
return self.number*2
Типичный класс, принимает аргументы в конструктор и есть какие-то методы, которые работают с этими данными. Как такое тестировать? Да не сложнее функций:
class Test(object):
"""
>>> a=Test(5)
>>> a.multiply_by_2()
10
"""
def __init__(self, number):
self.number = number
def multiply_by_2(self):
return self.number*2
if __name__ == "__main__":
import doctest
doctest.testmod()
Что мы тут видим - в docstring такой же код из REPL как и для функций.
А вот пример выполнения теста:
> python example.py -v
Trying:
a=Test(5)
Expecting nothing
ok
Trying:
a.multiply_by_2()
Expecting:
10
ok
3 items had no tests:
__main__
__main__.Test.__init__
__main__.Test.multiply_by_2
1 items passed all tests:
2 tests in __main__.Test
2 tests in 4 items.
2 passed and 0 failed.
Test passed.
Доказал, что для простого класса и функций тестирование слабо отличается?
Много тестов - много проблем?
Очевидный недостаток doctest - когда тестов становится много, то неудобно писать в docstring. Рекомендуется выносить в отдельный файл.
Для такого тестирования в doctest есть функция:
doctest.testfile("test.txt")
В test.txt помещаете текст из docstring. Например так:
Тестирование функции mult(a,b)
>>> from test_in_other_file import mult
>>> mult(2,3)
6
Выводы
По сравнению с классическими юнит-тестами, у доктестов есть как плюсы:
- простота написания тестов - можно скопировать прямо из REPL
- документация всегда соответствует коду
так и минусы:
- сложный код быстро становится не читаемым
- текстовый редактор не подсветит такой код,
- статический анализатор не найдет в нем ошибок
- подходит не для всех функций
Впрочем, ничто не мешает применять докстесты для мелких очевидных вещей (как в примере), и юнит-тесты для более сложных задач.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»