Лучший способ модульных тестовых методов, которые вызывают другие методы внутри одного класса

Недавно я обсуждал с некоторыми друзьями, какой из следующих двух методов лучше всего затушить результаты возврата или вызовы методов внутри одного класса из методов внутри одного класса.

Это очень упрощенный пример. На самом деле функции намного сложнее.

Пример:

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 также может быть общедоступной.

30 голосов | спросил tranceru1 26 FebruaryEurope/MoscowbTue, 26 Feb 2013 20:02:57 +0400000000pmTue, 26 Feb 2013 20:02:57 +040013 2013, 20:02:57

3 ответа


26

Отредактировано после первоначального обновления плаката.

Отказ от ответственности: не программист на C # (в основном Java или Ruby). Мой ответ был бы: я бы не стал его проверять вообще, и я не думаю, что вам следует.

Более длинная версия: частные /защищенные методы не являются частями API, они в основном являются вариантами реализации, которые вы можете решить, полностью или полностью отбросить, выбросить, без какого-либо влияния на внешний вид.

Я предполагаю, что у вас есть тест на FunctionA (), который является частью класса, видимого из внешнего мира. Он должен быть единственным, у которого есть контракт для реализации (и который может быть протестирован). У вашего частного /защищенного метода нет контракта на выполнение и /или тестирование.

См. соответствующую дискуссию: https: //stackoverflow .com /вопросы /105007 /должен-я-тест-приват-метода или только-общественные-оны

Следуя комментарию , если FunctionB является общедоступным, я просто проведу тестирование с использованием модульного теста. Вы можете подумать, что тест FunctionA не является полностью «единицей» (поскольку он вызывает FunctionB), но я бы не был слишком обеспокоен этим: если FunctionB-тест работает, но не тест FunctionA, это означает, что проблема заключается не в субдомен FunctionB, который достаточно хорош для меня как дискриминатора.

Если вы действительно хотите полностью отделить два теста, я бы использовал какую-то насмешливую технику, чтобы издеваться над FunctionB при тестировании FunctionA (как правило, возвращать фиксированное известное правильное значение). Мне не хватает знаний о экосистеме C #, чтобы советовать конкретной издевательской библиотеке, но вы можете посмотреть этот вопрос .

ответил Martin 26 FebruaryEurope/MoscowbTue, 26 Feb 2013 20:19:50 +0400000000pmTue, 26 Feb 2013 20:19:50 +040013 2013, 20:19:50
9

Я соглашаюсь с теорией о том, что если функция важна для проверки или важна для замены, то достаточно важно не быть частной деталью реализации тестируемого класса, а быть частью публичной реализации другой .

Итак, если я в сценарии, где у меня

class A 
{
     public B C()
     {
         D();
     }

     private E D();
     {
         // i actually want to control what this produces when I test C()
         // or this is important enough to test on its own
         // and, typically, both of the above
     }
}

Тогда я собираюсь реорганизовать.

class A 
{
     ICollaborator collaborator;

     public A(ICollaborator collaborator)
     {
         this.collaborator = collaborator;
     }

     public B C()
     {
         collaborator.D();
     }
}

Теперь у меня есть сценарий, где D () независимо тестируется и полностью заменяется.

В качестве средства организации мой сотрудник может не проживать на одном и том же уровне пространства имен. Например, если A находится в FooCorp.BLL, то мой соавтор может быть другим слоем глубоко, как в FooCorp.BLL.Collaborators (или независимо от названия). Мой коллаборатор может быть заметен только внутри сборки с помощью модификатора доступа internal, который затем я также выставляю на мой проект тестирования модулей ) с помощью атрибута сборки InternalsVisibleTo. Вывод заключается в том, что вы все равно можете поддерживать свой API в чистоте, насколько это необходимо, при создании проверяемого кода.

ответил Anthony Pegram 27 FebruaryEurope/MoscowbWed, 27 Feb 2013 19:54:43 +0400000000pmWed, 27 Feb 2013 19:54:43 +040013 2013, 19:54:43
1

Я лично использую Method1, то есть все методы в Action или Funcs, так как это значительно улучшило для меня проверку кода. Как и в любом решении, существуют плюсы и минусы этого подхода:

Доводы

  1. Позволяет использовать простую структуру кода, где использование шаблонов кода только для модульного тестирования может добавить дополнительную сложность.
  2. Позволяет закрывать классы и устранять виртуальные методы, которые требуются популярными фальшивыми фреймворками, такими как Moq. Уплотнение классов и устранение виртуальных методов делает их кандидатами для встраивания и других оптимизаций компилятора. ( https://msdn.microsoft.com/en-us/library/ff647802. aspx )
  3. Упрощает тестируемость, поскольку замена реализации Func /Action в модульном тесте так же просто, как просто присвоение нового значения Func /Action
  4. Также позволяет тестировать, если статический Func был вызван из другого метода, поскольку статические методы не могут быть изделены.
  5. Легко реорганизовать существующие методы на Funcs /Actions с момента синтаксиса для вызова метода на сайте вызова остается неизменным. (Для времен когда вы не можете реорганизовать метод в Func /Action, см. cons)

против

  1. Funcs /Actions не могут использоваться, если ваш класс может быть выведен, поскольку Funcs /Actions не имеют путей наследования, таких как методы
  2. Невозможно использовать параметры по умолчанию. Создание Func с параметрами по умолчанию влечет за собой создание нового делегата, который может привести к запутыванию кода в зависимости от варианта использования
  3. Невозможно использовать именованный синтаксис параметров для вызова методов вроде something (firstName: "S", lastName: "K")
  4. Самый большой Con - это то, что у вас нет доступа к «этой» ссылке в Funcs и Actions, поэтому любые зависимости от класса должны быть явно переданы в качестве параметров. Это хорошо, поскольку вы знаете все зависимости, но плохо, если у вас есть много свойств, от которых зависит ваш Func. Ваш пробег будет зависеть от вашего варианта использования.

Итак, чтобы подвести итог, использование тестов Funcs и Actions для Unit отлично, если вы знаете, что ваши классы никогда не будут переопределены.

Кроме того, я обычно не создаю свойства для Funcs, но строю их непосредственно как таковые

public class MyClass
{
     public Func<int> FunctionB = () => new Random().Next();

     public bool FunctionA()
     {
         return FunctionB() % 2 == 0;
     }
}

Надеюсь, это поможет!

ответил Salman Hasrat Khan 9 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowWed, 09 Sep 2015 18:38:04 +0300 2015, 18:38:04

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132