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
PRO

November 23, 2017
Tweet

More Decks by Moscow Python Meetup

Other Decks in Programming

Transcript

  1. Александр Шорин
    [email protected]
    Практический
    Hypothesis
    Moscow Python Meetup #50

    View Slide

  2. Немного про команду 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

    View Slide

  3. https://t.me/moscowspark

    View Slide

  4. Hypothesis
    • Библиотека для Property-тестирования
    • Генерирует данные за вас
    • Определяет пограничные случаи за вас
    • Находит наименьший набор данных ломающий тест
    • Заботится о скорости и качестве ваших тестов
    • Гибок, расширяем, удобен
    • Интеграция с pytest из коробки!

    View Slide

  5. Property Based Testing
    • Это как QuickCheck
    • Вместо проверок “ожидание-результат” проверяете
    свойства результата
    • Fuzzing? Да, похоже!

    View Slide

  6. Тестируем ожидания
    @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

    View Slide

  7. Вроде всё работает
    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%
    ██████████

    View Slide

  8. Проблемы
    • Мы проверили поведение для конкретных случаев.
    • Сколько еще нужно данных, чтобы убедиться в
    корректности работы функции?
    • А оно точно работает?

    View Slide

  9. Тестируем свойства
    @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)))

    View Slide

  10. И находим ошибку
    @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])

    View Slide

  11. Стратегии в Hypothesis
    >>> from hypothesis.strategies import *
    >>> just('Hello, Moscow Python!').example()
    'Hello, Moscow Python!'
    >>> none().example() is None
    True
    >>> booleans().example()
    False

    View Slide

  12. Стратегии в Hypothesis
    >>> integers().example()
    -150670313531850699691400483021473874929
    >>> integers(min_value=-1, max_value=100).example()
    -1
    >>> integers(min_value=0).filter(lambda i: i % 2 ==
    0).example()
    106140

    View Slide

  13. Стратегии в 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)

    View Slide

  14. Стратегии в Hypothesis
    >>> floats(min_value=-1, max_value=1).example()
    0.6611503674557068
    >>> floats().example()
    -inf

    View Slide

  15. Стратегии в Hypothesis
    >>> text().example()
    '\U000cea1b\x1b\x0c(0\U000a90eb\x08\x1d'
    >>> text(string.hexdigits).map(str.upper).example()
    'A5BBF104DAD1FC9EFCAF1'
    >>> text(characters(whitelist_categories={'Lu',
    'Ll'})).example()
    'ϥ ϹyⰢûʖꝖ'

    View Slide

  16. Стратегии в 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&$'

    View Slide

  17. Стратегии в 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

    View Slide

  18. Стратегии в 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]

    View Slide

  19. Стратегии в 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

    View Slide

  20. Расширения
    • 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

    View Slide

  21. Пользовательские стратегии
    @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)

    View Slide

  22. Будьте декларативны
    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():

    View Slide

  23. Health Checks
    • data_too_large: ограничивайте размер генерируемых данных;
    • filter_too_much: слишком строгая фильтрация, пересмотрите подход к
    генерации данных;
    • too_slow: слишком медленная генерация, скорее всего из-за больших
    данных;
    • hung_test: тест работает больше 5 минут ;

    View Slide

  24. 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.

    View Slide

  25. Профили настроек
    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'))

    View Slide

  26. Область применения
    • Везде где есть данные:
    • Алгоритмы
    • Протоколы
    • Базы данных
    • Структуры данных
    • ETL pipeline

    View Slide

  27. Ссылки
    Официальный сайт:
    http://hypothesis.works/
    Репозиторий:
    https://github.com/HypothesisWorks/hypothesis-python
    Список рассылки:
    https://groups.google.com/forum/#!forum/hypothesis-users
    IRC: irc://freenode.com/hypothesis

    View Slide

  28. Спрашивайте свои ответы!
    Спасибо за внимание!

    View Slide