Должен ли я проводить модульное тестирование внутренних функций, используемых в API, который я экспортирую?

Должен ли я проводить модульное тестирование внутренних функций, используемых в API, который я экспортирую?
Должен ли я проводить модульное тестирование внутренних функций, используемых в API, который я экспортирую? - faisaldada @ Unsplash

Я пишу CRUD-приложение на Python, которое экспонирует веб-API. Сначала я написал функции для взаимодействия с БД и написал тесты для этих функций.

def crud():
   # do something with db

def test_crud():
   crud()
   # check crud tables now have all required rows

Затем я написал функции для маршрутов API, которые делают некоторую проверку ввода, а затем просто вызывают соответствующую грубую функцию. Все, что делают API функции, это вызывают crud функции внутри себя и возвращают результат. Тесты для них выглядят следующим образом:

def api():
   # do some api stuff
   return crud()

def test_api():
   resp = api()
   # check response body has what we expect
   # check crud tables now have all required rows (1)

Здесь (1) это уже было для меня звоночком, но неважно.

Затем мне пришлось добавить новый module, который что-то делает с DB, вызывает какую-то crud функцию и должен использоваться в api функции вместо crud функции.

def module():
    # do some stuff
    crud()
    # do some stuff

def api():
   # do some api stuff
   return module()

Итак, теперь тесты выглядят следующим образом:

def test_crud():
   crud()
   # check crud tables now have all required rows

def test_module():
   module()
   # check module tables now have all required rows
   # check crud tables now have all required rows

def test_api():
   resp = api()
   # check response body has what we expect
   # check module tables now have all required rows
   # check crud tables now have all required rows (1)

Меня беспокоит то, что я проверяю одно и то же в трех местах, и в случае каких-то изменений мне придется трижды редактировать по сути один тест. Я не думаю, что мне следует удалять, например, проверки из test_api(), связанные с побочными эффектами вызываемых изнутри функций, потому что меня волнует как ответ API, так и эти эффекты.

Я читал похожие вопросы типа "нужно ли тестировать только публичные методы или приватные тоже?", где, в общем, ответ был "тестируйте публичные методы, но не приватные". Применимо ли это к моей ситуации, нужно ли мне удалить тесты для crud и module и оставить только test_api, который тестирует все то, что делают первые два?

Концепция, которая всегда оставалась со мной в разговоре с группой пользователей о тестировании, заключается в следующем:

  • Модульное тестирование и интеграционное тестирование существуют в спектре, а не по обе стороны четкой разделительной линии. Вы бы не рассматривали каждую функцию, встроенную в Python, как соавтора, над которым нужно издеваться, поэтому даже самый узкий «модульный тест» обязательно «интегрирует» низкоуровневый код.
  • Хорошо иметь тесты для нескольких частей этого спектра для одного и того же кода. Узкие модульные тесты отлично подходят для доказательства того, что компонент соответствует своему разработанному контракту — в широком смысле того, что он должен делать, а не в узком смысле типов ввода и вывода. Более широкие тесты, ориентированные на интеграцию, необходимы для безопасного рефакторинга, когда вы хотите знать, что замена компонентов A и B новыми компонентами X, Y и Z может дать вам тот же конечный результат.

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

  • Реализует ли код свою собственную логику и использует ли другие модули в качестве соавторов, у которых есть определенный контракт? Если это так, замените макет и проверьте, «при условии, что функция CRUD соответствует своему контракту, дополнительная логика будет выглядеть так». Например, «при условии, что база данных содержит 5 бронирований (возвращенных фиктивным объектом CRUD), модуль должен выполнить эту обработку».
  • Является ли код абстракцией более высокого уровня, использующей другие единицы в качестве деталей реализации? Если это так, напишите тест так, как будто вы не знаете, что это за детали реализации, и рассмотрите контракт на этом уровне. Например, «при условии, что пользователь запрашивает бронирование и некоторые бронирования существуют, выходные данные должны быть в этом формате JSON».

LetsCodeIt, 17 декабря 2022 г., 19:19