Всем доброго!

От нашего стола к вашему... То есть от нашего курса «Разработчик Python», несмотря на стремительно приближающий Новый год, мы подготовили вам интересный перевод о различных методах тестирования в Python.

Это руководство для тех, кто уже написал классное приложение на Python, но еще не писал для
них тесты.

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

В этом руководстве вы узнаете, как создать базовый тест, выполнить его и найти все баги, до того как это сделают пользователи! Вы узнаете о доступных инструментах для написания и выполнения тестов, проверите производительность приложения и даже посмотрите на проблемы безопасности.



Тестирование Кода

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

Автоматизированное vs. Ручное Тестирование

Хорошие новости! Скорее всего вы уже сделали тест, но еще не осознали этого. Помните, как вы впервые запустили приложение и воспользовались им? Вы проверили функции и поэкспериментировали с ними? Такой процесс называется исследовательским тестированием, и он является формой ручного тестирования.

Исследовательское тестирование — тестирование, которое проводится без плана. Во время исследовательского тестирования вы исследуете приложение.

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

Звучит безрадостно, верно?

Поэтому нужны автоматические тесты. Автоматическое тестирование — исполнение плана тестирования (части приложения, требующие тестирования, порядок их тестирования и ожидаемые результаты) с помощью скрипта, а не руками человека. В Python уже есть набор инструментов и библиотек, которые помогут создать автоматизированные тесты для вашего приложения. Рассмотрим эти инструменты и библиотеки в нашем туториале.

Модульные Тесты VS. Интеграционные Тесты

Мир тестирования полон терминов, и теперь, зная разницу между ручным и автоматизированным тестированием, опустимся на уровень глубже.

Подумайте, как можно протестировать фары машины? Вы включаете фары (назовем это шагом тестирования), выходите из машины сами или просите друга, чтобы проверить, что фары зажглись (а это — тестовое суждение). Тестирование нескольких компонентов называется интеграционным тестированием.

Подумайте о всех вещах, которые должны правильно работать, чтобы простая задача выдала корректный результат. Эти компоненты похожи на части вашего приложения: все те классы, функции, модули, что вы написали.

Главная сложность интеграционного тестирования возникает, когда интеграционный тест не дает правильный результат. Сложно оценить проблему, не имея возможности изолировать сломанную часть системы. Если фары не зажглись, возможно лампочки сломаны. Или может аккумулятор разряжен? А может проблема в генераторе? Или вообще сбой в компьютере машины?

Современные машины сами оповестят вас о поломке лампочек. Определяется это с помощью модульного теста.

Модульный тест (юнит-тест) — небольшой тест, проверяющий корректность работы отдельного компонента. Модульный тест помогает изолировать поломку и быстрее устранить ее.

Мы поговорили о двух видах тестов:

  1. Интеграционный тест, проверяющий компоненты системы и их взаимодействие друг с другом;
  2. Модульный тест, проверяющий отдельный компонент приложения.
  3. Вы можете создать оба теста на Python. Чтобы написать тест для встроенной функции sum(), нужно сравнить выходные данные sum() с известными значениями.

Например, вот так можно проверить что сумма чисел (1, 2, 3) равна 6:

>>> assert sum([1, 2, 3]) == 6, "Should be 6"

Значения правильные, поэтому в REPL ничего не будет выведено. Если результат sum() некорректный, будет выдана AssertionError с сообщением “Should be 6” (“Должно быть 6”). Проверим оператор утверждения еще раз, но теперь с некорректными значениями, чтобы получить AssertionError:

>>> assert sum([1, 1, 1]) == 6, "Should be 6"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: Should be 6

В REPL вы увидете AssertionError, так как значение sum() не равно 6.

Вместо REPL, положите это в новый Python-файл с названием test_sum.py и выполните его снова:

def test_sum():
    assert sum([1, 2, 3]) == 6, "Should be 6"

if __name__ == "__main__":
    test_sum()
    print("Everything passed")

Теперь у вас есть написанный тест-кейс (тестовый случай), утверждение и точка входа (командной строки). Теперь это можно выполнить в командной строке:

$ python test_sum.py
Everything passed

Вы видите успешный результат, “Everything passed” (“Все пройдено”).

sum() в Python принимает на вход любой итерируемый в качестве первого аргумента. Вы проверили список. Попробуем протестировать кортеж. Создадим новый файл с названием test_sum_2.py со следующим кодом:

def test_sum():
    assert sum([1, 2, 3]) == 6, "Should be 6"

def test_sum_tuple():
    assert sum((1, 2, 2)) == 6, "Should be 6"

if __name__ == "__main__":
    test_sum()
    test_sum_tuple()
    print("Everything passed")

Выполнив test_sum_2.py, скрипт выдаст ошибку, так как sum() (1, 2, 2) должен быть равен 5, а не 6. В результате скрипт выдает сообщение об ошибке, строку кода и трейсбек:

$ python test_sum_2.py
Traceback (most recent call last):
  File "test_sum_2.py", line 9, in <module>
    test_sum_tuple()
  File "test_sum_2.py", line 5, in test_sum_tuple
    assert sum((1, 2, 2)) == 6, "Should be 6"
AssertionError: Should be 6

Можно увидеть, как ошибка в коде вызывает ошибку в консоли с информацией, где она произошла, и каким был ожидаемый результат.

Такие тесты подойдут для простой проверки, но что если ошибки есть больше, чем в одном? На помощь приходят исполнители тестов (test runners). Исполнитель тестов — особое приложение, спроектированное для проведение тестов, проверки данных вывода и предоставления инструментов для отладки и диагностики тестов и приложений.

Выбор Исполнителя Тестов

Для Python доступно множество исполнителей тестов. Например, в стандартную библиотеку Python встроен unittest. В этом руководстве, будем использовать тест-кейсы и исполнители тестов unittest. Принципы работы unittest легко адаптируются для других фреймворков. Перечислим самые популярные исполнители тестов:

  • unittest;
  • nose или nose2;
  • pytest.

Важно выбрать исполнитель тестов, соответствующий вашим требованиям и опытности.

unittest

unittest встроен в стандартную библиотеку Python, начиная с версии 2.1. Вы наверняка столкнетесь с ним в коммерческих приложениях Python и проектах с открытым исходным кодом.
В unittest есть тестовый фреймворк и исполнитель тестов. При написании и исполнении тестов нужно соблюдать некоторые важные требования.

unittest требует:

  • Помещать тесты в классы, как методы;
  • Использовать специальные методы утверждения. Класс TestCase вместо обычного встроенного выражения assert.


Чтобы превратить ранее написанный пример в тест-кейс unittest, необходимо:

  1. Импортировать unittest из стандартной библиотеки;
  2. Создать класс под названием TestSum, который будет наследовать класс TestCase;
  3. Сконвертировать тестовые функции в методы, добавив self в качестве первого аргумента;
  4. Изменить утверждения, добавив использование self.assertEqual() метода в классе TestCase;
  5. Изменить точку входа в командной строке на вызов unittest.main().

Следуя этим шагам, создайте новый файл test_sum_unittest.py со таким кодом:

import unittest


class TestSum(unittest.TestCase):

    def test_sum(self):
        self.assertEqual(sum([1, 2, 3]), 6, "Should be 6")

    def test_sum_tuple(self):
        self.assertEqual(sum((1, 2, 2)), 6, "Should be 6")

if __name__ == '__main__':
    unittest.main()

Выполнив это в командной строке, вы получите одно удачное завершение (обозначенное .) и одно неудачное (обозначенное F):

$ python test_sum_unittest.py
.F
======================================================================
FAIL: test_sum_tuple (__main__.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_sum_unittest.py", line 9, in test_sum_tuple
    self.assertEqual(sum((1, 2, 2)), 6, "Should be 6")
AssertionError: Should be 6

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

Таким образом, вы выполнили два теста с помощью исполнителя тестов unittest.

Примечание: Если вы пишете тест-кейсы для Python 2 и 3 — будьте осторожны. В версиях Python 2.7 и ниже unittest называется unittest 2. При импорте из unittest вы получите разные версии с разными функциями в Python 2 и Python 3.

Чтобы узнать больше о unittest’ах почитайте unittest документацию.

nose

Со временем, после написания сотни, а то и тысячи тестов для приложения, становится все сложнее понимать и использовать данные вывода unittest.

nose совместим со всеми тестами, написанными с unittest фреймворком, и может заменить его тестовый исполнитель. Разработка nose, как приложения с открытым исходным кодом, стала тормозиться, и был создан nose2. Если вы начинаете с нуля, рекомендуется использовать именно nose2.

Для начала работы с nose2 нужно установить его из PyPl и запустить в командной строке. nose2 попытается найти все тестовые скрипы с test*.py в названии и все тест-кейсы, унаследованные из unittest.TestCase в вашей текущей директории:

$ pip install nose2
$ python -m nose2
.F
======================================================================
FAIL: test_sum_tuple (__main__.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_sum_unittest.py", line 9, in test_sum_tuple
    self.assertEqual(sum((1, 2, 2)), 6, "Should be 6")
AssertionError: Should be 6

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

Так выполняется тест, созданный в test_sum_unittest.py, из исполнителя тестов nose2. nose2 предоставляет множество флагов командной строки для фильтрации исполняемых тестов. Чтобы узнать больше, советуем ознакомиться с документацией Nose 2.

pytest

pytest поддерживает выполнение тест-кейсов unittest. Но настоящее преимущество pytest — его тест-кейсы. Тест-кейсы pytest — серия функций в Python-файле с test_ в начале названия.

Есть в нем и другие полезные функции:

  • Поддержка встроенных выражений assert вместо использования специальных self.assert*() методов;
  • Поддержка фильтрации тест-кейсов;
  • Возможность повторного запуска с последнего проваленного теста;
  • Экосистема из сотен плагинов, расширяющих функциональность.

Пример тест-кейса TestSum для pytest будет выглядеть следующим образом:

def test_sum():
    assert sum([1, 2, 3]) == 6, "Should be 6"

def test_sum_tuple():
    assert sum((1, 2, 2)) == 6, "Should be 6"

Вы избавились от TestCase, использования классов и точек входа командной строки.
Больше информации можно найти на Сайте Документации Pytest.

Написание Первого Теста

Объединим все, что мы уже узнали, и вместо встроенной функции sum() протестируем простую реализацию с теми же требованиями.

Создайте новую папку для проекта, внутри которой создайте новую папку с названием my_sum. Внутри my_sum создайте пустой файл с названием _init_.py. Наличие этого файла значит, что папка my_sum может быть импортирована в виде модуля из родительской директории.

Структура папок будет выглядеть так:

project/
¦
L-- my_sum/
L-- __init__.py


Откройте my_sum/__init__.py и создайте новую функцию с названием sum(), которая берет на вход итерируемые (список, кортеж, множество) и складывает значения.

def sum(arg):
    total = 0
    for val in arg:
        total += val
    return total

В этом примере создается переменная под названием total, перебираются все значения в arg и добавляются к total. Затем, по завершении итерации, результат возвращается.

Где Писать Тест

Начать написание теста можно с создания файла test.py, в котором будет содержаться ваш первый тест-кейс. Для тестирования у файла должна быть возможность импортировать ваше приложение, поэтому положите test.py в папку над пакетом. Дерево каталогов будет выглядеть следующим образом:

project/
¦
+-- my_sum/
¦ L-- __init__.py
|
L-- test.py


Вы заметите, что по мере добавления новых тестов, ваш файл становится все более громоздким и сложным для поддержки, поэтому советуем создать папку tests/ и разделить тесты на несколько файлов. Убедитесь, что названия всех файлов начинаются с test_, чтобы исполнители тестов понимали, что файлы Python содержат тесты, которые нужно выполнить. На больших проектах тесты делят на несколько директорий в зависимости от их назначения или использования.

Примечание: А что есть ваше приложение представляет собой один скрипт?
Вы можете импортировать любые атрибуты скрипта: классы, функции или переменные, с помощью встроенной функции __import__(). Вместо from my_sum import sum напишите следующее:


target = __import__("my_sum.py")
sum = target.sum

При использовании __import__() вам не придется превращать папку проекта в пакет, и вы сможете указать имя файла. Это полезно, если имя файла конфликтует с названиями стандартных библиотек пакетов. Например, если math.py конфликтует с math модулем.

Как Структурировать Простой Тест

Перед написанием тестов, нужно решить несколько вопросов:

  1. Что вы хотите протестировать?
  2. Вы пишете модульный тест или интеграционный тест?

Сейчас вы тестируете sum(). Для него можно проверить разные поведения, например:

  • Можно ли суммировать список целых чисел?
  • Можно ли суммировать кортеж или множество?
  • Можно ли суммировать список чисел с плавающей точкой?
  • Что будет, если дать на вход плохое значение: одно целое число или строку?
  • Что будет, если одно из значений отрицательное?

Проще всего тестировать список целых чисел. Создайте файл test.py со следующим кодом:

import unittest

from my_sum import sum


class TestSum(unittest.TestCase):
    def test_list_int(self):
        """
        Test that it can sum a list of integers
        """
        data = [1, 2, 3]
        result = sum(data)
        self.assertEqual(result, 6)

if __name__ == '__main__':
    unittest.main()

Код в этом примере:

  • Импортирует sum() из пакета my_sum(), который вы создали;
  • Определяет новый класс тест-кейса под названием TestSum, наследующий unittest.TestCase;
  • Определяет тестовый метод .test_list_int() для тестирования целочисленного списка. Метод .test_list_int() сделает следующее
:
  1. Объявит переменную data со списком значений (1, 2, 3);
  2. Присвоит значение my_sum.sum(data) переменной result;
  3. Определит, что значение result равно 6 с помощью метода .assertEqual() на unittest.TestCase классе.

  • Определяет точку входа командной строки, которая запускает исполнителя теста unittest .main().

Если вы не знаете, что такое self, или как определяется .assertEqual(), то можете освежить знания по объектно-ориентированному программированию с Python 3 Object-Oriented Programming.

Как Писать Утверждения

Последний шаг в написании теста — проверка соответствия выходных данных известным значениям. Это называют утверждением (assertion). Существует несколько общих рекомендаций по написанию утверждений:

  • Проверьте, что тесты повторяемы и запустите их несколько раз, чтобы убедиться, что каждый раз они дают одни и те же результаты;
  • Проверьте и подтвердите результаты, которые относятся к вашим входным данным — проверьте, что результат действительно является суммой значений в примере sum().

В unittest есть множество методов для подтверждения значений, типов и существования переменных. Вот некоторые из наиболее часто используемых методов:

Метод Эквивалент
.assertEqual(a, b) a == b
.assertTrue(x) bool(x) is True
.assertFalse(x) bool(x) is False
.assertIs(a, b) a is b
.assertIsNone(x) x is None
.assertIn(a, b) a in b
.assertIsInstance(a, b) isinstance(a, b)


У .assertIs(), .assertIsNone(), .assertIn(), and .assertIsInstance() есть противоположные методы, называемые .assertIsNot() и тд.

Побочные эффекты

Писать тесты сложнее, чем просто смотреть на возвращаемое значение функции. Зачастую, выполнение кода меняет другие части окружения: атрибуты класса, файлы файловой системы, значения в базе данных. Это важная часть тестирования, которая называется побочные эффекты. Решите, тестируете ли вы побочный эффект до того, как включить его в список своих утверждений.

Если вы обнаружили, что в блоке кода, который вы хотите протестировать, много побочных эффектов, значит вы нарушаете Принцип Единственной Ответственности. Нарушение принципа единственной ответственности означает, что фрагмент кода делает слишком много вещей и требует рефакторинга. Следование принципу единственной ответственности — отличный способ проектирования кода, для которого не составит труда писать простые повторяемые модульные тесты, и, в конечном счете, создания надежных приложений.

Запуск Первого Теста

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

Запуск Исполнителей Тестов

Исполнитель тестов — приложение Python, которое выполняет тестовый код, проверяет утверждения и выдает результаты тестирования в консоли. В конец test.py добавьте этот небольшой фрагмент кода:

if __name__ == '__main__':
    unittest.main()

Это точка входа командной строки. Если вы выполните этот скрипт, запустив python test.py в командной строке, он вызовет unittest.main(). Это запускает исполнителя тестов, обнаруживая все классы в этом файле, наследуемые из unittest.TestCase.

Это один из многих способов запуска исполнителя тестов unittest. Если у вас есть единственный тестовый файл с названием test.py, вызов python test.py — отличный способ начать работу.

Другой способ — использовать командную строку unittest. Попробуем:

$ python -m unittest test

Это исполнит тот же самый тестовый модуль (под названием test) через командную строку. Можно добавить дополнительные параметры для изменения выходных данных. Один из них -v для многословности (verbose). Попробуем следующее:

$ python -m unittest -v test
test_list_int (test.TestSum) ... ok

----------------------------------------------------------------------
Ran 1 tests in 0.000s

Мы исполнили один тест из test.py и вывели результаты в консоль. Многословный режим перечислил имена выполненных тестов и результаты каждого из них.

Вместо предоставления имени модуля, содержащего тесты, можно запросить авто-обнаружение при помощи следующего:


$ python -m unittest discover

Эта команда будет искать в текущей директории файлы с test*.py в названии, чтобы протестировать их.

При наличии нескольких тестовых файлов и соблюдении шаблона наименования test*.py, можно передать имя директории при помощи -s флага и названия папки.

$ python -m unittest discover -s tests

unittest запустит все тесты в едином тестовом плане и выдаст результаты.
Наконец, если ваш исходный код находится не в корневом каталоге, а в подкаталоге, например в папке с названием src/, можно с помощью -t флага сообщить unittest, где выполнять тесты, для корректного импорта модулей:

$ python -m unittest discover -s tests -t src

unittest найдет все файлы test*.py в директории src/ внутри tests, а затем выполнит их.

Понимание Результатов Тестирование

Это был очень простой пример, где все прошло успешно, поэтому попробуем понять выходные данные проваленного теста.

sum() должен принимать на вход другие списки числового типа, например дроби.

К началу кода в файле test.py добавьте выражение для импорта типа Fraction из модуля fractions стандартной библиотеки.

from fractions import Fraction

Теперь добавим тест с утверждением, ожидая некорректное значение. В нашем случае, ожидаем, что сумма ?, ? и ? будет равна 1:

import unittest

from my_sum import sum


class TestSum(unittest.TestCase):
    def test_list_int(self):
        """
        Test that it can sum a list of integers
        """
        data = [1, 2, 3]
        result = sum(data)
        self.assertEqual(result, 6)

    def test_list_fraction(self):
        """
        Test that it can sum a list of fractions
        """
        data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)]
        result = sum(data)
        self.assertEqual(result, 1)

if __name__ == '__main__':
    unittest.main()
 

Если вы запустите тесты повторно с python -m unittest test, получите следующее:

$ python -m unittest test
F.
======================================================================
FAIL: test_list_fraction (test.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 21, in test_list_fraction
    self.assertEqual(result, 1)
AssertionError: Fraction(9, 10) != 1

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

В этих выходных данных вы видите следующее:

  • В первой строке указаны результаты выполнения всех тестов: один проваленный (F), один пройденный (.);
  • FAIL показывает некоторые детали проваленного теста:

  1. Название тестового метода (test_list_fraction);
  2. Тестовый модуль (test) и тест-кейс (TestSum);
  3. Трейсбек строки с ошибкой;
  4. Детали утверждения с ожидаемым результатом (1) и фактическим результатом (Fraction(9, 10))

Помните, можно добавить дополнительную информацию к выходным данным теста с помощью флага -v к команде python -m unittest.

Запуск тестов из PyCharm

Если вы используете PyCharm IDE, то можете запустить unittest или pytest, выполнив следующие шаги:

  1. В окне Project tool, выберите директорию tests.
  2. В контекстном меню выберите команду запуска unittest. Например, ‘Unittests in my Tests…’.

Это выполнит unittest в тестовом окне и выдаст результаты в PyCharm:



Больше информации доступно на сайте PyCharm.

Запуск Тестов из Visual Studio Code

Если вы пользуетесь Microsoft Visual Studio Code IDE, поддержка unittest, nose и pytest уже встроена в плагин Python.

Если он у вас установлен, можно настроить конфигурацию тестов, открыв Command Palette по Ctrl+Shift+P и написав “Python test”. Вы увидите список вариантов:



Выберите Debug All Unit Tests, после чего VSCode отправит запрос для настройки тестового фреймворка. Кликните по шестеренке для выбора исполнителя тестов (unittest) и домашней директории (.).

По завершении настройки, вы увидите статус тестов в нижней части экрана и сможете быстро получить доступ к тестовым логам и повторно запустить тесты, кликнув по иконкам:



Видим, что тесты выполняются, но некоторые из них провалены.

THE END

В следующей части статьи мы рассмотрим тесты для фреймворков, таких как Django и Flask.

Ждём ваши вопросы и комментарии тут и, как всегда, можно зайти к Станиславу на день открытых дверей.

Комментарии (14)


  1. anthonio
    17.12.2018 06:54

    A QA engineer walks into a bar. Orders a beer. Orders 0 beers. Orders 99999999999 beers. Orders a lizard. Orders -1 beers. Orders a ueicbksjdhd.

    First real customer walks in and asks where the bathroom is. The bar bursts into flames, killing everyone.

    — Brenan Keller (@brenankeller) November 30, 2018



  1. vovochkin
    17.12.2018 09:16
    +2

    1) Вывод unittest:

    Заголовок спойлера
    $ python test_sum_unittest.py
    .F
    ======================================================================
    FAIL: test_sum_tuple (__main__.TestSum)
    — Traceback (most recent call last):
    File «test_sum_unittest.py», line 9, in test_sum_tuple
    self.assertEqual(sum((1, 2, 2)), 6, «Should be 6»)
    AssertionError: Should be 6

    — Ran 2 tests in 0.001s

    FAILED (failures=1)



  1. true-grue
    17.12.2018 09:54

    Меня удивляет, почему так мало внимания (в данной заметке вообще не уделяется) такой замечательной штуке, как doctest. Это одна из тех вещей, которую из Питона (или, возможно, из Лиспа) позаимствовали многие современные языки.


    1. atomheart
      17.12.2018 12:16

      Спасибо за наводку! Очень интересная штука, о которой я не встречал упоминаний.


    1. kvichans
      17.12.2018 13:37

      Спасибо, это ценно. Стандартная библиотека не исчерпаема «как атом»


    1. yetanotherman
      17.12.2018 16:10

      Штука классная, но очень легко с ней перестараться. На её примере очень легко показать, что автотесты это хорошо и правильно, но при первых признаках того, что тесты и доки в вашем проекте расходятся — нужно частично или полностью уходить на «взрослые» фреймворки — иначе беда.


  1. Xop
    17.12.2018 11:50

    А ещё удивляет, почему так мало пишут про такую классную штуку, как property based testing. Причем в питоне есть просто офигенный фреймворк hypothesis как раз для этого.


    1. worldmind
      17.12.2018 14:58

      У вас есть опыт использования?


      1. Xop
        17.12.2018 15:10

        Есть положительный опыт использования самого property based подхода в проекте на C++, есть опыт использования конкретно hypothesis в pet-проекте, и сейчас как раз в процессе переноса этого опыта на ещё один "боевой" проект.


        1. worldmind
          17.12.2018 15:17

          При случае черканите статейку, а то я пока не определюсь насколько практичен этот поход, понятно, когда в какой-нибудь вводной статье тестируют функцию `sum`всё круто, но на практике бывают всякие сложные объекты и всё такое — непонятно будет ли выигрыш или проще в json какой написать тестовый набор данных.


          1. Xop
            17.12.2018 15:29
            +1

            Да, были мысли написать сборник идей для поиска свойств с примерами (обычно именно тут в начале самый большой затык с этим подходом происходит). Ну а если основные впечатления и кратко, то:


            • обычные example-based тесты никто не отменял
            • нужен некоторый опыт и определенная повернутость мозга, чтобы быстро придумывать хорошие property-тесты
            • подходит не для всего (хотя чем больше опыта, тем для бОльшего количества проблем получается находить хорошие свойства)
            • когда подходит (в моих случаях — подходит часто), то позволяет относительно небольшими усилиями вылавливать просто горы всяких edge-кейсов


            1. ser-mk
              17.12.2018 16:53

              Было бы интересно, если приведете в статье какие-нить нетривиальные примеры.
              Потому что в статьях про property based testing обычно рассматривается банальщина с числами и строками.


              1. Xop
                17.12.2018 20:04

                Да, нетривиальные (на мой взгляд) примеры очень даже есть, мотивация написать статью усилилась, осталось только найти время.


  1. amarao
    17.12.2018 16:27

    Статья очень unittest'овая. Для меня моим Первым Тестовым Фреймворком был pytest, и каждый раз, натыкаясь на unittest, я испытываю эстетический дискомфорт. pytest — реально питоническая система тестирования, с минимумом boilerplate, но позволяющая разрастаться на сложные штуки. Оно не требует сложного для простого, но позволяет сложное для сложного.