Протестируйте открытый метод, который вызывает приватный метод, используя NUnit
У меня есть открытый метод в классе, который внутренне вызывает определенный частный метод в этом классе. Это выглядит примерно так:
public class MyClass : IMyClassInterface
{
public List<int> MyMethod(int a, int b)
{
MyPrivateMethod(a, b, ref varList, ref someVal);
}
private void MyPrivateMethod(int a, int b, ref List<int> varList, ref double someval)
{
}
}
Теперь я хочу протестировать этот публичный метод с использованием NUnit. Я использую NMock 2.0 для насмешек. Как мне это сделать? Поскольку он внутренне вызывает этот закрытый метод, который я не хочу обнародовать. Или есть способ сделать это, если я вместо этого переключу закрытый метод на защищенный?
3 ответа
Теперь я хочу протестировать этот публичный метод (...)
Это замечательно. Это то, что вы должны делать. Забудьте о внутренних деталях на мгновение. С точки зрения публичного метода, есть ли разница между этими двумя фрагментами?
// Your current implementation
public void MyMethod(int a, int b)
{
MyPrivateMethod(a, b);
}
private void MyPrivateMethod(int a, int b)
{
var c = a + b;
// some more code
}
// Private method inlined
public void MyMethod(int a, int b)
{
var c = a + b;
// some more code
}
Кто бы ни звонил (публично) MyMethod
не сможет заметить никакой разницы между этими двумя. Конечный результат такой же. Неважно, есть ли вызов частного метода, потому что для публичного API это не имеет значения. Вы можете встроить указанный частный метод, сделать так, чтобы он исчез навсегда, и с общественной точки зрения ничего не изменится. Конечный результат - единственное, что важно. Вы тестируете конечный результат, наблюдаемый потребителем кода. Не какой-то внутренний бред.
Важная реализация это:
Правильно разработанный код SOLID никогда не поставит вас в положение, которое потребует от вас частной насмешки. Источник проблемы? Плохой дизайн.
Источник: Как издеваться над частным методом - решения
Ага. Грустно, но правда, ваш дизайн не так уж велик. В зависимости от того, хотите ли вы изменить это или нет, вы можете выбрать несколько подходов:
- не пытайтесь высмеивать личные детали, сосредоточьтесь на общедоступном API (не помогает с дизайном)
- извлекать приватный метод в класс, вводить зависимости (долгосрочное решение, улучшает дизайн и делает код легко тестируемым)
- сделать закрытый метод защищенным, переопределить в тесте, как предложено в другом ответе (не помогает с проблемой разработки, может не дать полезного теста)
Что бы вы ни выбрали, я оставлю до вас. Тем не менее, я еще раз подчеркну это - частный метод насмешек - это не модульное тестирование, не проблема библиотек или инструментов - это проблема проектирования, и он лучше всего решается как таковой.
С другой стороны, (если вы можете) не используйте NMock2. Это библиотека с последними изменениями 2009 года. Это как 30-летний автомобиль, который последний раз обслуживали 15 лет назад. В настоящее время есть намного лучшие (FakeItEasy, Moq, NSubstitute).
Да, "хитрость" состоит в том, чтобы использовать защищенный вместо частного, а затем наследовать класс и запустить тест нового класса, который выполняет защищенный метод. Это очень распространенный способ сделать тестовый и устаревший код.
[TestClass]
public class UnitTest1
{
[TestMethod]
public void TestMethod1()
{
MyClassTestWrapped t = new MyClassTestWrapped();
Assert.IsTrue(t.MyPrivateMethod(...));
Assert.IsTrue(t.MyMethod(...));
MockFactory _factory = new MockFactory();
Mock<MyClassTestWrapped> mock;
mock = _factory.CreateMock<MyClass>();
mock.Expects.One.MethodWith(d => d.MyPrivateMethod()); // do the nmock magic here
}
}
public class MyClass : IMyClassInterface
{
public List<int> MyMethod(int a, int b)
{
MyPrivateMethod(a, b, ref varList, ref someVal);
}
// here change to protected
protected void MyPrivateMethod(int a, int b, ref List<int> varList, ref double someval)
{
}
}
public interface IMyClassInterface
{
}
public class MyClassTestWrapped : MyClass
{
public List<int> MyMethod(int a, int b)
{
base.MyMethod(a, b);
}
public List<int> MyPrivateMethod(int a, int b,ref List<int> varList, ref double someval)
{
base.MyPrivateMethod(a, b, ref varList, ref someval);
}
}
В то время как в настоящее время вам необходимо провести рефакторинг вашего кода, чтобы потерять приватный модификатор (обертки и что-то еще), вы можете сделать это довольно легко с помощью таких инструментов, как Typemock Isolator.
Я добавил код в ваш пример, чтобы написать тест:
public class MyClass
{
public List<int> MyMethod(int a, int b)
{
List<int> varList = new List<int>();
double someVal = 0;
MyPrivateMethod(a, b, ref varList, ref someVal);
return varList;
}
private void MyPrivateMethod(int a, int b, ref List<int> varList, ref double someval)
{
}
}
При таком прямолинейном подходе вы просто фальсифицируете закрытый метод, как он есть в вашем коде (без изменений в производстве), даже если это параметры ref:
[Test]
public void TestMethod1()
{
//Arrange
var myClass = new MyClass();
var expectedVarList = new List<int> {1,2,3};
Isolate.NonPublic.WhenCalled(myClass, "MyPrivateMethod")
.AssignRefOut(expectedVarList, 0.0)
.IgnoreCall();
//Act
var resultVarList = myClass.MyMethod(0, 0);
//Assert
CollectionAssert.AreEqual(expectedVarList, resultVarList);
}