Ложный объект, созданный методом, а не конструктором - PullRequest
0 голосов
/ 22 мая 2018

Я пытаюсь выполнить модульное тестирование кода, который использует API, поэтому я пытаюсь отделить его.

Я создал интерфейс для класса "Application" внутри API, который запечатан.

Затем я создал класс, который использует интерфейс, в котором есть один метод, который возвращает объект типа «Приложение».

Вот где у меня возникают проблемы, в своем модульном тесте я пытаюсь создать «Приложение».msgstr "объект для проверки правильности возвращаемого значения.Однако у класса «Application» нет конструкторов, ничего общего или частного (я проверил с помощью отражения).Объект создается путем вызова статического Application.Connect (AnotherTypeFromAPI arg), который возвращает объект Application.

Как вернуть поддельный объект, который я не могу создать?

appMock.Connect(arg).Returns("How do I return an Application object here?"));

ИлиЯ поступил об этом неправильно в отношении кода модульного тестирования, который опирается на API?Весь API основывается на типе «Application», поэтому, если я не могу его подделать, я пока не уверен, каким образом я могу заглушить или высмеять другие необходимые мне методы.

Я использую C #, NUnit, NSUbstitute.

Ответы [ 2 ]

0 голосов
/ 22 мая 2018

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

Что у вас есть

Если я понимаюВаш вопрос правильно, у вас есть запечатанный класс 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();

, чтобы удалить зависимость от конкретного типа.

0 голосов
/ 22 мая 2018

Обычно вы не фальсифицируете или подделываете статические методы, такие как Application.Connect.Просто разбейте тестируемый код так, чтобы он принимал уже созданный объект IApplication.

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