История полностью вымышленная и приведена только для того, чтобы использовать повествовательную манеру.
Возьмем самый простой пример калькулятора из интернета, на котором обычно демонстрируются как нужно писать модульные тесты и подход TDD.
Итак у меня есть некая система, которая рассчитывает зарплату сотрудников.
Пример кода:
public class SalaryCalculationService {
private readonly Calculator_calculator;
public SalaryCalculationService(Calculator calculator) {
_calculator = calculator;
}
public int CalculateSalary(Employee employee) {
//logic that uses calculator goes here
}
}
Одной из ее зависимостей является класс Calculator.
Пример теста SalaryCalculationService:
public class SalaryCalculationServiceTest {
[TestMethod]
public void ShouldCalculateSalaryCorrectly() {
//Arrange
var calculatorMock = new Mock<Calculator>().Sum().Returns((a, b) => Math.Sum(a, b)); //Мы абстрагируемся от конкретной реализации ICalculator
var calculationService = new SalaryCalculationService(calculatorMock.Object);
var employee = new Employee();
employee.SetWorkingDays(10);
//Act
var result = calculationService.CalculateSalary(employee);
//Assert
Assert.Equals(40, result);
}
}
Пример кода Calculator:
public class Calculator: ICalculator {
public int Sum(int a, int b) {
return a + b;
}
}
Пример кода CalculatorTest:
public class CalculatorTest {
public void ShouldCalculateSumCorrectly() {
//Arrange
var calculator = new Calculator();
//Act
var result = calculator.Sum(2, 2);
//Assert
Assert.Equals(4, result);
}
}
И вот появляется требование, что калькулятор теперь может считать только только нечетных чисел.
Мой коллега работает по TDD, создает тест на это поведение, реализует это поведение, запускает все тесты - зеленые, значит, можно чекинить.
Очевидно, что система в комплексе работать не будет, потому что разные части системы, которые использовали Calculator не ожидают, что теперь он работает таким странным образом. Так и оказалось. Наши тестеры быстро нашли этот баг.
Для того, чтобы избежать таких случаев мы ввели интеграционное тестирование, которое проверяет, как модули взаимодействуют между собой. Для обеспечения скорости мы мокаем все соединения с базой данных, сеть, диск и тд. Интеграционные тесты выявили ряд багов такого плана в нашем приложении, которые были успешно исправлены.
Но вот загвоздка, нам все еще летят баги, причем на те модули, которы покрыты тестами. Мы разбираемся в ситуации и понимаем, что один из наших сервисов, который использует базу данных, отпарвляет SQL запрос с синкаксической ошибкой. Ну вот опять двадцать пять. Все пишем по TDD, все тесты зеленые, а баги все равно появляются.
Появляется ощущение, что чем меньше мы мокаем, тем больше багов ловят наши тесты. Или же альтернативная формулировка: чем более высокоурвнево мы тестируем, тем более реальную картину видим. Если мы разрабатываем бэкэнд, то появляется ощущение, что стоит начинать тестировать с момента, как HTTP запрос пришел на вход управляемого нами кода.
Я прочитал сотню статей по unit/integration тестам и так и не нашел ответ на вопрос: как правильно организовать Unit/Integration тестирование, чтобы создынные тесты отлавливали баги связанные как минимум с рефакторингом, чтобы позволяли рефакторить код без постояных изменений в тестах (меняем завимость = переписываем все моки для нее по всему приложению) и тд?
Расскажите, пожалуйста, о вашем опыте, очень важно узнать разные мнения на эту проблему!