Вспомогательные классы обычно являются статическими классами, поэтому вам не нужно создавать их экземпляры. Создание экземпляра управляемого объекта .NET (особенно вспомогательных классов) не требует больших затрат, это просто вопрос удобства.
Крайне заманчиво просто собрать статический класс с минимальным количеством вспомогательных методов и выполнить работу. У них есть свое место в коде, и их можно использовать, особенно когда имеется детерминированный ввод/вывод. например ComputeHash строки, найти среднее число и т. д.
Но единственная причина, по которой статические классы не рекомендуются, заключается в том, что они обычно мешают модульному тестированию и создают всевозможные проблемы. (Подделки, кроты, приватные аксессоры и т.д.)
Интерфейсный подход даже для вспомогательных классов помогает при модульном тестировании всего кода. Это особенно верно для больших проектов, включающих рабочие процессы, в которых статические вспомогательные методы являются лишь частью рабочего процесса.
например Предположим, вам нужно проверить, является ли текущий год високосным. Заманчиво написать быстрый статический метод.
public static class DateHelper
{
public static bool IsLeapYear()
{
var currentDate = DateTime.UtcNow;
// check if currentDate's year is a leap year using some unicorn logic
return true; // or false
}
}
и если этот метод используется в вашем коде где-то вроде:
public class Birthday
{
public int GetLeapYearDaysData()
{
// some self-logic..
// now call our static method
var isLeapYear = DateHelper.IsLeapYear();
// based on this value, you might return 100 or 200.
if (isLeapYear)
{
return 100;
}
return 200;
}
}
Теперь, если вы попытаетесь выполнить модульное тестирование этого метода в открытом доступе int GetLeapYearDaysData(), у вас могут возникнуть проблемы, поскольку возвращаемое значение неопределенно. т. е. зависит от текущего года, и не рекомендуется, чтобы модульные тесты вели себя непредсказуемо/ ухудшаться с течением времени.
// this unit test is flaky
[Test]
public void TestGetLeapYearDaysData()
{
var expected = 100;
// we don't know if this method will return 100 or 200.
var actual = new Birthday().GetLeapYearDaysData();
Assert.AreEqual(expected, actual);
}
Вышеупомянутая проблема возникает из-за того, что мы не можем контролировать/издеваться над методом IsLeapYear() в приведенном выше коде. так что мы в его власти.
Теперь представьте следующую конструкцию:
public interface IDateHelper
{
bool IsLeapYear();
}
public class DateHelper : IDateHelper
{
public bool IsLeapYear()
{
var currentDate = DateTime.UtcNow;
// check if currentDate's year is a leap year using some unicorn logic
return true; // or false
}
}
Теперь в наш класс рождения можно внедрить хелпер:
public class Birthday
{
private IDateHelper _dateHelper;
// any caller can inject their own version of dateHelper.
public Birthday(IDateHelper dateHelper)
{
this._dateHelper = dateHelper;
}
public int GetLeapYearDaysData()
{
// some self-logic..
// now call our injected helper's method.
var isLeapYear = this._dateHelper.IsLeapYear();
// based on this value, you might return 100 or 200.
if (isLeapYear)
{
return 100;
}
return 200;
}
}
// now see how are unit tests can be more robust and reliable
// this unit test is more robust
[Test]
public void TestGetLeapYearDaysData()
{
var expected = 100;
// use any mocking framework or stubbed class
// to reliably tell the unit test that 100 needs to be returned.
var mockDateHelper = new Mock<IDateHelper>();
// make the mock helper return true for leap year check.
// we're no longer at the mercy of current date time.
mockDateHelper.Setup(m=>m.IsLeapYear()).Returns(true);
// inject this mock DateHelper in our BirthDay class
// we know for sure the value that'll be returned.
var actual = new Birthday(mockDateHelper).GetLeapYearDaysData();
Assert.AreEqual(expected, actual);
}
Как видите, когда вспомогательные методы были основаны на интерфейсе, их было легко тестировать. В ходе большого проекта множество таких небольших статических методов в конечном итоге приводят к узким местам при тестировании ключевых функциональных потоков.
Поэтому стоит заранее знать об этой ловушке и делать дополнительные инвестиции заранее. В основном определите, какие классы/методы должны быть статическими, а какие не должны быть.
person
Raja Nadar
schedule
03.04.2014