惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
S
Securelist
U
Unit 42
The Cloudflare Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Simon Willison's Weblog
Simon Willison's Weblog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
B
Blog
T
Tenable Blog
The Hacker News
The Hacker News
The Register - Security
The Register - Security
IT之家
IT之家
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
P
Privacy & Cybersecurity Law Blog
博客园_首页
T
Tailwind CSS Blog
人人都是产品经理
人人都是产品经理
C
Cybersecurity and Infrastructure Security Agency CISA
Know Your Adversary
Know Your Adversary
NISL@THU
NISL@THU
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
Tor Project blog
C
CERT Recently Published Vulnerability Notes
Apple Machine Learning Research
Apple Machine Learning Research
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
T
The Exploit Database - CXSecurity.com
V
Vulnerabilities – Threatpost
A
Arctic Wolf
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V
V2EX
aimingoo的专栏
aimingoo的专栏
大猫的无限游戏
大猫的无限游戏
Scott Helme
Scott Helme
L
LINUX DO - 热门话题
Cyberwarzone
Cyberwarzone
V
Visual Studio Blog
月光博客
月光博客
爱范儿
爱范儿
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
美团技术团队
G
GRAHAM CLULEY
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
H
Heimdal Security Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Property-based тестирование: как находить баги, которые вы не придумали
Earthly Man · 2026-06-23 · via Все публикации подряд на Хабре

Средний

5 мин

0

Тесты на примерах проверяют те случаи, до которых вы додумались. Property-based тесты проверяют тысячи случаев, до которых вы НЕ додумались, — и при падении сами ужимают вход до минимального контрпримера. Разбираем, как это работает под капотом, какие свойства бывают, и показываем на коде (Python/Hypothesis), как PBT за минуту находит баг, который ручной тест-кейс не нашёл бы никогда.

Классический unit-тест — это «вход X → ожидаю Y». Вы придумали кейс, зафиксировали ожидание, поехали. Проблема в том, что баги обычно живут не в кейсах, которые вы придумали, а ровно в тех, до которых не дотянулась фантазия: пустая строка, эмодзи, дубликаты, отрицательный ноль, перевод строки внутри значения, целочисленное переполнение.

Property-based testing (PBT) переворачивает подход: вы описываете свойство — утверждение, которое должно быть истинно для любого корректного входа, — а фреймворк сам генерирует сотни случайных входов и пытается это свойство опровергнуть. Нашёл контрпример — ужимает его до минимального («shrinking») и показывает вам.

Пример на примерах vs свойство

Тест на примерах для функции сортировки:

def test_sort_example():
    assert my_sort([3, 1, 2]) == [1, 2, 3]

Один вход, одно ожидание. А вот свойства, которые верны для любого списка:

from hypothesis import given, strategies as st

@given(st.lists(st.integers()))
def test_sort_keeps_length(xs):
    assert len(my_sort(xs)) == len(xs)          # длина не меняется

@given(st.lists(st.integers()))
def test_sort_is_ordered(xs):
    r = my_sort(xs)
    assert all(r[i] <= r[i + 1] for i in range(len(r) - 1))  # реально отсортировано

@given(st.lists(st.integers()))
def test_sort_is_permutation(xs):
    assert sorted(my_sort(xs)) == sorted(xs)    # это перестановка исходного, ничего не потеряли/не добавили

@given(...) запускает каждый тест на сотне+ сгенерированных списков: пустых, из одного элемента, с дубликатами, с MIN_INT/MAX_INT, гигантских. Вам не нужно придумывать эти кейсы — генератор делает это за вас.

Как это работает под капотом

Три кирпича:

1. Генераторы (strategies) — описывают пространство входов: st.integers()st.text()st.lists(...)st.dictionaries(...), и комбинации.

2. Свойство — булево утверждение, которое должно держаться для всех входов из этого пространства.

3. Shrinking — когда свойство падает на каком-то монструозном случайном входе, фреймворк автоматически ищет минимальный вход, на котором оно всё ещё падает.

Именно shrinking превращает PBT из «рандомного фаззера» в инструмент отладки. Сравните два сообщения о падении:

# без shrinking — попробуй пойми, что тут сломалось:
Falsifying example: s = 'a8Q\x00\udce2\n\t   ZZ9📦\r\x7f...(2000 символов)'

# с shrinking — Hypothesis ужал до минимума:
Falsifying example: test_roundtrip(s='\n')

Во втором случае сразу видно: баг в обработке перевода строки. Это и есть киллер-фича.

Каталог свойств: что вообще утверждать

Самое сложное в PBT — не код, а придумать свойство. Хорошая новость: есть готовые паттерны, которые покрывают большинство случаев.

1. Round-trip (туда-обратно). Самый мощный и частый. decode(encode(x)) == x: сериализация/парсинг, кодеки, JSON, URL-энкодинг, сжатие.

@given(st.text())
def test_json_roundtrip(s):
    assert json.loads(json.dumps(s)) == s

2. Инвариант. Что-то, что всегда истинно про результат: длина, сумма, упорядоченность, «баланс не ушёл в минус», «id уникальны».

3. Идемпотентность. f(f(x)) == f(x): нормализация, slugifytrim, дедупликация, применение миграции дважды.

@given(st.text())
def test_slugify_idempotent(s):
    assert slugify(slugify(s)) == slugify(s)

4. Оракул / две реализации. Сравнить быструю реализацию с медленной-но-очевидной (или со старой версией при рефакторинге): результаты должны совпадать на любом входе.

5. Метаморфические отношения. Когда «правильный ответ» неизвестен, но известно, как он должен меняться: sort(reverse(xs)) == sort(xs); добавление товара в корзину увеличивает итог ровно на цену товара.

6. «Никогда не падает». Самое дешёвое свойство для старта: на любом валидном входе функция не кидает необработанное исключение. Часто ловит первые баги ещё до того, как вы сформулировали что-то умнее.

Реальный баг за минуту

Допустим, мы написали «наивный» CSV-сериализатор:

def to_csv_row(fields: list[str]) -> str:
    return ",".join(fields)

def from_csv_row(line: str) -> list[str]:
    return line.split(",")

Round-trip-свойство:

@given(st.lists(st.text()))
def test_csv_roundtrip(fields):
    assert from_csv_row(to_csv_row(fields)) == fields

Hypothesis почти мгновенно находит и ужимает контрпример:

Falsifying example: test_csv_roundtrip(fields=[','])

Поле, внутри которого есть запятая, ломает round-trip: [','] → "," → ['', '']. Классический баг CSV-экранирования, который на «нормальных» примерах (['a','b']) никогда бы не вылез. Вы его не придумывали — генератор придумал за вас. Это и есть ценность.

Stateful / model-based testing

Высшая лига PBT: тестировать не функцию, а последовательность операций над системой (очередь, кэш, БД, API). Вы описываете возможные действия и модель (упрощённый эталон), а фреймворк генерирует случайные сценарии и сверяет систему с моделью после каждого шага.

from hypothesis.stateful import RuleBasedStateMachine, rule, invariant

class CacheModel(RuleBasedStateMachine):
    def __init__(self):
        super().__init__()
        self.real = LruCache(capacity=3)
        self.model = {}

    @rule(k=st.integers(), v=st.integers())
    def put(self, k, v):
        self.real.put(k, v)
        self.model[k] = v

    @invariant()
    def size_within_capacity(self):
        assert len(self.real) <= 3

Так находят баги вытеснения, гонок состояний, рассинхрона — то, что обычным тестом ловится только случайно. Именно таким подходом Джон Хьюз с QuickCheck находил баги в распределённых БД и автомобильном софте — см. оригинальную статью QuickCheck (Claessen & Hughes, 2000), с которой всё началось.

Инструменты по языкам

  • Python — Hypothesis (де-факто стандарт, отличный shrinking, stateful из коробки).

  • JS/TS — fast-check.

  • JVM (Java/Kotlin) — jqwik, QuickTheories.

  • .NET — FsCheck.

  • Go — rapid, gopter.

  • Rust — proptest, quickcheck.

  • Haskell/Erlang — QuickCheck, PropEr (родина подхода).

Подводные камни — честно

  • Сформулировать свойство сложнее, чем кейс. Это главный барьер. Начинайте с «не падает» и round-trip, дальше войдёте во вкус.

  • Генератор должен покрывать реальное пространство. Если ограничить st.text() только ASCII — не найдёте багов с Unicode. И наоборот: слишком широкий генератор даёт «нереальные» падения.

  • Флак при скрытой недетерминированности. Свойство, зависящее от времени/глобального состояния/реального рандома, будет мигать. Фиксируйте время и seed.

  • Воспроизводимость. Hypothesis хранит базу упавших примеров и при следующем прогоне проверяет их первыми — коммитьте её в CI, иначе «поймал баг локально, в CI зелено».

  • Скорость. PBT-тест в сотни раз тяжелее одного юнита. Держите его в отдельном прогоне/наборе, а не в самом горячем pre-commit.

  • Это не замена примерам. Конкретный регресс-кейс на найденный баг по-прежнему полезен (быстрый, читаемый). PBT и примеры дополняют друг друга.

Как внедрить за один спринт

  • Возьмите одну «чистую» функцию с понятным контрактом: парсер, сериализатор, нормализатор, расчёт.

  • Напишите два свойства: «не падает» + round-trip (или инвариант).

  • Прогоните — почти наверняка что-то найдётся; ужатый контрпример превратите в обычный регресс-тест.

  • Закоммитьте базу примеров Hypothesis в репозиторий, добавьте PBT-набор в CI отдельным шагом.

  • Дальше добавляйте свойства на оракул (при рефакторинге — старая vs новая реализация) и, когда созреете, stateful-модель на ключевую структуру данных.

Вывод. Property-based тестирование закрывает слепое пятно тестов-на-примерах: оно systematically бьёт по входам, до которых вы не додумались, и при падении даёт минимальный, читаемый контрпример. Стоит начать с одной функции и пары свойств «не падает + round-trip» — и вы удивитесь, что найдёте уже в первый день.


Пишу про практику QA каждый день в Telegram-канале «QA — Quality Assurance»: t.me/qa10100011000001. Если зашло — забегайте.