Тестирование Python приложений с помощью hypothesis

Тестирование с использованием наработанных экспертами входных воздействий

При написании unit-тестов бывает трудно подобрать тестовые значения. Каждый из нас начинает идти по одним и тем же граблям, набирая свою базу “волшебных значений”, которые скорее всего уронят ваш код и найдут в нем баг.

Не так давно был моден тренд на генерацию случайных значенийв качестве входных значений.

Это и в самом деле очень эффективный способ проверять систему в целом.

Но это не очень удобно, потому что либо приводит к непредсказумости тестов - при одном прогоне они падают, при другом - нет. Либо требуют где-то поддерживать базу тест-кейсов, куда инструментарий для таких тестов будет складывать упавшие кейсы, чтобы их не потерять. Что в условиях распределенных CI со множеством агентов не очень просто организовать. Да еще в условиях когда мы хотим локально запускать unit-тесты, еще до отправки на CI.

К тому же, чтобы случайно попасть на болевые точки надо прогнать большое число тестов со случайными данными - это по сути вариант Теоремы о бесконечных обезьянах.

Для Python существует замечательный инструмент, который с одной стороны не имеет проблемы “мигающих тестов”, а с другой уже собрал за нас базу “проблемных” входных параметров.

class Employee:
    def __init__(self, salary):
        self.salary = salary

    def give_a_raise(self, increase):
        self.salary += increase


from hypothesis import given
from hypothesis import strategies as st


money_strategy = st.floats(min_value=1, max_value=1000000, allow_nan=False, allow_infinity=False)


@given(money_strategy, money_strategy, money_strategy)
def test_bonus_distribution(salary1, salary2, bonus_fund):
    e1 = Employee(salary1)
    e2 = Employee(salary2)
    increase1 = bonus_fund / 2
    increase2 = bonus_fund - increase1
    e1.give_a_raise(increase1)
    e2.give_a_raise(increase2)
    money_spent = e1.salary - salary1 + e2.salary - salary2
    assert bonus_fund == money_spent
test_finance.py F
test_finance.py:15 (test_bonus_distribution)
1.0000000000000002 != 1.0

Expected :1.0
Actual   :1.0000000000000002

Конечно, я привел первый пришедший в голову пример как нельзя работать с деньгами - их никогда нельзя представлять в приложении как float. Бухгалтерии важна каждая копейка, и они почему-то предпочитают работать с деньгами в десятичном представлении. Что плохо сочетается с хранением в компьютере в двоичном виде, который по определению не может точно представить любое произвольное дробное.

С другой стороны, пример замечателен тем, что показывает, что перед нами не просто test framework, а экспертная БД, которая зачастую покажет, какой код неверен на уровне архитектуры.