Как бы я рефакторинг статического метода, чтобы я мог проверить свой метод?
Я знаю, что не могу использовать Moq для моделирования статического вызова метода внутри тестируемого метода, так что мне нужно сделать, чтобы реорганизовать метод, чтобы я мог его протестировать? У меня также есть метод, вызывающий метод базового класса, нужно ли мне его реорганизовать, и если да, то как? Я не хочу использовать MS.Fakes или TypeMocks и создавать шиммы, я бы предпочел рефакторинг и писать твердый код!
public override DateTime ResolveDate(ISeries comparisonSeries, DateTime targetDate)
{
if (comparisonSeries == null)
{
throw new ArgumentNullException("comparisonSeries");
}
switch (comparisonSeries.Key)
{
case SeriesKey.R1:
case SeriesKey.R2:
case SeriesKey.R3:
case SeriesKey.R4:
case SeriesKey.R5:
return DateHelper.PreviousOrCurrentQuarterEnd(targetDate);
}
return base.ResolveDate(comparisonSeries, targetDate);
}
[TestMethod]
public void SomeTestMethod()
{
var mockIAppCache = new Mock<IAppCache>();
var mockISeries = new Mock<ISeries>();
ReportFR2 report = new ReportFR2(SeriesKey.FR2, mockIAppCache);
DateTime resolvedDate = report.ResolveDate(mockISeries, DateTime.Now);
//Assert.AreEqual("something", "something");
}
2 ответа
Глядя на вышесказанное, вы можете проверить три основных условия:
Когда серия сравнения равна нулю
Когда ключ серии сравнения - R1:R5
Когда ключ серии сравнения не нулевой и ничего кроме R1:R5
В условии 1 вы можете довольно легко покрыть это тестом.
В условии 2, когда это R1:R5, то, где это появляется, это поражает ваш статический метод.
- С точки зрения вашего метода ResolveDate, вы все равно заботитесь о значении, когда оно попадает в эту ветку.
- Было бы неплохо доказать, что R1:R5 вызывают статический помощник, но, как упоминалось в комментариях, единственный способ сделать это - заключить в DateHelper интерфейс и передать его конструктору этого класса. Это может не стоить усилий.
- Если вы решите не делать этого, я предлагаю вам пройти тест, относящийся к этой ветке, а затем написать еще один набор тестов, нацеленный на вашу
DateHelper.PreviousOrCurrentQuarterEnd()
функционировать напрямую, поражая все крайние случаи. Это поможет определить, какой код является виновником в случае сбоя.
То же самое можно сказать и для условия 3 как условия 2. Несмотря на то, что оно находится в вашем базовом классе, оно все еще является допустимой логической ветвью.
Опять же, вам будет трудно доказать, что он называется вашим базовым классом,
Но все еще актуально проверить результат.
Итак, я думаю, что у вас есть четыре набора тестов, которые вы можете написать для начала, а затем, пройдя эти тесты, вы можете решить, хотите ли вы провести рефакторинг своего служебного класса. DateHelper.
Я думаю, вы скажете нет:-D
Учитывая класс ReportRF2 и нулевой ряд сравнения
При вызове report.ResolveDate
Это должно сгенерировать исключение ссылки Null. использование
`Assert.Throws( () => report.ResolveDate( null, DateTime.Now));
Имеется класс ReportRF2 и ключ серии в наборе R1:R5
При определении даты для границы X (например, 1/1/0101) она должна быть равна y;
Когда ".." для..., ...; (повторите для ваших краевых / граничных случаев; рассмотрите возможность использования Data Driven)
Учитывая класс ReportRF2 и ключ серии NOT в наборе R1:R5 и NOT NULL
- при определении даты для границы X, ... аналогично #2, но, вероятно, отличаются ожидаемые результаты.
Учитывая статический служебный класс DateHelper
- при вычислении PreviousOrCurrentQuarterEnd() и дате X, она должна быть равна y,
- похож на крайние случаи в #2 выше.
Это даст вам представление об ожидаемых результатах и скажет вам, что неудачи связаны с вашим ResolveDate
метод или ваш DateHelper.PreviousOrCurrentQuarterEnd()
метод. Он может не изолировать столько, сколько хотелось бы пуристу, но пока вы покрываете свои крайние случаи и свои удачные пути, это доказывает, что ваше приложение функционирует так, как запланировано (пока эти тесты проходят).
На самом деле он не позволяет вам утверждать, что используется определенное поведение, кроме случаев, когда ряд сравнения равен нулю, поэтому вам решать, нужна ли вам эта проверка. Но у вас все еще должно быть доказательство того, что когда вводятся определенные значения или диапазоны, вы получаете предсказуемые результаты, и это добавляет некоторую ценность.
Просто чтобы добавить к хорошему ответу @Damon, завернув DateHelper
С интерфейсом это можно сделать довольно легко:
public interface IDateHelper
{
DateTime PreviousOrCurrentQuarterEnd(DateTime targetDate);
}
Как уже отмечалось, вам понадобится экземпляр класса, который реализует этот интерфейс, но только для вашего производственного кода, так как модульные тесты будут просто использовать Mock<IDateHelper
:
public class InstanceDateHelper : IDateHelper
{
public DateTime PreviousOrCurrentQuarterEnd(DateTime targetDate)
{
return DateTimeHelper.PreviousOrCurrentQuarterEnd(targetDate);
}
}
Вуаля, теперь вы можете издеваться над IDateHelper
интерфейс, и у вас есть реализация, которая использует существующий статический код.
Я использовал эту технику упаковки для написания модульных тестов для метода, который запускает новый Process
Таким образом, я мог протестировать метод, фактически не запуская полноценный процесс, когда все, что мне нужно было знать - вызовет ли тестируемый метод .Start(StartInfo)
без побочных эффектов.
Изобразите этот метод:
public bool StartProcessAndWaitForExit(ProcessStartInfo info)
{
var process = Process.Start(info); // test-hindering static method call
//...
}
Единственное изменение, которое я должен был сделать, было это:
public bool StartProcessAndWaitForExit(IProcessWrapper process, ProcessStartInfo info)
{
var process = process.Start(info); // injected wrapper interface makes method testable
//...
}
Если ResolveDate
это единственный метод в вашем классе, который требует IDateHelper
впрыскивать его как параметр метода нормально; если у вас есть куча методов, которые также нуждаются в этом, вставьте это в качестве аргумента конструктора и сделайте private readonly IDateHelper _helper;
поле (инициализируется в конструкторе) - лучший путь.