Наткнулся тут на язык Whiley со встроенным контрактным программированием. По сути, под капотом просто на этапе проверки запускает функцию с разными входными параметрами. Если входные данные не соответствуют pre-condition, просто игнорируем этот тест. Если соответствует, но падает на ensure (post-condition), то, значит, контракт нарушен, паникуем. Собственно, это вся магия. Простой способ вместо полных тестов писать только условия на входные параметры и результат, а о конкретный значениях, да ещё чтобы покрыть все крайние случаи, пусть машина думает.
В Python подобное можно провернуть с помощью hypothesis. Навешиваете на тест декоратор, в котором говорите “я хочу получать на вход 2 целых числа”, пишете сам тест, и hypothesis сделает всё остальное: подберет значение, сгруппирует ошибки, сформирует минимальный пример ломающих всё входных данных. Я уже писал про него когда-то, в посте про тестирование в Python. Пример из документации:
def test_decode_inverts_encode(s):
assert decode(encode(s)) == s```
Но можно пойти дальше. В большом проекте тестов много, да и никто на них не смотрит, когда код читает, на самом деле. У нас есть сигнатура функции, из которой понятно, какие параметры в функцию нужно передать. А теперь ещё есть аннотации типов. Указываем, какого типа значения можно передавать в каждый параметр, и всё, мы уже знаем довольно много о входных значениях. Ну так вот, [hypothesis-auto](https://timothycrosley.github.io/hypothesis-auto/) — как раз тот самый инструмент, который умеет смотреть на сигнатуру и аннотации, чтобы формировать стратегии для hypothesis. Чуть побольше можно почтать в [Opensource findings](https://t.me/opensource_findings/138). Пример кода:
```auto_test(divide, auto_allow_exceptions_=(ZeroDivisionError, ))```
Но аннотации покрывают далеко не все условия, накладываемые на входные параметры. Даже в примере выше мы знаем, что второй параметр `divide` должен быть не нулём. Библиотека [deal](https://github.com/life4/deal) позволяет делать честные контракты. Только аннотации туда не стоит переносить, потому что типы можно проверить статически (что [mypy](https://github.com/python/mypy) и делает), а контракты — только запустив функцию. Отличие контрактов от простых проверок и исключений в том, что здесь условия чётко отделяются от основного кода, унифицируются в плане формы и гарантированно запускаются перед функцией (или после, в случае post-condition). Пример кода:
```@deal.pre(lambda a, b: b != 0)
def div(a: int, b: int) -> float:
return a / b```
А теперь лёгким движением руки скрещиваем hypothesis-auto, аннотации и deal, и получаем магическую строчку для автоматического тестирования функции выше:
```auto_test(div, auto_allow_exceptions_=deal.PreContractError)```
Конечно, это не все тесты, которые нужно сделать для проекта. Но когда можно красиво описать условия, накладываемые на входные и выходные значения функции, а затем просто попросить машину самостоятельно проверить эти условия — это очень круто. Бесплатные тесты и повышение понятности кода при минимальных усилиях.