Статьи

Написание тестов для кода доступа к данным — не тестируйте фреймворк

Когда мы пишем тесты для нашего кода доступа к данным, должны ли мы тестировать каждый метод его открытого API?

Сначала это звучит естественно. В конце концов, если мы не тестируем все, как мы можем знать, что наш код работает, как ожидалось?

Этот вопрос дает нам важную подсказку:

Наш код

Мы должны писать тесты только для нашего собственного кода.

Каков наш собственный код?

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

Например, если мы хотим создать репозиторий Spring Data JPA, который предоставляет операции CRUD для объектов Todo , мы должны создать интерфейс, который расширяет интерфейс CrudRepository . Исходный код интерфейса TodoRepository выглядит следующим образом:

1
2
3
4
5
import org.springframework.data.repository.CrudRepository;
 
public TodoRepository extends CrudRepository<Todo, Long> {
 
}

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

Эти методы не являются нашим кодом, потому что они реализованы и поддерживаются командой Spring Data. Мы используем только их.

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

1
2
3
4
5
6
7
8
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
 
public TodoRepository extends CrudRepository<Todo, Long> {
 
    @Query("SELECT t FROM Todo t where t.title=:searchTerm")
    public List<Todo> search(@Param("searchTerm") String searchTerm)
}

Было бы легко утверждать, что этот метод является нашим собственным кодом, и поэтому мы должны его протестировать. Однако правда немного сложнее. Несмотря на то, что запрос JPQL был написан нами, Spring Data JPA предоставляет код, который передает этот запрос используемому поставщику JPA.

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

Если мы хотим идентифицировать наш собственный код доступа к данным, мы должны найти необходимую часть каждого метода. Если эта часть была написана нами, мы должны рассматривать этот метод как наш собственный код.

Это все довольно очевидно, и более интересный вопрос:

Должны ли мы проверить это?

Наш интерфейс репозитория предоставляет два вида методов классам, которые его используют:

  1. Он предоставляет методы, которые объявлены интерфейсом CrudRepository .
  2. Он предоставляет метод запроса, который был написан нами.

Должны ли мы писать интеграционные тесты в интерфейс TodoRepository и тестировать все эти методы?

Нет, мы не должны делать это, потому что

  1. Методы, объявленные интерфейсом CrudRepository , не являются нашим собственным кодом. Этот код написан и поддерживается командой Spring Data, и они убедились, что он работает. Если мы не верим, что их код работает, мы не должны его использовать.
  2. Наше приложение, вероятно, имеет много интерфейсов репозитория, которые расширяют интерфейс CrudRepository . Если мы решили написать тесты для методов, объявленных интерфейсом CrudRepository , мы должны написать эти тесты во все репозитории. Если мы выберем этот путь, мы потратим много времени на написание тестов для чужого кода, и, честно говоря, оно того не стоит.
  3. Наш собственный код может быть настолько простым, что запись тестов в наш репозиторий не имеет смысла.

Другими словами, мы должны сосредоточиться на поиске ответа на этот вопрос:

Должны ли мы писать интеграционные тесты для наших методов репозитория (методов, которые мы написали), или мы должны просто писать сквозные тесты?

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

Один из способов принять это решение — подумать о том, какой объем работы требуется для проверки каждого возможного сценария. Это имеет смысл, потому что:

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

Вот почему имеет смысл минимизировать наши инвестиции (время) и максимизировать нашу прибыль (тестовое покрытие). Мы можем сделать это, следуя этим правилам:

  • Если мы можем протестировать все возможные сценарии, написав всего несколько тестов, мы не должны тратить свое время на написание интеграционных тестов для нашего метода репозитория. Мы должны написать сквозные тесты, которые гарантируют, что функция работает должным образом.
  • Если нам нужно написать больше, чем несколько тестов, мы должны написать интеграционные тесты для нашего метода репозитория и написать только несколько сквозных тестов (тесты дыма).

Резюме

Этот пост научил нас двум вещам:

  • Мы не должны тратить время на написание тестов для среды доступа к данным (или библиотеки), написанной кем-то другим. Если мы не доверяем этой платформе (или библиотеке), мы не должны ее использовать.
  • Иногда нам не следует писать интеграционные тесты для нашего кода доступа к данным. Если тестируемый код достаточно прост (мы можем охватить все ситуации, написав несколько тестов), мы должны протестировать его, написав сквозные тесты.