Обычный подход к тестированию выглядит так:
придумать странный input → написать тест → проверить поведение
Так можно покрыть десятки сценариев и всё равно пропустить баг.
Именно это часто происходит с edge cases.
Например, для URL-нормализатора можно написать десятки тестов с «необычными» URL, но упустить случай, когда вход состоит только из пробелов. Функция внезапно вернёт пустую строку — а тестов на это не окажется.
Здесь и помогает Hypothesis — библиотека для property-based testing.
Вместо того чтобы перечислять конкретные примеры, описывается свойство, которое должно быть истинным для любого входа.
Обычный тест:
def test_round_trip_specific():
assert decode(encode("hello world")) == "hello world"
Property-based тест:
from hypothesis import given
from hypothesis import strategies as st
@given(st.text())
def test_round_trip_any_string(s):
assert decode(encode(s)) == s
Теперь библиотека сама генерирует сотни и тысячи вариантов:
- пустые строки
- unicode-символы
- пробелы
- null bytes
- очень длинные значения
- нестандартные boundary cases
И цель у неё одна — сломать функцию.
Если найден баг, Hypothesis автоматически уменьшает вход до минимального воспроизводимого примера.
Получается что-то вроде:
Falsifying example:
url=''
Три свойства, которые полезны почти в любом проекте
1. Round-trip
@given(st.text())
def test_encode_decode(s):
assert decode(encode(s)) == s
2. Idempotency
Повторное применение функции не должно менять результат.
@given(st.text())
def test_normalize_idempotent(url):
assert normalize(url) == normalize(normalize(url))
3. Не должно падать
Очень недооценённый сценарий:
@given(st.text())
def test_parser_does_not_crash(s):
try:
parse(s)
except ValueError:
pass
Такие тесты часто находят баги там, где никто их не ожидал.
📍 Навигация: Вакансии • Задачи • Собесы
Библиотека питониста
#буст