Сценарий
У меня есть вызов класса Model, который представляет собой сложный составной объект многих других объектов разных типов. Вы можете думать об этом как о Car, который имеет Door[], Tire[], Engine, Driver и т.д. И эти объекты, в свою очередь, имеют вспомогательные объекты, такие как Engine имеет SparkPlug, Clutch, Generator и т.д.
У меня есть класс Metrics, который вычисляет некоторые более или менее сложные показатели о Model, по сути выглядит примерно так:
public class Metrics{
private final Model model;
public Metrics(Model aModel){model = aModel;}
public double calculateSimpleMetric1(){...}
public double calculateSimpleMetric2(){...}
public double calculateSimpleMetricN(){...}
public double calculateComplexMetric(){
/* Function that uses calls to multiple calculateSimpleMetricX to
calculate a more complex metric. */
}
}
Я уже написал тесты для функций calculateSimpleMetricX, и каждый из них требует нетривиальных, но управляемых сумм (10-20 строк) установочного кода, чтобы правильно издеваться над связанными частями Model.
Проблема
Из-за неизбежной сложности класса Model stubbing/mocking все зависимости для calculateComplexMetric() создавали бы очень большой и трудный для поддержания теста (более 100 строк кода установки для проверки разумного репрезентативного сценария, а я необходимо протестировать довольно много сценариев).
Моя мысль состоит в том, чтобы создать частичный макет Model и заглушить соответствующие функции calculateSimpleMetricX(), чтобы уменьшить код установки до управляемых 10 или около того строк кода. Поскольку эти функции уже протестированы отдельно.
Однако в документации Mockito указано, что частичные mocks - это запах кода.
Вопрос
Лично я бы разрезал этот угол и просто частично высмеял класс Metrics. Но мне интересно узнать, что такое "пуристский" способ структурирования этого кода и unit test?
Решение
В итоге я разделился на более мелкие, более сплоченные классы, как это было предложено принятым ответом, и используя инъекцию зависимостей:
public class AbstractMetric{
abstract public double calculate();
}
public class ComplexMetric extends AbstractMetric{
private final SimpleMetric1 sm1;
private final SimpleMetric2 sm2;
public ComplexMetric(SimpleMetric1 asm1, SimpleMetric2 asm2){
sm1 = asm1;
sm2 = asm2;
}
@Ovrerride
public double calculate(){
return functionof(sm1.calculate(), sm2.calculate());
}
}
Это можно проверить, и я могу позволить каждой реализации AbstractMetric реализовать кэширование, если им необходимо повысить производительность на более позднем этапе. Я считаю, что добавление метрик к конструктору приемлемо по сравнению с передачей их вместе с функцией расчета в каждом вызове. Даже это может быть скрыто с помощью метода factory.