Эта проблема может быть решена, но вы используете неправильный шаблон.Вместо того, чтобы выставлять экземпляр приложения через новый интерфейс, вам нужно создать интерфейс, который полностью заменяет конкретную зависимость.
Что у вас есть
Если я понимаюВаш вопрос правильно, у вас есть запечатанный класс Application, в котором есть несколько методов, которые должна вызывать ваша программа, и у него нет открытого конструктора, только статический метод фабрики.Вот простой пример для обсуждения только с одним методом: SomeMethod()
.
public sealed class Application
{
//private ctor prevents anyone from using new to create this
private Application()
{
}
//Here's the method we want to mock
public void SomeMethod(string input)
{
//Implementation that needs to be stubbed or mocked away for testing purposes
}
//Static factory method
static public Application GetInstance()
{
return new Application();
}
}
То, что вы пытались
То, что вы сделали, может выглядеть так:
interface IApplication
{
Application Application { get; }
}
class ApplicationWrapper : IApplication
{
protected readonly Application _application;
public ApplicationWrapper()
{
_application = Application.GetInstance();
}
public Application Application
{
get { return _application; }
}
}
Так что в вашем основном коде вы делаете это:
var a = new ApplicationWrapper();
a.Application.SomeMethod("Real argument");
Этот подход никогда не будет работать для модульного тестирования, потому что у вас все еще есть прямая зависимость от запечатанного класса Application.Вы только что переместили это.Вам все еще нужно вызвать Application.SomeMethod()
, который является конкретным методом;вы должны зависеть только от интерфейса, а не от чего-то конкретного.
Что бы работало
Теоретически, "правильный" способ сделать это - обернуть все ,Таким образом, вместо того, чтобы выставлять Application
как свойство, вы сохраняете его частным;вместо этого вы предоставляете упакованные версии методов, например:
public interface IApplication
{
void SomeMethod(string input);
}
public class ApplicationWrapper : IApplication
{
protected readonly Application _application;
public ApplicationWrapper()
{
_application = Application.GetInstance();
}
public void SomeMethod(string input)
{
_application.SomeMethod(input);
}
}
Тогда вы бы назвали это следующим образом:
var a = new ApplicationWrapper();
a.SomeMethod("Real argument");
Или в полном классе с DI, это будетвыглядеть следующим образом:
class ClassUnderTest
{
protected readonly IApplication _application; //Injected
public ClassUnderTest(IApplication application)
{
_application = application; //constructor injection
}
public void MethodUnderTest()
{
_application.SomeMethod("Real argument");
}
}
Как выполнить модульное тестирование
В своем модульном тесте вы теперь можете смоделировать приложение IA с новым классом, например
class ApplicationStub : IApplication
{
public string TestResult { get; set; } //Doesn't exist in system under test
public void SomeMethod(string input)
{
this.TestResult = input;
}
}
Уведомлениеэтот класс абсолютно не зависит от Application.Поэтому вам больше не нужно вызывать new
для него или вообще вызывать его фабричный метод.Для модульного тестирования вам просто нужно убедиться, что он вызывается правильно.Вы можете сделать это, передав заглушку и проверив TestResult
после этого:
//Arrange
var stub = new ApplicationStub();
var c = ClassUnderTest(stub);
//Act
c.MethodUnderTest("Test Argument");
//Assert
Assert.AreEqual(stub.TestResult, "Test Argument");
Писать полную оболочку немного сложнее (особенно если в ней много методов), но вы можетегенерировать большую часть этого кода с помощью отражения или сторонних инструментов.И это позволяет вам полностью тестировать модули, что является основной идеей перехода к этому IApplication
интерфейсу для начала.
TLDR:
Вместо
IApplication wrapper = new ApplicationWrapper();
wrapper.Application.SomeMethod();
Вы должны использовать
IApplication wrapper = new ApplicationWrapper();
wrapper.SomeMethod();
, чтобы удалить зависимость от конкретного типа.