Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Практический Hypothesis

Практический Hypothesis

Александр Шорин (Rambler&Co) @ Moscow Python Meetup 50

"На MoscowPython 33 я уже рассказывал в общих чертах про состояние property-тестирования в Python и коротко про Hypothesis. Предлагаю продолжить, но уже по существу. В этом докладе я на примерах покажу, как эффективно применять Hypothesis, находить неочевидные баги и повышать качество ваших тестов".

Видео: http://www.moscowpython.ru/meetup/50/practical-hypothesis/

Moscow Python Meetup

November 23, 2017
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. Немного про команду ML • Делаем вещи с 2012г. •

    Computer Vision для кинотеатров • Оптимизируем CTR/CPA в DSP • Персональные рекомендации (Ecco, Рамблер/старт) • Работаем с передовыми технологиями на острие прогресса • У нас Python 3.6, aiohttp, Hadoop, Hive, Spark, Clickhouse, Airflow, PyTorch, Tensorflow, Kafka, PostgreSQL, ... • Интересно? Ждем ваше резюме на https://rambler-co.ru/jobs
  2. Hypothesis • Библиотека для Property-тестирования • Генерирует данные за вас

    • Определяет пограничные случаи за вас • Находит наименьший набор данных ломающий тест • Заботится о скорости и качестве ваших тестов • Гибок, расширяем, удобен • Интеграция с pytest из коробки!
  3. Property Based Testing • Это как QuickCheck • Вместо проверок

    “ожидание-результат” проверяете свойства результата • Fuzzing? Да, похоже!
  4. Тестируем ожидания @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
  5. Вроде всё работает plugins: sugar-0.9.0, hypothesis-3.38.4 test_qsort.py::test_qsort[value0-result0] ✓ 33% ███▍

    test_qsort.py::test_qsort[value1-result1] ✓ 67% ██████▋ test_qsort.py::test_qsort[value2-result2] ✓ 100% ██████████
  6. Проблемы • Мы проверили поведение для конкретных случаев. • Сколько

    еще нужно данных, чтобы убедиться в корректности работы функции? • А оно точно работает?
  7. Тестируем свойства @given(st.lists(st.integers())) def test_qsort(input_list): sorted_list = qsort(input_list) assert len(sorted_list)

    == len(input_list) for item in sorted_list: assert item in input_list input_list.remove(item) assert len(input_list) == 0 assert all(sorted_list[i - 1] <= sorted_list[i] for i in range(1, len(sorted_list)))
  8. И находим ошибку @given(st.lists(st.integers())) def test_qsort(input_list): sorted_list = qsort(input_list) >

    assert len(sorted_list) == len(input_list) E assert 1 == 2 E + where 1 = len([0]) E + and 2 = len([0, 0])
  9. Стратегии в Hypothesis >>> from hypothesis.strategies import * >>> just('Hello,

    Moscow Python!').example() 'Hello, Moscow Python!' >>> none().example() is None True >>> booleans().example() False
  10. Стратегии в Hypothesis >>> integers().wrapped_strategy WideRangeIntStrategy() >>> integers(min_value=42, max_value=100).wrapped_strategy BoundedIntStrategy(42,

    100) >>> integers(min_value=-5, max_value=100).wrapped_strategy integers(min_value=0, max_value=100) | integers(min_value=-5, max_value=0)
  11. Стратегии в Hypothesis >>> from_regex('^(?:not )?to beer$').example() 'to beer\n' >>>

    from_regex('^\d{2} bottles of beer on the (?:wall|table)').example() '๙۲ bottles of beer on the table\U00072e0b\x00&$'
  12. Стратегии в Hypothesis >>> arrays(dtype=np.float16, shape=3, elements=floats(0, 1)).example() array([ 0.45361328,

    0.45361328, 0.97607422], dtype=float16) >>> cols = columns(['a', 'b'], dtype=np.float16) >>> rows = tuples(floats(0, 1), floats(0, 1)) >>> data_frames(cols, rows).example() a b 0 0.446045 0.578613 1 0.600098 0.611328
  13. Стратегии в Hypothesis: PEP-484 @given(a=st.infer, b=st.infer) def test_pep484(a: int, b:

    float): assume(b != 0) assume(math.isfinite(b)) … >>> from_type(List[Union[Optional[int], str]]).example() ['\x16\U000cf6b6\x01', 87150340692974773999671278779717418097, -122085028893051399750292566476660900154, None, None, None, None]
  14. Стратегии в Hypothesis … их много! • tuples • lists

    • dicts • fixed_dictionaries • sets • frozensents • iterables • one_of • sampled_from • builds • permutations • from_type • shared • choices • binary • datetimes • times • fractions • decimals • namedtuples • uuids
  15. Расширения • hypothesis.extras • pytz / datetimes • django •

    numpy / pandas • https://github.com/lazka/hypothesis-fspaths • https://github.com/CurataEng/hypothesis-protobuf • https://github.com/maximkulkin/lollipop-hypothesis • https://github.com/stratis-storage/hs-dbus-signature
  16. Пользовательские стратегии @st.composite def path_absolute(draw, segment_strategy=None): """Generates strictly absolute paths

    per :rfc:`3986#3.3`:: path-absolute = "/" [ segment-nz *( "/" segment ) ] """ acc = ['/'] num_segments = draw(st.integers(min_value=1, max_value=255)) acc.append(draw(segment_nz(segment_strategy))) acc.extend(draw(st.lists( segment(segment_strategy).map(partial(operator.add, '/')), max_size=num_segments - 1 or 0, ))) return ''.join(acc)
  17. Будьте декларативны def url(): return st.builds(build_url, scheme(), authority(), path(), query(),

    fragment()) def authority(): return st.builds(build_authority, userinfo(), host(), port()) def userinfo(): return st.builds(build_userinfo, username(), password()) def username():
  18. Health Checks • data_too_large: ограничивайте размер генерируемых данных; • filter_too_much:

    слишком строгая фильтрация, пересмотрите подход к генерации данных; • too_slow: слишком медленная генерация, скорее всего из-за больших данных; • hung_test: тест работает больше 5 минут ;
  19. Health Checks - test run deadline def test_fun(sc, rows): df

    = sc.parallelize(rows).toDF() ... test.py::test_fun hypothesis_temporary_module_a95cb91fb51532e1e8b5aff17575d554e4d57c39:24: HypothesisDeprecationWarning: Test took 4335.64ms to run. In future the default deadline setting will be 200ms, which will make this an error. You can set deadline to an explicit value of e.g. 4400 to turn tests slower than this into an error, or you can set it to None to disable this check entirely.
  20. Профили настроек 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'))
  21. Область применения • Везде где есть данные: • Алгоритмы •

    Протоколы • Базы данных • Структуры данных • ETL pipeline