«QuickCheck в Python: проверка гипотез и поиск ошибок»,...

36
QuickCheck в Python QuickCheck в Python Проверка гипотез и поиск ошибок Проверка гипотез и поиск ошибок Шорин Александр / @kxepal

Transcript of «QuickCheck в Python: проверка гипотез и поиск ошибок»,...

Page 1: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

QuickCheck в PythonQuickCheck в PythonПроверка гипотез и поиск ошибокПроверка гипотез и поиск ошибок

Шорин Александр / @kxepal

Page 2: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

О чем пойдет речь:О чем пойдет речь:Что не так с нашими тестамиЧто такое QuickCheck......и как его неправильно портироватьЗнакомство с HypothesisВремя офигенных историй

Page 3: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Что не так с нашими тестами?Что не так с нашими тестами?

Page 4: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Простейший примерПростейший пример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

(да, в жизни все сложнее)(да, в жизни все сложнее)

Page 5: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

ТестируемТестируемdef test_empty():

assert qsort([]) == []

def test_sort():assert qsort([3, 2, 1]) == [1, 2, 3]

def test_sorted():assert qsort([1, 2, 3]) == [1, 2, 3]

Шаблонная копипастаШаблонная копипаста

Page 6: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Мы можем решить проблему копипасты...Мы можем решить проблему копипасты...

@pytest.mark.parametrize(('value', 'result'), [ ([], []), ([1, 2, 3], [1, 2, 3]), ([3, 2, 1], [1, 2, 3]),])def test_qsort(value, result):

assert qsort(value) == result

Page 7: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

...хотя может показаться, что нет...хотя может показаться, что нет

@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

Page 8: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Можем ли мы проверить все допустимые случаи?Можем ли мы проверить все допустимые случаи?+----------------------------------------------------------------------+| ВЕЛИКОЕ ВСЁ || || || || || || || || || || +---------------------+| | Приемлемые данные || | Х |+------------------------------------------------+-------------------^-+ | Наши тесты -------+

Page 9: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Ошибку в коде мы так и неОшибку в коде мы так и ненашлинашли

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: 1E Full diff:E - [1]E + [1, 1, 1]

Page 10: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Bug Driven DevelopmentBug Driven DevelopmentBug. Test. Fix. Repeat.Bug. Test. Fix. Repeat.

Page 11: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

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

Page 12: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

QuickCheckQuickCheckИзначально написан на HaskellОсновывается на научно-исследовательскихработахПозиционируется как библиотека дляавтоматического тестирование функций наоснове спецификаций и свойств данныхПортирован на Scala, Erlang, Clojure, JavaScript...Продвигается компанией Quviqhttp://www.cse.chalmers.se/~rjmh/QuickCheck/

Page 13: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Как оно работает (примерно)Как оно работает (примерно) passed +-----------------------+ v |+-----------+ sample +------+ failed +----------+ +--------+| Generator | --------> | Test | --------> | Shrinker | --> | Report |+-----------+ +------+ +----------+ +--------+ | ^ sample | ^ | +------------------+ | | | +-----------------------------------------------------------+ success

Page 14: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Выглядит просто?Выглядит просто?

Page 15: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Нельзя просто взять и написатьНельзя просто взять и написатьправильный QuickCheckправильный QuickCheck

https://github.com/agrif/pyquickcheckhttps://github.com/Cue/qchttps://github.com/dbravender/qchttps://github.com/futoase/PyQCheckhttps://github.com/JesseBuesking/pythoncheckhttps://github.com/markchadwick/paycheckhttps://github.com/msoedov/quick.pyhttps://github.com/npryce/python-factcheckhttps://github.com/Xion/pyqcyhttps://github.com/zombiecalypse/qcchttps://pypi.python.org/pypi/pytest-quickcheck...

Page 16: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Типичные ошибкиТипичные ошибкиСлепое копирование Haskell реализацииИспользование типов в качестве генераторовОтсутствующий или же "глупый" shrinkingЗаброшенные или же в зачаточном состоянии

Page 17: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Hypothesis [haɪˈpɒθɪsɪs]Hypothesis [haɪˈpɒθɪsɪs]Автор: David R. MacIver

https://github.com/DRMacIver/hypothesis

Page 18: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Что умеетЧто умеетГенерация всех основных типов данныхРекурсивные типыState Machine, N-ary деревьяПозволяет создавать свои стратегии безпогружения в деталиИнтегрирован с Fake Factory, Django, pytestСледит за качеством тестовЗапоминает найденные багиХорошо настраивается

http://hypothesis.readthedocs.org/en/master/data.htmlhttp://hypothesis.readthedocs.org/en/master/data.html

Page 19: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Генерация данныхГенерация данныхfrom hypothesis import strategies as st

def nulls(): return st.none()def booleans(): return st.booleans()def numbers(): return st.integers() | st.floats()def strings(): return st.text()def arrays(elements): return st.lists(elements)def objects(elements): return st.dictionaries(strings(), elements)def values(): simple_values = nulls() | booleans() | numbers() | strings()

return (simple_values | st.recursive(simple_values,

lambda children: arrays(children) | objects(children)))

Page 20: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Генерация данныхГенерация данных>>> doc = json_st.objects(json_st.values())>>> doc.example(){'G G\u202fn G𝞠\u202f_n( n(nn_ n': 9.943339378805967e-309}>>> doc.example(){'': None, '\x85': '', '\U00014481': None,'\u3000': -2.45410883359415e-309, ' \x85': 1.5564453946197205e-308,'I': ' ','\u3000\u2029': -9.05230966734913e-309, '\U00014481\U00014481 ⁱ': None,'Nj': -1.80149788818e-311, ' ': -1.414261190585428e+202,'\u2029ⁱNj': ' ', ' \u2029': inf, ' ': ' ',

'\u3000 ': -1.0065151140507456e+206, 'Nj ': None, ' ⁱ': None,'I\U00014481': -1.2296585975031088e+145, '\x80': ' ','\x85ⁱ \x80\x80Nj': -6.438869672267061e+116, ' ': None,'\u3000\x80': None, '\u2029\x80Nj': -698356955173.6532, ' ': ' ',' \x85': None, '\x85ⁱ\U00014481': None, ' ': None, 'ⁱ': None,' \u3000 ': ' '}

Page 21: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Поиск ошибокПоиск ошибок>>> @given(json_st.objects(json_st.values()))... def test_json(doc):... assert json.loads(json.dumps(doc)) == doc>>> test_json()Falsifying example: test_json(doc={'': nan})Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in test_json File "./hypothesis/core.py", line 583, in wrapped_test print_example=True, is_final=True File "./hypothesis/executors/executors.py", line 25, in default_executor

return function() File "./hypothesis/core.py", line 365, in run

return test(*args, **kwargs) File "<stdin>", line 3, in test_jsonAssertionError

Page 22: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Поиск ошибокПоиск ошибок>>> @given(json_st.objects(json_st.values()))... @settings(verbosity=hypothesis.Verbosity.verbose)... def test_json(doc):... assert json.loads(json.dumps(doc)) == doc>>> test_json()

Page 23: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Поиск ошибокПоиск ошибокTrying example: test_json(doc={})Trying example: test_json(doc={'': True})Trying example: test_json(doc={'': None})Trying example: test_json(doc={'': False})Trying example: test_json(doc={'': -43203256341979896423482879160843})Trying example: test_json(doc={'': 24}) ...Trying example: test_json(doc={'': 100440})Trying example: test_json(doc={'': 30323947834323202215971170911015})Trying example: test_json(doc={'': 0.0})Trying example: test_json(doc={'': inf})Trying example: test_json(doc={'': -inf})Successfully shrunk example 27 timesFalsifying example: test_json(doc={'': nan})

Page 24: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Исправление ошибокИсправление ошибокimport mathfrom hypothesis import strategies as st

def numbers():return st.integers() | st.floats().filter(math.isfinite)

Page 25: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Property-тестированиеProperty-тестирование@hypothesis.given(st.lists(st.integers()))def test_qsort(l): ls = qsort(l)

for i in ls:assert i in l

l.remove(i)assert len(l) == 0

assert all(ls[i - 1] <= ls[i] for i in range(1, len(ls)))

Page 26: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Property-тестированиеProperty-тестированиеl = [0]

@hypothesis.given(st.lists(st.integers()))def test_qsort(l):

ls = qsort(l)

for i in ls:assert i in l

l.remove(i)> assert len(l) == 0E assert 1 == 0E + where 1 = len([0])

foo.py:18: AssertionError---------------- Hypothesis ----------------Falsifying example: test_qsort(l=[0, 0])

Page 27: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

База примеровБаза примеровHypothesis сохраняет найденные ошибки в SQLite

базу для последующего воспроизведения.

Page 28: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Health CheckHealth CheckГенерируемые данные слишком большиеСлишком строгая фильтрацияСлишком медленная стратегияИспользование random вызовов в кодеТест возвращает результат

Page 29: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Гибкая настройкаГибкая настройкаКоличество найденных успехных примеров(example)Количество итераций на тестГлубина поиска минимального примераСлишком медленная стратегияВремя выполнения тестаQA контрольПрофили

Page 30: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

ПрофилиПрофилиimport osfrom hypothesis import settingsfrom 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'))

Page 31: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Что хорошо тестируетсяЧто хорошо тестируетсяАлгоритмыСтруктуры данных любой сложностиРеализации протоколов, парсеров, базы данныхЛюбые чистые функцииДетерминированные API

Page 32: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co
Page 33: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co
Page 34: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co
Page 35: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Полезные ссылкиПолезные ссылкиHypothesishttps://github.com/DRMacIver/hypothesisHypothesis Talkshttps://github.com/DRMacIver/hypothesis-talksConjecturehttps://github.com/DRMacIver/conjecturePYCON UK 2015: Finding more bugs with less workhttps://www.youtube.com/watch?v=62ubHXzD8tMHow I handled Erlang R18 Maps with QuickCheckhttps://vimeo.com/143849945СurEr - Concolic Testinghttps://www.youtube.com/watch?v=XVOV0KQAf-8

Page 36: «QuickCheck в Python: проверка гипотез и поиск ошибок», Александр Шорин, Rambler&Co

Спасибо за внимание!Спасибо за внимание!