Upload
cs-center
View
238
Download
3
Embed Size (px)
Citation preview
Лекция 10: Тестирование, документирование,
дистрибуция
Фёдор Гоголев
3 декабря 2014 г.
Тестирование
Тестирование: обзор
• Мы будем говорить о модульном тестировании.
• Тестировать, чтобы проверять.
• Тестировать, чтобы улучшать.
• Некоторые тестируют, чтобы писать.
• Модуль unittest.
• В стандартной библиотеке.
• Клон JUnit, до 2001 года назывался PyUnit.
• Модуль doctest.
• В стандартной библиотеке.
• Подходит для совсем небольших проектов.
1 / 35
Почему не assert
def cut_suffix(s, suffix):
if s.find(suffix) > 0:
s = s.rstrip(suffix)
return s
assert cut_suffix("abcd", "cd") == "ab", "positive"
assert cut_suffix("abcd", "abcd") == "", "whole"
assert cut_suffix("abcdcd", "cd") == "abcd", "repeat"
Traceback (most recent call last):
File "test.py", line 7, in <module>
assert cut_suffix("abcd", "abcd") == "", "whole"
AssertionError: whole
• Прерывается выполнение после первого AssertionError
• Практически отсутствует информация об ошибке
2 / 35
Модуль unittest
import unittest
def cut_suffix(s, suffix):
if s.find(suffix) > 0:
s = s.rstrip(suffix)
return s
class TestCutSuffix(unittest.TestCase):
def test_positive(self):
self.assertEqual(cut_suffix("abcd", "cd"), "ab")
def test_positive_whole(self):
self.assertEqual(cut_suffix("abcd", "abcd"), "")
def test_suffix_repeat(self):
self.assertEqual(cut_suffix("abcdcd", "cd"), "abcd")
if __name__ == "__main__":
unittest.main()
3 / 35
$ python cut_suffix_test.py
.FF
======================================================================
FAIL: test_positive_whole (__main__.TestCutSuffix)
----------------------------------------------------------------------
AssertionError: 'abcd' != ''
- abcd
+
======================================================================
FAIL: test_suffix_repeat (__main__.TestCutSuffix)
----------------------------------------------------------------------
AssertionError: 'ab' != 'abcd'
- ab
+ abcd
? ++
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=2)
4 / 35
Методы класса unittest.TestCase
• assertEqual(x, y) / assertNotEqual(x, y)
• assertTrue(x) / assertFalse(x)
• assertIs(x, y) / assertIsNot(x, y)
• assertIsNone(x) / assertIsNotNone(x)
• assertIn(x, collection) / assertNotIn(x, collection)
• assertIsInstance(obj, cls) / assertNotIsInstance(obj, cls)
• Есть специализированные методы для встроенныхколлекций и регулярных выражений, например:
• assertDictEqual(x, y)
• assertSetEqual(x, y)
• assertRegex(pattern, s)
• assertAlmostEqual(x, y, places=5)
5 / 35
>>> from unittest import TestCase
>>> TestCase().assertTrue("baz" in ["foo", "bar"])
...
AssertionError: False is not true
>>> TestCase().assertIn("baz", ["foo", "bar"])
...
AssertionError: 'baz' not found in ['foo', 'bar']
>>> TestCase().assertIs(0, True)
...
AssertionError: 0 is not True
>>> TestCase().assertSetEqual({"foo", "bar"}, {"foo", "baz"})
...
AssertionError: Items in the first set but not the second:
'bar'
Items in the second set but not the first:
'baz'
>>> TestCase().assertAlmostEqual(0.001, 0.002, places=2)
>>> TestCase().assertAlmostEqual(0.001, 0.002, places=3)
...
AssertionError: 0.001 != 0.002 within 3 places
6 / 35
Исключения в unittest
Класс TestCase передоставляет метод assertRaises для работы с
исключениями.
def fail():
return 1 / 0
class FailTest(unittest.TestCase):
def test_fail(self):
self.assertRaises(ZeroDivisionError, fail)
def test_fail_manager(self):
with self.assertRaises(Exception) as exc_manager:
fail()
self.assertIsInstance(exc_manager.exception,
ZeroDivisionError)
7 / 35
unittest: методы setUp и tearDown
• Иногда необходимо тестировать что-то, у чего есть
состояние. В таком случае надо инициализировать это
состояние отдельно для каждого теста. В этом помогают
методы setUp и tearDown класса TestCase:
• Например, протестируем класс Bits:
class TestBits(unittest.TestCase):
def setUp(self): # Вызывается до запуска каждого теста
self.bits = Bits(32)
def tearDown(self): # Вызывается после выполнения
pass # каждого теста
def test_bits_true(self):
for idx in range(len(self.bits))
self.bits[idx] = True
self.assertNotIn(False, self.bits)
def test_bits_false(self):
self.assertNotIn(True, self.bits)
8 / 35
Модуль doctest — тесты в документации
import doctest
def cut_suffix(s, suffix):
"""
Функция возвращает строку без суффикса.
>>> cut_suffix("abcd", "cd")
'ab'
>>> cut_suffix("abcd", "abcd")
''
>>> cut_suffix("abcdcd", "cd")
'abcd'
"""
if s.find(suffix) > 0:
s = s.rstrip(suffix)
return s
if __name__ == "__main__":
doctest.testmod()
9 / 35
$ python cut_suffix_test.py
*************************************************
File "__main__", line 7, in __main__.cut_suffix
Failed example:
cut_suffix("abcd", "abcd")
Expected:
''
Got:
'abcd'
*************************************************
File "__main__", line 9, in __main__.cut_suffix
Failed example:
cut_suffix("abcdcd", "cd")
Expected:
'abcd'
Got:
'ab'
*************************************************
1 items had failures:
2 of 3 in __main__.cut_suffix
***Test Failed*** 2 failures.
TestResults(failed=2, attempted=3)
10 / 35
doctest: исключения и состояние
def fail(n):
"""
Всего лишь падает с ZeroDivisionError.
>>> n = 5
>>> fail(n)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero
"""
return n / 0
• doctest работает со строковым выводом интерпретатора.
• Можно считать, что для каждого докстринга будет запущен
свой интерпретатор, все строки используют общее
пространство имён.
11 / 35
Лучшие системы
• Проблемы unittest:
• Общие претензии: неродной синтаксис и многословный.
• До недавнего времени был исключительно ограничен в
возможностях.
• Удивительно трудно найти все тесты для крупного проекта.
• Альтернативы:
• pytest — очень «магический», огромное количество
возможностей, сложнее прочих.
• nosetests — вырос из pytest, значительно проще, меньше
возможностей.
• Множество плагинов для каждой альтернативы.
• Обе альтернативы успешно запускают тесты от unittest и
doctest.
12 / 35
pytest: пример
@pytest.mark.parametrize("left, right",
[("foo", "boo"), ({1, 2}, {2, 3})])
def test_equality(left, right):
assert left == right
$ py.test test_something.py
___________ test_equality[foo-boo] ___________
left = 'foo', right = 'boo'
> assert left == right
E assert 'foo' == 'boo'
E - foo
E + boo
_________ test_equality[left1-right1] _________
left = set([1, 2]), right = set([2, 3])
> assert left == right
E assert set([1, 2]) == set([2, 3])
E Extra items in the left set:
E 1
E Extra items in the right set:
E 3
=========== 2 failed in 0.02 seconds ===========13 / 35
Тестирование: резюме
• Стандартная библиотека Python предоставляет несколько
инструментов для работы с тестами.
• Класс unittest.TestCase содержит десятки методов для
различных тестов, которые в первую очередь нужны для
удобной визуализации ошибок.
• Для небольших проектов стоит использовать doctest.
• Вы можете дополнительно посмотреть на альтернативные
системы тестирования: pytest и nosetests.
14 / 35
Документирование
Документирование: история
• Документация в Python пишется в докстрингах модуля,
класса, функции, доступна через аттрибут __doc__ и может
быть выведена, например, с помощью встроенной
функции help.
• В 2001 году был написан сервер pydoc, который
анализировал объекты и генерировал соответствующий
HTML, до сих пор доступный в виде модуля.
• Ещё через два года появился автоматический генератор
API документации Epydoc, который поддерживал
Javadoc-style докстринги, аннотации типов, ссылки на
объекты и т. д.
• Сам Python к этому времени всё ещё использовал LATEXи не
было особой связи между докстрингами в коде и итоговой
документацией.
• С версии 2.6 (2008 год) Python перешёл на генератор
документации Sphinx и на reStructuredText, которые
теперь стали de facto стандартом документации.15 / 35
Sphinx
• Sphinx — основная на данный момент система
документирования для Python.
• Использует язык разметки reStructuredText (rst, rest),
специально созданный для документирования Python.
• Поддерживает большое количество форматов вывода.
Используется, как правило, HTML.
• Поддерживает огромное количество расширений, включая
систему коллекции документации из докстрингов.
• Принципиально поддерживает разные языки
программирования. Но не получил широкого
распространения нигде кроме Python.
18 / 35
def double_hash(k, value, modulo):
"""
Функция возвращает список чисел не больше ``modulo``,
являющихся результатом применения ``k`` хеш
функций к ``value``.
`Подробнее <http://bit.ly/double-hashing>`_.
:param int k: количество хеш-функций, которые необходимо
построить
:param int value: значение
:param int modulo: модуль
:rtype: int
"""
h1 = hash(abs(value))
h2 = (h1 >> 32) & sys.maxsize
return [(h1 + (h2 + 1) * i) % modulo for i in range(k)]
19 / 35
reStructuredText: Разметка
• Инлайн разметка: *курсивный*, **жирный**,
``моноширинный``
• Группировка:Заголовок
=========
Подзаголовок
------------
• Списки:* Ненумерованный список
* Второй элемент спика
#. Вложенный нумерованный список
• Внешние ссылки:
`Текст ссылки <http://compscicenter.ru/>`_
• Внутренние ссылки:• Метка в документации: .. _label-name:
• Ссылка на метку: :ref:`Текст ссылки <label-name>`
• Ссылка на Python объект: :func:`double_hash`,
:class:`BloomFilter`, :mod:`string`
20 / 35
reStructuredText: Разметка
• Параграфы отбиваются друг от друга одной или
несколькими пустыми строками.
• Блоки кода выделяются маркером :: и должны быть
идентированы.
• Подробнее о разметке:
http://sphinx-doc.org/rest.html
Первый параграф, возможно, многострочный, содержит какой-то
текст.
Следующий параграф и пример блока кода::
>>> from pyte import Screen
>>> screen = Screen(80, 40)
21 / 35
reStructuredText: Список определений, список полей
• Существует синтаксис для списка определений и списка
полей.
• Каждый элемент списка определений содержит термин и
определение, определение может содержать несколько
параграфов.
• Список полей удобно воспринимать как таблицу
ключ-значение, при этом отображение полей, как правило,
зависит от контекста использования.
• Списки определений и списки полей используются,
например, для аннотаций объектов.
Термин
Определение термина.
Следующий термин : классификатор : классификатор 2
Определение следующего термина.
:ключ: значение
:ключ с параметрами: значение
22 / 35
Аннотации
• Речь о маркерах в документации, которые определяют:
принимаемые значения, их типы, возвращаемые значения,
возможные поднимаемые исключения.
• Подобные маркеры в формате Javadoc поддерживал ещё
Epydoc.
• Есть несколько общепринятых стандартов12 для
добавления аннотаций к документируемым объектам.
• Каждый из них воспринимается современными IDE,
обеспечивая соответствующие подсказки.
1http://bit.ly/annotate-numpy2http://bit.ly/annotate-sphinx
23 / 35
Предлагаемый Shpinx
def double_hash(k, value, modulo):
"""
:param int k: количество хеш-функций, которые необходимо
построить
:param int value: значение
:param int modulo: модуль
:return: список чисел не больше ``modulo``,
являющихся результатом применения ``k`` хеш
функций к ``value``.
:rtype: int
:raises ValueError: параметр k должен быть
положительным числом
"""
24 / 35
Стандарт numpy
def double_hash(k, value, modulo):
"""
Parameters
----------
k : int
количество хеш-функций, которые необходимо построить
value : int
значение
modulo : int
модуль
Returns
-------
int
список чисел не больше ``modulo``,
являющихся результатом применения ``k`` хеш
функций к ``value``.
Raises
------
ValueError
параметр k должен быть положительным числом
"""
25 / 35
reStructuredText: Директивы
• reStructuredText был спроектирован как расширяемый
язык.
• Директива — основная единица расширения, некоторые изних:
• .. math:: — выражение LATEXформулы.
• .. image:: — изображение в документе
• .. csv-table:: — таблица из csv
Например, выражение
.. math::
\epsilon \approx
\left( 1 - e^{\dfrac{-k n}{m}} \right)^k.
Будет показано как:
ε ≈
1− e
−kn
m
k
.
26 / 35
Sphinx: Быстрый старт
• Sphinx поставляется с программой sphinx-quickstart,
которая, после ответов на несколько вопросов, строит
стандартное дерево директорий для документации. Стоит
ответить «yes» хотя бы на вопрос о включении
расширения autodoc.
• Расширение autodoc позволяет генерировать
документацию из .py файлов так же, как это делает pydoc.
• В начале, для генерации осмысленной документации
достаточно воспользоваться программой sphinx-apidoc.
• В зависимости от ответов на вопросы, получается
примерно такое дерево директорий:
docs # Корневая директория документации
|-- _build # Скомпилированная документация
|-- conf.py # Файл настроек, в частности,
# включенные расширения
|-- index.rst # Точка входа документации
|-- pyte.rst # Файл сгенерированный sphinx-apidoc
27 / 35
Sphinx: Быстрый старт
$ sphinx-quickstart
Welcome to the Sphinx 1.2.3 quickstart utility.
> Root path for the documentation [.]: docs
...
Please indicate if you want to use one of the following
Sphinx extensions:
> autodoc: automatically insert docstrings from
modules (y/n) [n]: yes
...
Finished: An initial directory structure has been created.
$ sphinx-apidoc -o docs pyte
Creating file docs/pyte.rst.
Creating file docs/modules.rst.
$ PYTHONPATH=. sphinx-build docs/ docs/_build/html
Running Sphinx v1.2.2
...
build succeeded.
28 / 35
pyte.rst
• sphinx-autodoc генерирует примерно такие файлы
документации:
pyte package
============
Module contents
---------------
.. automodule:: pyte
:members:
:undoc-members:
:show-inheritance:
pyte.charsets module
--------------------
.. automodule:: pyte.charsets
:members:
:undoc-members:
:show-inheritance:
29 / 35
Документирование: резюме
• Подавляющее большинство Python проектов
документируются с помощью Sphinx и reStructuredText.
• Sphinx поддерживает автоматическую генерацию
документации из исходного кода.
• Мы рассмотрели базовый синтаксис reStructuredText иSphinx, за кадром остались:
• Многие встроенные директивы, например .. toctree::
для генерации оглавления.
• Множество расширений, например, автоматическое
выполнение доктестов.
• Темы и шаблоны итоговой документации.
• Другие форматы вывода: PDF, ePub, man и т. д.
31 / 35
Дистрибуцияв четырёх слайдах
Репозиторий
• С Python поставляется огромная стандартная библиотека.
• Однако в ней нет фильтра Блума и других полезных вещей.
• Есть репозитории Python пакетов.
• Есть центральный репозиторий —
http://pypi.python.org/, более пятидесяти тысяч
пакетов.
• Утилита, для работы с репозиторием — pip:
$ pip install sphinx
...
$ python -c "import sphinx; print(sphinx.__version__)"
1.2.3
$ pip uninstall sphinx
32 / 35
Структура Python проекта
pyte # Корневая директория проекта
|-- docs/ # Директория с документацией
|-- ...
|-- pyte/ # Пакет в проекте
|-- __init__.py # Инициализатор пакета, файл
# доступен при вызове `import pyte`
|-- screen.py # Один из модулей, импортируется
# как `import pyte.screen`
|-- tests/ # Тесты для модуля
|-- __init__.py # Тесты тоже являются пакетом,
# поэтому нужен инициализатор
|-- test_config.py # Один из модулей с тестами
|-- setup.py # Файл конфигурации проекта
33 / 35
setup.py
from distutils.core import setup
setup(name="pyte",
version="0.4.8",
packages=["pyte"],
tests_require=["pytest"],
install_requires=["readline>=6.2.4.1"],
platforms=["any"],
author="Sergei Lebedev",
author_email="[email protected]",
description="Simple VTXXX-compatible terminal emulator."
keywords=["vt102", "vte", "terminal emulator"],
url="https://github.com/selectel/pyte",
)
34 / 35
setup.py
• С помощью setup.py (фактически distutils) можно:
• Собрать пакет.
• Протестировать пакет.
• Загрузить пакет в репозиторий.
• Выполнить тесты.
• Собрать документацию.
• Загрузить документацию.
• Например:
$ python setup.py install
running install
...
Finished processing dependencies for pyte==0.4.8
• Пакет установлен в систему.
35 / 35