Я пытаюсь выполнить код модульного тестирования, который использует API, поэтому я пытаюсь отделиться.
Я создал интерфейс для класса «Приложение» внутри API, который запечатан.
Затем я создал класс, который использует интерфейс, у которого есть один метод, возвращающий объект типа «Приложение».
Вот где у меня возникают проблемы: в своем модульном тесте я пытаюсь создать объект «Приложение», чтобы проверить правильность возвращаемого значения. Однако класс «Приложение» не имеет конструкторов, ничего публичного или частного (я проверил с помощью отражения). Объект создается путем вызова статического Application.Connect (AnotherTypeFromAPI arg), который возвращает объект Application.
Как мне вернуть фальшивый объект, который я не могу создать?
appMock.Connect(arg).Returns("How do I return an Application object here?"));
Или я ошибаюсь в отношении кода модульного тестирования, который полагается на API? Весь API основан на типе «Приложение», поэтому, если я не могу его подделать, я еще не уверен, как я могу заглушить или издеваться над другими методами, которые мне нужны.
Я использую C#, NUnit, NSUbstitute.





Обычно вы не имитируете или подделываете статические методы, такие как Application.Connect. Просто разделите тестируемый код так, чтобы он принимал уже созданный объект IApplication.
Эту проблему можно решить, но вы используете неправильный шаблон. Вместо того, чтобы открывать экземпляр приложения через новый интерфейс, вам нужно создать интерфейс, который полностью заменяет является конкретной зависимостью.
Если я правильно понимаю ваш вопрос, у вас есть запечатанный класс приложения, у которого есть некоторые методы, которые ваша программа должна иметь возможность вызывать, и у него нет общедоступного конструктора, только статический фабричный метод. Вот простой пример для обсуждения, только с одним методом, 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");
}
}
В модульном тесте теперь вы можете имитировать IApplication с новым классом, например
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 для начала.
Вместо
IApplication wrapper = new ApplicationWrapper();
wrapper.Application.SomeMethod();
ты должен использовать
IApplication wrapper = new ApplicationWrapper();
wrapper.SomeMethod();
убрать зависимость от конкретного типа.
Спасибо за пример, я начал этот путь, но метод принимает другой объект из API в качестве аргумента, который также использует фабричный метод для создания экземпляра объекта. И это приближалось к тому моменту, когда мне пришлось бы обернуть большую часть API. Также я понял, что начинаю тестировать сам API, а не свой код.
Я отметил это как правильный ответ, потому что изначально я шел по пути тестирования API, а это не мой код. Я создал свой собственный класс, использующий API. Таким образом я могу заглушить методы в классе, который я создал, чтобы проверить, произошел ли вызов сторонней библиотеки. Разделение позволяет мне заглушить API, чтобы я мог протестировать свой код.