Единичное тестирование частных методов в С#

Visual Studio позволяет тестировать отдельные методы с помощью автоматически созданного класса accessor. Я написал тест частного метода, который компилируется успешно, но он не работает во время выполнения. Достаточно минимальная версия кода и теста:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

Ошибка выполнения:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Согласно intellisense - и, следовательно, я предполагаю, что цель-компилятор имеет тип TypeA_Accessor. Но во время выполнения это тип TypeA, и, следовательно, сбой добавления списка.

Есть ли способ остановить эту ошибку? Или, возможно, более вероятно, какие другие советы есть у других людей (я предсказываю, может быть, "не тестировать частные методы" и "не имеют модульных тестов, управляющих состоянием объектов" ).

Ответ 1

Да, не проверяйте частные методы.... Идея unit test состоит в том, чтобы протестировать устройство публичным API.

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

Один совет/инструмент мышления..... Существует идея, что ни один метод никогда не должен быть закрытым. Значит, все методы должны жить в открытом интерфейсе объекта.... если вы чувствуете, что вам нужно сделать его приватным, он, скорее всего, живет на другом объекте.

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

Ответ 2

Вы можете использовать PrivateObject Class

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);

Ответ 3

"Ничего не называют стандартной или лучшей практикой, возможно, это просто популярные мнения".

То же самое верно и для этого обсуждения.

enter image description here

Все зависит от того, что, по вашему мнению, является единицей, если вы считаете, что UNIT - это класс, тогда вы ударите только общественный метод. Если вы считаете, что UNIT - это строки кода, попадающие в частные методы, вы не почувствуете себя виноватыми.

Если вы хотите вызвать частные методы, вы можете использовать класс PrivateObject и вызвать метод invoke. Вы можете посмотреть это indepth youtube видео (http://www.youtube.com/watch?v=Vq6Gcs9LrPQ), в котором показано, как использовать "PrivateObject", а также обсуждается, является ли тестирование частных методов логичным или нет.

Ответ 4

Еще одна мысль заключается в том, чтобы расширить тестирование на "внутренние" классы/методы, придавая больше смысла этому тестированию. Вы можете использовать InternalsVisibleToAttribute в сборке, чтобы представить их отдельным модулям модульного тестирования.

В сочетании с закрытым классом вы можете подойти к такой инкапсуляции, чтобы метод теста был виден только из сборки unittest ваших методов. Учтите, что защищенный метод в закрытом классе де-факто является частным.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

И юнит тест:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}

Ответ 5

Один способ проверить частные методы - это отражение. Это относится и к NUnit и XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);

Ответ 6

Э-э-э... Пришел сюда с точно такой же проблемой: протестировать простой, но ключевой частный метод. После прочтения этой темы, похоже, что "я хочу просверлить эту простую дыру в этом простом куске металла, и я хочу убедиться, что качество соответствует спецификациям", а затем приходит "Хорошо, это не так просто. Прежде всего, нет подходящего инструмента для этого, но вы можете построить обсерваторию гравитационных волн в своем саду. Прочитайте мою статью на http://foobar.brigther-than-einstein.org/ Во-первых, конечно, вы Я должен пройти несколько курсов квантовой физики, затем вам понадобятся тонны ультра-холодного азотистого вещества и, конечно, моя книга, доступная на Amazon "...

Другими словами...

Нет, обо всем по порядку.

Каждый метод, будь то частный, внутренний, защищенный, публичный, должен быть тестируемым. Должен быть способ внедрить такие тесты без лишних слов, как было представлено здесь.

Зачем? Именно из- за архитектурных упоминаний, сделанных некоторыми авторами. Возможно, простое повторение принципов программного обеспечения может устранить некоторые недоразумения.

В этом случае обычными подозреваемыми являются: OCP, SRP и, как всегда, KIS.

Но подожди минутку. Идея сделать все публично доступным является скорее менее политической и некой позицией. Но. Когда дело доходит до кода, даже в тогдашнем Open Source Community, это не догма. Вместо этого "сокрытие" чего-либо является хорошей практикой, чтобы облегчить знакомство с определенным API. Например, вы бы скрывали самые основные расчеты вашего нового рыночного строительного блока цифрового термометра - не для того, чтобы скрыть математику за реальной измеренной кривой для любопытных читателей кода, но чтобы предотвратить зависимость вашего кода от некоторых, возможно, неожиданно важные пользователи, которые не смогли удержаться от использования своего ранее частного, внутреннего, защищенного кода, для реализации своих собственных идей.

О чем я говорю?

private double TranslateMeasurementIntoLinear (двойное фактическое измерение);

Легко объявить Эру Водолея или то, что сейчас называют, но если мой сенсорный датчик получит от 1.0 до 2.0, реализация Translate... может измениться от простого линейного уравнения, которое легко понять и применимый "для всех", к довольно сложным вычислениям, использующим анализ или что-то еще, и поэтому я бы нарушил другой код. Зачем? Потому что они не понимали самих принципов программного кодирования, даже KIS.

Короче говоря: нам нужен простой способ проверить приватные методы - без лишних слов.

Первое: всех с новым годом!

Второе: репетируйте уроки своего архитектора.

Третье: "публичный" модификатор - это религия, а не решение.

Ответ 7

Вы можете создать класс оболочки вокруг класса, который содержит закрытый метод, который вы хотите проверить. Этот класс-оболочка содержит открытый метод Call_MyPrivateFunction и который, в свою очередь, вызывает частную функцию его базового класса.

Обратите внимание, что уровень доступа метода должен быть изменен на [protected]

Пример кода:

public class Order
{
    public int Quantity { get; set; }

    protected bool OrderIsBig ()
    {
        //This is the method we want to test. It needs to be protected in stead of private. Else... no cigar
        return Quantity > 100;
    }
}

//Use this wrapper class in your unit test.
public class FakeOrder : Order
{

    public bool Call_OrderIsBig()
    {
        //This makes the actual call to the protected method "OrderIsBig"
        return OrderIsBig();
    }
}

Код unit test может выглядеть так:

FakeOrder order = new FakeOrder();
order.Quantity = 200;

bool isBig = order.Call_OrderIsBig();   //Make a call to a public method of the FakeOrder class which in turn makes a call to the protected method.

Ответ 8

TL; DR: Извлечь приватный метод в другой класс, проверить на этом классе; подробнее о принципе SRP (принцип единой ответственности)

Похоже, вам нужно извлечь из private метода в другой класс; в этом должно быть public. Вместо того, чтобы пытаться протестировать private метод, вы должны протестировать public метод этого другого класса.

У нас есть следующий сценарий:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Нам нужно проверить логику _someLogic; но кажется, что Class A играет большую роль, чем ему нужно (нарушает принцип SRP); просто рефакторинг на два класса

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

Таким образом, someLogic может быть протестирован на A2; в A1 просто создайте поддельный A2, а затем введите его в конструктор, чтобы проверить, что A2 вызывается для функции с именем someLogic.

Ответ 9

Из книги Эффективная работа с устаревшим кодом:

"Если нам нужно протестировать закрытый метод, мы должны сделать его публичным. обнародование беспокоит нас, в большинстве случаев это означает, что наш класс делать слишком много, и мы должны это исправить. "

По словам автора, способ исправить это - создать новый класс и добавить метод как public.

Автор объясняет далее:

"Хороший дизайн поддается тестированию, а дизайн, который не поддается тестированию, плохой".

Таким образом, в этих пределах ваш единственный реальный вариант - создать метод public либо в текущем, либо в новом классе.

Ответ 10

Другой вариант, который не был упомянут, - это просто создать класс модульного теста в качестве дочернего объекта тестируемого объекта. Пример NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Это позволит легко тестировать приватные и защищенные (но не унаследованные приватные) методы и позволит вам отделить все свои тесты от реального кода, чтобы не развертывать тестовые сборки в рабочей среде. Переключение ваших частных методов на защищенные методы будет приемлемым для многих унаследованных объектов, и это довольно простое изменение.

ОДНАКО...

Хотя это интересный подход к решению проблемы тестирования скрытых методов, я не уверен, что буду выступать за то, чтобы это было правильным решением проблемы во всех случаях. Кажется немного странным проводить внутреннее тестирование объекта, и я подозреваю, что могут быть некоторые сценарии, когда этот подход взорвет вас. (Неизменяемые объекты, например, могут сделать некоторые тесты действительно сложными).

Хотя я упоминаю этот подход, я хотел бы предположить, что это скорее мозговая атака, чем законное решение. Возьми это с зерном соли.

ОБНОВЛЕНИЕ: Я нахожу это действительно весело, что люди голосуют за этот ответ, так как я явно описываю это как плохую идею. Значит ли это, что люди соглашаются со мной? Я так растерялся.....

Ответ 11

В VS 2005/2008 вы можете использовать private accessor для проверки частного участника, но этот способ был исчезать в более поздняя версия VS