Недавно я обсуждал с некоторыми друзьями, какой из следующих двух методов лучше всего заглушить, чтобы вернуть результаты или вызовы методов внутри одного класса из методов внутри того же класса.
Это очень упрощенный пример. На самом деле функции намного сложнее.
Пример:
public class MyClass
{
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected int FunctionB()
{
return new Random().Next();
}
}
Таким образом, чтобы проверить это у нас есть 2 метода.
Метод 1: Используйте функции и действия, чтобы заменить функциональность методов. Пример:
public class MyClass
{
public Func<int> FunctionB { get; set; }
public MyClass()
{
FunctionB = FunctionBImpl;
}
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected int FunctionBImpl()
{
return new Random().Next();
}
}
[TestClass]
public class MyClassTests
{
private MyClass _subject;
[TestInitialize]
public void Initialize()
{
_subject = new MyClass();
}
[TestMethod]
public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
{
_subject.FunctionB = () => 1;
var result = _subject.FunctionA();
Assert.IsFalse(result);
}
}
Метод 2: Сделайте члены виртуальными, производным классом и в производном классе используйте Функции и Действия для замены функциональности. Пример:
public class MyClass
{
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected virtual int FunctionB()
{
return new Random().Next();
}
}
public class TestableMyClass
{
public Func<int> FunctionBFunc { get; set; }
public MyClass()
{
FunctionBFunc = base.FunctionB;
}
protected override int FunctionB()
{
return FunctionBFunc();
}
}
[TestClass]
public class MyClassTests
{
private TestableMyClass _subject;
[TestInitialize]
public void Initialize()
{
_subject = new TestableMyClass();
}
[TestMethod]
public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
{
_subject.FunctionBFunc = () => 1;
var result = _subject.FunctionA();
Assert.IsFalse(result);
}
}
Я хочу знать, что лучше, а также почему?
Обновление: ПРИМЕЧАНИЕ: функция B также может быть общедоступной
c#
design
unit-testing
tranceru1
источник
источник
FunctionA
возвращает bool, но устанавливает только локальную переменнуюx
и ничего не возвращает.public static
но в другом классе.FunctionB
сломан по дизайну.new Random().Next()
почти всегда неправильно. Вы должны ввести экземплярRandom
. (Random
также плохо спроектированный класс, который может вызвать несколько дополнительных проблем)Ответы:
Отредактировано после оригинального обновления постера.
Отказ от ответственности: не программист C # (в основном Java или Ruby). Мой ответ будет таким: я бы вообще не проверял это, и я не думаю, что вы должны это делать.
Более длинная версия: частные / защищенные методы не являются частью API, они в основном являются вариантами реализации, которые вы можете решить просмотреть, обновить или полностью исключить, не оказывая никакого влияния извне.
Я полагаю, у вас есть тест для FunctionA (), который является частью класса, видимого из внешнего мира. Он должен быть единственным, у которого есть контракт на реализацию (и который можно было бы проверить). Ваш частный / защищенный метод не имеет контракта для выполнения и / или тестирования.
См. Соответствующее обсуждение там: https://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones
После комментария , если FunctionB общедоступен, я просто протестирую оба, используя модульный тест. Вы можете подумать, что тест FunctionA не является полностью «модульным» (так как он вызывает FunctionB), но меня это не слишком беспокоит: если тест FunctionB работает, но не тест FunctionA, это означает, что проблема не в поддомен FunctionB, что достаточно для меня как дискриминатора.
Если вы действительно хотите иметь возможность полностью разделить два теста, я бы использовал некоторую технику насмешки, чтобы смоделировать FunctionB при тестировании FunctionA (как правило, возвращать фиксированное известное правильное значение). Мне не хватает знаний об экосистеме C #, чтобы посоветовать конкретную библиотеку-макет, но вы можете посмотреть на этот вопрос .
источник
MyClass
и переопределение метода функциональностью, которую вы хотите оцепить. Также было бы неплохо обновить ваш вопрос, чтобы включитьFunctionB
его в общедоступный.protected
методы являются частью открытой поверхности класса, если только вы не гарантируете, что в разных сборках не может быть реализаций вашего класса.Я согласен с теорией о том, что если функция важна для тестирования или важна для замены, достаточно важно не быть частной реализацией подробностей тестируемого класса, но быть открытой деталью реализации другого класса.
Так что, если я нахожусь в сценарии, где у меня есть
Тогда я собираюсь рефакторинг.
Теперь у меня есть сценарий, в котором D () независимо тестируется и полностью заменяется.
Как средство организации, мой соавтор может не жить на том же уровне пространства имен. Например, если
A
есть в FooCorp.BLL, то мой соавтор может быть другого уровня, как в FooCorp.BLL.Collaborators (или как угодно подходящее имя). Кроме того, мой соавтор может быть виден только внутри сборки черезinternal
модификатор доступа, который я затем также предоставлю для моего проекта модульного тестирования черезInternalsVisibleTo
атрибут сборки. Вывод заключается в том, что вы можете поддерживать чистоту своего API в отношении вызывающих абонентов, создавая проверяемый код.источник
Добавляя к тому, на что указывает Мартин,
Если ваш метод закрыт / защищен - не проверяйте его. Он является внутренним для класса и не должен быть доступен вне класса.
В обоих подходах, которые вы упоминаете, у меня есть эти проблемы -
Метод 1 - Это фактически изменяет поведение тестируемого класса в тесте.
Метод 2 - Это на самом деле не тестирует производственный код, а тестирует другую реализацию.
В поставленной задаче я вижу, что единственная логика A - видеть, является ли выход FunctionB четным. Несмотря на иллюстрацию, FunctionB дает значение Random, которое сложно протестировать.
Я ожидаю реалистичного сценария, в котором мы можем настроить MyClass таким образом, чтобы мы знали, что вернет FunctionB. Тогда наш ожидаемый результат известен, мы можем вызвать FunctionA и подтвердить фактический результат.
источник
protected
почти так же, какpublic
. Толькоprivate
иinternal
есть детали реализации.internal protected
, использовать частный помощник отражения или создать производный класс в своем тестовом проекте.Я лично использую Method1, то есть превращаю все методы в Actions или Funcs, поскольку это значительно улучшило тестируемость кода для меня. Как и у любого решения, у этого подхода есть свои плюсы и минусы:
Pros
Cons
Итак, подведем итог: использование Funcs и Actions for Unit test - это замечательно, если вы знаете, что ваши классы никогда не будут переопределены.
Кроме того, я обычно не создаю свойства для функций, а вставляю их непосредственно как таковые
Надеюсь это поможет!
источник
Использование Mock возможно. Nuget: https://www.nuget.org/packages/moq/
И поверьте мне, это довольно просто и имеет смысл.
Для макета нужен виртуальный метод для переопределения.
источник