Сценарий
У меня есть вызов класса 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.