Модульное тестирование частных методов в C#



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



//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, и, следовательно, список добавить неудачи.



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

605   8  

8 ответов:

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

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

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

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

можно использовать Класс PrivateObject

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

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

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

enter image description here

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

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

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

в сочетании с sealed class вы можете подойти к такой инкапсуляции, что тестовый метод виден только из 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
}
}

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

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

Вы можете создать класс-оболочку вокруг класса, который содержит собственный метод, который вы хотите проверить. Этот класс-оболочка содержит открытый метод 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();
    }
}

код модульного теста может выглядеть так:

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.

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.

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

Comments

    Ничего не найдено.