Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Глубокое погружение в стратегии
strategies_deep

Глубокое погружение в стратегии

Все встроенные стратегии, композиция, фильтрация, mapped strategies

Глубокое погружение в стратегии

«Стратегии — это язык, на котором вы описываете пространство входных данных для ваших тестов.»

#Что такое стратегия?

Стратегия — это объект Hypothesis, который описывает, как генерировать значения определённого типа. Стратегии являются ленивыми (не генерируют данные до запроса) и композируемыми (можно комбинировать для создания сложных структур).

from hypothesis import strategies as st # Стратегия не генерирует данные до вызова example() int_strategy = st.integers() value = int_strategy.example() # Например: 42

#Все встроенные стратегии

#1. Числовые типы

#Целые числа

st.integers() # Генерирует: 0, 1, -1, 2147483647, -2147483648, 100, ... st.integers(min_value=0, max_value=100) # Генерирует: 0, 1, 50, 99, 100 st.integers(min_value=-10, max_value=-1) # Генерирует: -10, -5, -1

Особенности:

  • Автоматически включает границы диапазона
  • Тестирует 0, 1, -1 даже внутри диапазона
  • Использует variable-length encoding для shrinking

#Числа с плавающей точкой

st.floats() # Генерирует: 0.0, -0.0, inf, -inf, nan, 1e-308, 1e308, ... st.floats(allow_nan=False, allow_infinity=False) # Исключает специальные значения st.floats(min_value=0.0, max_value=1.0) # Генерирует: 0.0, 0.5, 1.0, 0.001, 0.999 st.floats(width=32) # 32-битные float st.floats(width=64) # 64-битные double (по умолчанию)

Важно: allow_nan=False может замедлить генерацию, так как Hypothesis будет отбрасывать NaN значения.

#Decimal

st.decimals() # Генерирует: Decimal('0'), Decimal('1.5'), Decimal('NaN'), ... st.decimals(allow_nan=False, allow_infinity=False) st.decimals(min_value=Decimal('-100'), max_value=Decimal('100')) st.decimals(places=2) # Фиксированное количество знаков после точки

#2. Строки и байты

st.text() # Генерирует: "", "0", "A", "\x00", "\U0001F600", "A" * 100, ... st.text(min_size=1, max_size=10) # Непустые строки длиной 1-10 символов st.text(alphabet=st.characters(whitelist_categories=('Lu', 'Ll'))) # Только буквы (uppercase и lowercase) st.binary() # Генерирует: b"", b"\x00", b"\xff", b"abc", ... st.binary(min_size=1, max_size=100) st.characters() # Генерирует: специальные символы, ASCII, Unicode st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')) # Только буквы и цифры

Специальные символы, которые автоматически генерирует st.text():

  • Пустая строка: ""
  • Нуль-символ: "\x00"
  • Перевод строки: "\n", "\r\n"
  • Табуляция: "\t"
  • Unicode: "\U0001F600" (эмодзи), "\u0000" (null)
  • Комбинации: "\x00\n", "A\x00B"

#3. Булевы и None

st.booleans() # Генерирует: True, False st.none() # Генерирует: None st.just(42) # Всегда генерирует: 42

#4. Коллекции

#Списки

st.lists(st.integers()) # Генерирует: [], [0], [1, 2, 3], [0, 0, 0], ... st.lists(st.integers(), min_size=1, max_size=10) # Непустые списки длиной 1-10 st.lists(st.integers(), unique=True) # Уникальные элементы st.lists(st.integers(), unique_by=lambda x: x % 2) # Уникальность по ключу (чётность)

#Множества

st.sets(st.integers()) # Генерирует: set(), {0}, {1, 2, 3}, ... st.frozensets(st.text()) # Генерирует: frozenset(), {""}, {"a", "b"}, ...

#Словари

st.dictionaries(st.text(), st.integers()) # Генерирует: {}, {"": 0}, {"a": 1, "b": 2}, ... st.dictionaries( st.text(min_size=1, max_size=5), st.integers(min_value=0, max_value=100), min_size=1, max_size=10 )

#Кортежи

st.tuples(st.integers(), st.text(), st.booleans()) # Генерирует: (0, "", True), (42, "abc", False), ... st.tuples(st.integers(), st.integers()) # Фиксированная длина: (0, 0), (1, -1), ...

#5. Опциональные и объединение

st.one_of(st.integers(), st.text()) # Генерирует: 0, "", 42, "abc", ... st.one_of(st.integers(), st.text(), st.lists(st.integers())) # Три типа данных st.integers() | st.text() # Альтернативный синтаксис # Эквивалентно st.one_of(st.integers(), st.text()) st.optional(st.integers(), default=None) # Генерирует: None, 0, 42, ...

#6. Преобразование стратегий

#map

st.integers().map(str) # Генерирует: "0", "42", "-1", ... st.integers().map(lambda x: x * 2) # Генерирует: 0, 2, -2, 84, ... st.text().map(len) # Генерирует: 0, 1, 5, 100, ...

Важно: map применяется к каждому сгенерированному значению. Shrinking работает на уровне исходной стратегии.

#filter

st.integers().filter(lambda x: x > 0) # Генерирует только положительные: 1, 2, 50, 100, ... st.text().filter(lambda s: len(s) >= 3) # Строки длиной минимум 3 символа st.integers().filter(lambda x: x % 2 == 0) # Только чётные числа

Предупреждение: Если фильтр слишком строгий, Hypothesis не сможет генерировать достаточно примеров. Используйте assume() внутри теста для сложной фильтрации.

#flatmap

# flatmap позволяет использовать сгенерированное значение для создания новой стратегии st.integers(min_value=1, max_value=10).flatmap( lambda n: st.lists(st.integers(), min_size=n, max_size=n) ) # Генерирует списки, где длина равна первому сгенерированному числу

#7. Рекурсивные стратегии

# Деревья: лист — целое число, узел — список детей st.recursive( st.integers(), # Базовый случай (листья) lambda children: st.lists(children, max_size=3), # Рекурсивный случай max_leaves=10 ) # Генерирует: 0, [0, 1], [[0], 1, 2], ...

#8. Функции и вызовы

st.functions() # Генерирует mock-функции st.functions(like=lambda x: x, returns=st.integers()) # Функция с определённой сигнатурой

#Композиция стратегий

#Пример 1: Генерация email

email_strategy = ( st.builds( "{}@{}.{}".format, st.text(alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')), min_size=1, max_size=10), st.text(alphabet=st.characters(whitelist_categories=('Lu', 'Ll', 'Nd')), min_size=2, max_size=10), st.sampled_from(['com', 'org', 'net', 'ru', 'io']) ) )

#Пример 2: Генерация даты рождения

from datetime import date birth_date_strategy = st.builds( date, year=st.integers(min_value=1950, max_value=2010), month=st.integers(min_value=1, max_value=12), day=st.integers(min_value=1, max_value=31) )

Проблема: эта стратегия может генерировать невалидные даты (31 февраля). Решение:

from datetime import date import calendar def is_valid_date(year, month, day): try: date(year, month, day) return True except ValueError: return False birth_date_strategy = st.builds( date, year=st.integers(min_value=1950, max_value=2010), month=st.integers(min_value=1, max_value=12), day=st.integers(min_value=1, max_value=31) ).filter(lambda d: is_valid_date(d.year, d.month, d.day))

Или лучше через @composite (см. следующую тему).

#Пример 3: Генерация JSON-подобных структур

json_strategy = st.recursive( st.one_of( st.none(), st.booleans(), st.integers(), st.floats(allow_nan=False, allow_infinity=False), st.text() ), lambda children: st.one_of( st.lists(children, max_size=5), st.dictionaries(st.text(), children, max_size=5) ), max_leaves=20 )

#Фильтрация: assume() vs filter()

#filter() на уровне стратегии

# Фильтрация при генерации positive_integers = st.integers().filter(lambda x: x > 0) @given(positive_integers) def test_positive(x): assert x > 0

Плюсы:

  • Применяется ко всем использованиям стратегии
  • Переиспользуется в разных тестах

Минусы:

  • Может замедлить генерацию, если фильтр строгий
  • Hypothesis может не найти достаточно примеров

#assume() внутри теста

@given(st.integers(), st.integers()) def test_division(x, y): assume(y != 0) # Исключаем деление на ноль assume(x > 0) # Только положительные x result = x / y assert isinstance(result, float)

Плюсы:

  • Гибкая фильтрация на основе контекста
  • Можно использовать несколько assume()
  • Лучше для отладки (видно, какие данные отбрасываются)

Минусы:

  • Не переиспользуется между тестами
  • Может замедлить тест при частом отбрасывании

#Когда что использовать

СитуацияРешение
Простой фильтр (x > 0)filter()
Сложный фильтр с несколькими условиямиassume()
Фильтр используется в нескольких тестахfilter()
Фильтр зависит от других параметровassume()
Риск слишком строгого фильтраassume() с предупреждением

#Mapped стратегии и shrinking

Важное свойство map: shrinking работает на уровне исходной стратегии, а не преобразованной.

@given(st.integers().map(lambda x: x * 100)) def test_mapped(x): # x всегда кратно 100 assert x % 100 == 0

Если тест падает на x = 10000, shrinker попытается уменьшить исходное число:

  • Исходное: 100 → после map: 10000
  • Shrinking: 100 → 1 → после map: 100

Вы увидите failing case: x = 100 (минимальное кратное 100).

#Примеры из production

#1. Генерация тестовых данных для интернет-магазина

from decimal import Decimal from datetime import datetime, timedelta def order_strategy(): return st.builds( dict, order_id=st.uuids(), customer_id=st.integers(min_value=1, max_value=1000000), items=st.lists( st.builds( dict, product_id=st.integers(min_value=1, max_value=10000), quantity=st.integers(min_value=1, max_value=100), price=st.decimals(min_value=0.01, max_value=10000, places=2) ), min_size=1, max_size=10 ), created_at=st.datetimes( min_value=datetime(2020, 1, 1), max_value=datetime.now() ), discount=st.decimals(min_value=0, max_value=0.5, places=2) ) @given(order_strategy()) def test_order_total_positive(order): total = sum(item['quantity'] * item['price'] for item in order['items']) total_with_discount = total * (1 - order['discount']) assert total_with_discount >= 0

#2. Генерация URL для API тестирования

from urllib.parse import urljoin def api_url_strategy(base_path: str = "/api/v1"): paths = st.sampled_from([ "/users", "/products", "/orders", "/cart", "/checkout", "/search" ]) ids = st.integers(min_value=1, max_value=1000000).map(str) query_params = st.dictionaries( st.sampled_from(["page", "limit", "sort", "filter", "q"]), st.one_of(st.integers(), st.text()), max_size=3 ) return st.builds( lambda path, id_, params: f"{base_path}{path}/{id_}?{params}", paths, ids, query_params.map(lambda p: "&".join(f"{k}={v}" for k, v in p.items())) )

#Заключение

Стратегии — это фундамент Hypothesis. Понимание всех встроенных стратегий и умение их комбинировать позволяет описывать сложные пространства входных данных.

Ключевые выводы:

  1. Используйте конкретные стратегии для каждого типа данных
  2. Композиция стратегий через builds, one_of, tuples
  3. filter() для простых условий, assume() для сложных
  4. map применяется после генерации, shrinking работает на исходной стратегии
  5. recursive для деревьев и вложенных структур

В следующей теме вы научитесь создавать собственные стратегии с помощью @composite.


Следующая тема: Создание собственных стратегий — @composite, draw(), рекурсивные стратегии для доменных типов.

Далее: Создание собственных стратегий