Тестирование программного обеспечения

TDD — мои ошибки и способы их исправления

Вещи, которые я узнал со временем…

Я наткнулся на это отличное видео [1] на TDD. В этом видео есть несколько замечательных моментов, которые перекликаются с моим опытом.

Я собираюсь поделиться этими моментами более подробно, добавив свой опыт.

Изолированное выполнение тестов

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

Пример №1 — Тестирование слоев DAO

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

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

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

Pseudocode

1. Start unit test
2. Create a database
3. Populate test data
4. Test functionality
5. Tear down the created database.

Пример №2 — Мокинг

Мы используем насмешки для тестирования объекта, например service layer, который зависит от дорогого ресурса, скажем, DAO layer communicating with a database.

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

В следующем примере два теста разделяютservice, а также фиктивный объект.

Не делайте этого.

class MyServiceSpec extends AnyWordSpec with MockFactory ... {

  private val dao: MongodbProductDAO = mock[MongodbProductDAO]
  private val myService = new MyService(dao)
  
  "MyService" should {
    "my first test" in { ... }
    "my second test" in { ... }
  }

}

Сделайте это вместо этого.

class MyServiceSpec extends FixtureAnyWordSpec with MockFactory with Matchers {

  override type FixtureParam = MySerivce

  override def withFixture(test: OneArgTest): Outcome = {
    withFixture(
      test.toNoArgTest(
        new MySerivce(mock[MongodbProductDAO])
      )
    )
  }

  "MyService" should {
    "my first test" in { service => ... }
    "my second test" in { service => ... }
  }
}

Каждый тест будет иметь свой собственный служебный объект и, следовательно, имитируемый объект.

Красно-зеленый рефакторинг

Вам может быть интересно, что red-green-refactor = test driven development. Я бы сказал да, но я узнал кое-что новое из этого видео [1].

Вот оно.

1. Write a behavior that fails even without writing any 
implementation -> (RED).
2. Write (even ugly) code (without caring about design patterns etc) to
make the behavior working -> (GREEN)
3. Do refactoring. This is a cleanup process & using design patterns 
comes here.

Это значит:

  1. Можно написать уродливый код, чтобы передать заданное поведение и очистить код (рефакторинг/использование шаблонов проектирования и т. д.) позже.
  2. (ВАЖНО) Рефакторинг не должен нарушить наш тест. Если да, то что-то серьезно не так либо с реализацией, либо с поведением.

Тестирование поведения, а не реализации

Как следует из названия, наш тест не должен быть тесно связан с деталями реализации.

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

Если бы я тестировал поведение, я бы не тестировал приватные методы. Обсуждение тестирования приватных методов вы найдете здесь.

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

Медленное выполнение теста означает медленную обратную связь

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

Здесь я не имею в виду (обязательно) отдельные тесты, но когда весь конвейер CICD работает для вашего конкретного решения.

История о медленном выполнении тестов

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

Это означает, что если у нас есть 2 теста, каждый из которых выполняется 4 раза, то всего будет 8 выполнений. Большее количество казней требует больше времени.

Это замедляет весь конвейер CICD и, следовательно, приводит к медленной обратной связи.

Ресурс

[1] https://www.youtube.com/watch?v=EZ05e7EMOLM

Спасибо за прочтение. Если у вас есть какие-либо вопросы, пожалуйста, не стесняйтесь задавать их в разделе комментариев.

Если вам понравился этот пост, вам также могут понравиться следующие посты.

Want to connect?

Facebook | LinkedIn | Twitter