Существуют три наисложнейшие проблемы в программировании: именование, кэширование и выход за границу массива. Проверка пограничных случаев поведения кода наиболее важна, но эта зона также наименее тестируема. Придумать и предугадать все возможные ситуации человеку тяжело, и порой мы что-то упускаем из виду. Вот было бы здорово, если бы тесты сами находили такие случаи, при которых код падает… Мечты? О том, как превратить их в реальность, и рассказал Александр.
Мир глазами нейросетей, Данила Байгушев, Александр Сноркин ()
«QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co
1. QuickCheck в PythonQuickCheck в Python
Проверка гипотез и поиск ошибокПроверка гипотез и поиск ошибок
Шорин Александр / @kxepal
2. О чем пойдет речь:О чем пойдет речь:
Что не так с нашими тестами
Что такое QuickCheck...
...и как его неправильно портировать
Знакомство с Hypothesis
Время офигенных историй
3. Что не так с нашими тестами?Что не так с нашими тестами?
4. Простейший примерПростейший пример
def qsort(list_):
if len(list_) <= 1:
return list_
head = qsort([x for x in list_[1:] if x < list_[0]])
tail = qsort([x for x in list_[1:] if x > list_[0]])
return head + [list_[0]] + tail
(да, в жизни все сложнее)(да, в жизни все сложнее)
7. ...хотя может показаться, что нет...хотя может показаться, что нет
@pytest.mark.parametrize(('value', 'result'), [
([], []),
([1, 2, 3], [1, 2, 3]),
([3, 2, 1], [1, 2, 3]),
(['a', 'c', 'b'], ['a', 'b', 'c']),
(['a', 'A'], ['A', 'a']),
([[0], [3], [1, 2, 3]], [[0], [1, 2, 3], [3]]),
...
])
def test_qsort(value, result):
assert qsort(value) == result
8. Можем ли мы проверить все допустимые случаи?Можем ли мы проверить все допустимые случаи?
+----------------------------------------------------------------------+
| ВЕЛИКОЕ ВСЁ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| +---------------------+
| | Приемлемые данные |
| | Х |
+------------------------------------------------+-------------------^-+
|
Наши тесты -------+
9. Ошибку в коде мы так и неОшибку в коде мы так и не
нашлинашли
def test_qsort():
> assert qsort([1, 1, 1]) == [1, 1, 1]
E assert [1] == [1, 1, 1]
E Right contains more items, first extra item: 1
E Full diff:
E - [1]
E + [1, 1, 1]
12. QuickCheckQuickCheck
Изначально написан на Haskell
Основывается на научно-исследовательских
работах
Позиционируется как библиотека для
автоматического тестирование функций на
основе спецификаций и свойств данных
Портирован на Scala, Erlang, Clojure, JavaScript...
Продвигается компанией Quviq
http://www.cse.chalmers.se/~rjmh/QuickCheck/
13. Как оно работает (примерно)Как оно работает (примерно)
passed
+-----------------------+
v |
+-----------+ sample +------+ failed +----------+ +--------+
| Generator | --------> | Test | --------> | Shrinker | --> | Report |
+-----------+ +------+ +----------+ +--------+
| ^ sample | ^
| +------------------+ |
| |
+-----------------------------------------------------------+
success
15. Нельзя просто взять и написатьНельзя просто взять и написать
правильный QuickCheckправильный QuickCheck
https://github.com/agrif/pyquickcheck
https://github.com/Cue/qc
https://github.com/dbravender/qc
https://github.com/futoase/PyQCheck
https://github.com/JesseBuesking/pythoncheck
https://github.com/markchadwick/paycheck
https://github.com/msoedov/quick.py
https://github.com/npryce/python-factcheck
https://github.com/Xion/pyqcy
https://github.com/zombiecalypse/qcc
https://pypi.python.org/pypi/pytest-quickcheck
...
16. Типичные ошибкиТипичные ошибки
Слепое копирование Haskell реализации
Использование типов в качестве генераторов
Отсутствующий или же "глупый" shrinking
Заброшенные или же в зачаточном состоянии
18. Что умеетЧто умеет
Генерация всех основных типов данных
Рекурсивные типы
State Machine, N-ary деревья
Позволяет создавать свои стратегии без
погружения в детали
Интегрирован с Fake Factory, Django, pytest
Следит за качеством тестов
Запоминает найденные баги
Хорошо настраивается
http://hypothesis.readthedocs.org/en/master/data.htmlhttp://hypothesis.readthedocs.org/en/master/data.html
28. Health CheckHealth Check
Генерируемые данные слишком большие
Слишком строгая фильтрация
Слишком медленная стратегия
Использование random вызовов в коде
Тест возвращает результат
29. Гибкая настройкаГибкая настройка
Количество найденных успехных примеров
(example)
Количество итераций на тест
Глубина поиска минимального примера
Слишком медленная стратегия
Время выполнения теста
QA контроль
Профили
30. ПрофилиПрофили
import os
from hypothesis import settings
from hypothesis import Verbosity
settings.register_profile("ci", settings(max_examples=1000))
settings.register_profile("dev", settings(max_examples=10))
settings.register_profile("debug", settings(max_examples=10,
verbosity=Verbosity.verbose))
settings.load_profile(os.getenv('HYPOTHESIS_PROFILE', 'default'))
31. Что хорошо тестируетсяЧто хорошо тестируется
Алгоритмы
Структуры данных любой сложности
Реализации протоколов, парсеров, базы данных
Любые чистые функции
Детерминированные API
32.
33.
34.
35. Полезные ссылкиПолезные ссылки
Hypothesis
https://github.com/DRMacIver/hypothesis
Hypothesis Talks
https://github.com/DRMacIver/hypothesis-talks
Conjecture
https://github.com/DRMacIver/conjecture
PYCON UK 2015: Finding more bugs with less work
https://www.youtube.com/watch?v=62ubHXzD8tM
How I handled Erlang R18 Maps with QuickCheck
https://vimeo.com/143849945
СurEr - Concolic Testing
https://www.youtube.com/watch?v=XVOV0KQAf-8