python-testing · pytest · Hypothesis · Schemathesis · Locust

Тесты на Python: книжный магазин

Testing in Python: a bookshop

Свой Bookshop API на FastAPI и SQLite и 77 тестов вокруг него: API, контракт, свойства, интеграция с базой, безопасность и нагрузка. Тесты нашли в собственном сервисе четыре расхождения с контрактом и ошибку многопоточности, которую не видел ни один функциональный тест.

A Bookshop API of our own on FastAPI and SQLite and 77 tests around it: API, contract, properties, database integration, security and load. The tests found four contract gaps in our own service and a threading bug no functional test saw.

77тестов за 16 сtests in 16 s 4дыры в контрактеcontract gaps 0.3 %500-х под нагрузкой — до исправления500s under load, before the fix 11/11посаженных багов пойманыplanted bugs caught
01

Округление: почему нужен Hypothesis

Rounding: why Hypothesis

Скидка округляется до копеек «как на кассе» — половина вверх. Банковское округление отличается только когда остаток ровно половина копейки. Ни одна из семи вручную подобранных корзин такого не содержала, и посаженный баг M1 поймал только Hypothesis, сравнивающий сервис с независимым оракулом. Введите сумму и скидку.

The discount is rounded to cents the way a till does: half up. Banker's rounding differs only when exactly half a cent is left. None of the seven hand-picked baskets had that, and planted bug M1 was caught only by Hypothesis comparing the service with an independent oracle. Enter an amount and a discount.

02

Что Schemathesis нашёл в нашем же контракте

What Schemathesis found in our own contract

Schemathesis читает /openapi.json, который публикует сам сервис, генерирует запросы и проверяет ответы. Каждая находка — место, где опубликованный контракт обещал не то, что делает код.

Schemathesis reads the /openapi.json the service itself publishes, generates requests and checks the answers. Each finding is a place where the published contract promised something the code doesn't do.

03

Locust и одно соединение на всех

Locust and one connection for everyone

FastAPI выполняет обработчики в пуле потоков. Пока все потоки делили одно соединение SQLite, чтение рядом с записью иногда получало пустую строку — 500. Функциональные тесты ходят по одному запросу и этого не видят; 30 пользователей Locust увидели за полминуты.

FastAPI runs handlers in a thread pool. While every thread shared one SQLite connection, a read next to a write sometimes got an empty row: a 500. Functional tests send one request at a time and never see it; 30 Locust users saw it within half a minute.

04

Посаженные баги

Planted bugs

python scripts/mutants.py сажает по одному багу и запускает весь набор.

python scripts/mutants.py plants one bug at a time and runs the whole suite.

05

Словарь и команды

Terms and commands