Поделиться через


Использование заглушек для изоляции частей приложений друг от друга при модульном тестировании

Типы заглушки одну из 2 технологий, которые платформа Microsoft Fakes предоставляет, позволяя легко определить компонент тестируется от других компонентов, которые вызывает.Заглушка небольшую часть кода, которая заменяет другого компонента во время тестирования.Преимущество использования заглушки, оно возвращает последовательные результатов теста, что упрощает записи.Можно выполнять тесты и даже если другие компоненты не работают при.

Для просмотра руководства и быстрого запуска в фальшивкам см. в разделе Изоляция тестируемого кода с помощью Microsoft Fakes.

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

В схеме, компонент StockAnalyzer одно не требуется выполнить.Обычно он используется другой компонент, RealStockFeed.Но RealStockFeed возвращает различные результаты при каждом его методы вызываются, что усложняет выполнение StockAnalyzer.Во время тестирования, он не является взаимозаменяемым с другим классом, StubStockFeed.

Классы Real и Stub соответствуют одному интерфейсу.

Поскольку заглушки используют возможности в структуре код таким образом, обычно используется заглушки для выявления одну часть приложения от других.Определить его от других сборок, не относящемся для элемента управления, например, System.dll, обычно использовал бы оболочки.Дополнительные сведения см. в разделе Использование оболочек совместимости для изоляции приложения от других сборок при модульном тестировании.

Требования

  • Visual Studio Ultimate

Содержание раздела

Использование заглушки

Hh549174.collapse_all(ru-ru,VS.110).gifРазработка для внедрения зависимости

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

Начнем с в этом примере мотировать " в схеме.Класс StockAnalyzer считывает цена акци и создает некоторые интересные результаты.Он имеет некоторые открытые методы, которые не нужно выполнить.Чтобы обеспечить простоту, рассмотрим следующий поиск только один из этих методов, очень простой одно то отчетов существующую цена указанной общей папки.Не требуется создавать модульный тест этого метода.Здесь первый вариант теста.

        [TestMethod]
        public void TestMethod1()
        {
            // Arrange:
            var analyzer = new StockAnalyzer();
            // Act:
            var result = analyzer.GetContosoPrice();
            // Assert:
            Assert.AreEqual(123, result); // Why 123?
        }
    <TestMethod()> Public Sub TestMethod1()
        ' Arrange:
        Dim analyzer = New StockAnalyzer()
        ' Act:
        Dim result = analyzer.GetContosoPrice()
        ' Assert:
        Assert.AreEqual(123, result) ' Why 123?
    End Sub

Одна проблема с данным тестом немедленно очевидна. цена акци различаются, поэтому утверждение обычно завершится с ошибкой.

Другая проблема может быть то, что компонент StockFeed, который используется StockAnalyzer, находится в разработке.Здесь первый вариант кода тестируемого метода:

        public int GetContosoPrice()
        {
            var stockFeed = new StockFeed(); // NOT RECOMMENDED
            return stockFeed.GetSharePrice("COOO");
        }
    Public Function GetContosoPrice()
        Dim stockFeed = New StockFeed() ' NOT RECOMMENDED
        Return stockFeed.GetSharePrice("COOO")
    End Function

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

Внедрение интерфейса рассматриваются обе эти проблемы.

Внедрение интерфейса применяет следующие правила:

  • Код любого компонента приложения никогда не явно ссылаться на класс в компонентах, или в объявлении или в инструкцию new.Вместо этого, переменные и параметры необходимо объявлять с интерфейсами.Экземпляры компонента должны быть созданы только контейнером компонента.

    "Компонентом" в данном случае рекомендуется значим класс, или группу в составе классы, создании и обновлении вместе.Обычно компонент код в одном проекте Visual Studio.Менее важно отделить классы в один компонент, поскольку они обновлены одновременно.

    Также не настолько необходимо отделить компоненты из классов относительно стабильной платформы, таких как System.dll.Интерфейсы записи для всех этих классов будет создан суматоху код.

Код StockAnalyzer следовательно может быть улучшен путем разделения его из StockFeed с помощью интерфейса следующим образом:

    public interface IStockFeed
    {
        int GetSharePrice(string company);
    }

    public class StockAnalyzer
    {
        private IStockFeed stockFeed;
        public Analyzer(IStockFeed feed)
        {
            stockFeed = feed;
        }
        public int GetContosoPrice()
        {
            return stockFeed.GetSharePrice("COOO");
        }
    }
Public Interface IStockFeed
    Function GetSharePrice(company As String) As Integer
End Interface

Public Class StockAnalyzer
    ' StockAnalyzer can be connected to any IStockFeed:
    Private stockFeed As IStockFeed
    Public Sub New(feed As IStockFeed)
        stockFeed = feed
    End Sub  
    Public Function GetContosoPrice()
        Return stockFeed.GetSharePrice("COOO")
    End Function
End Class

В этом примере, StockAnalyzer передается реализацией IStockFeed при построении.В приложении ", код инициализации, выполняет подключение:

analyzer = new StockAnalyzer(new StockFeed())

Более гибкие способы поиска это подключение.Например, StockAnalyzer может принять объект фабрики, который может создать различные реализации IStockFeed в различных условиях.

Hh549174.collapse_all(ru-ru,VS.110).gifСоздание заглушки

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

Можно просто написать заглушки классы обычным способом.Но Microsoft Fakes предусмотрено более динамическим способом создания наиболее подходящую создается для каждого теста.

Для использования заглушек необходимо сначала создать типы заглушки из определения интерфейса.

Добавление сборки фальшивок

  1. В обозревателе решений, разверните узел Ссылки проекта модульного теста.

    • При работе в Visual Basic, необходимо выбрать Показать все файлы на панели инструментов обозревателя решений, чтобы узнать, представляют собой список.
  2. Выделите сборку, которая содержит определения интерфейса, для которых необходимо создать заглушки.

  3. В контекстном меню выберите команду Добавить сборку имитаций.

Hh549174.collapse_all(ru-ru,VS.110).gifЗапись теста с заглушками

[TestClass]
class TestStockAnalyzer
{
    [TestMethod]
    public void TestContosoStockPrice()
    {
      // Arrange:

        // Create the fake stockFeed:
        IStockFeed stockFeed = 
             new StockAnalysis.Fakes.StubIStockFeed() // Generated by Fakes.
                 {
                     // Define each method:
                     // Name is original name + parameter types:
                     GetSharePriceString = (company) => { return 1234; }
                 };

        // In the completed application, stockFeed would be a real one:
        var componentUnderTest = new StockAnalyzer(stockFeed);

      // Act:
        int actualValue = componentUnderTest.GetContosoPrice();

      // Assert:
        Assert.AreEqual(1234, actualValue);
    }
    ...
}
<TestClass()> _
Class TestStockAnalyzer

    <TestMethod()> _
    Public Sub TestContosoStockPrice()
        ' Arrange:
        ' Create the fake stockFeed:
        Dim stockFeed As New StockAnalysis.Fakes.StubIStockFeed
        With stockFeed
            .GetSharePriceString = Function(company)
                                       Return 1234
                                   End Function
        End With
        ' In the completed application, stockFeed would be a real one:
        Dim componentUnderTest As New StockAnalyzer(stockFeed)
        ' Act:
        Dim actualValue As Integer = componentUnderTest.GetContosoPrice
        ' Assert:
        Assert.AreEqual(1234, actualValue)
    End Sub
End Class

Представляет элемент волшебства здесь класс StubIStockFeed.Для каждого общего типа в сборке механизм Microsoft Fakes создает класс заглушки.Имя класса, производного от имени заглушки интерфейса, с "Fakes.Stub" в качестве префикса и добавлены имена типа параметра.

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

Hh549174.collapse_all(ru-ru,VS.110).gifПроверка значений параметра

Чтобы проверить, когда компонент осуществляет другому компоненту, передаются правильные значения.Можно поместить в любое утверждение заглушке, или можно хранить значения и проверьте его в основном корпусе теста.Например:

[TestClass]
class TestMyComponent
{
       
    [TestMethod]
    public void TestVariableContosoPrice()
    {
     // Arrange:
        int priceToReturn;
        string companyCodeUsed;
        var componentUnderTest = new StockAnalyzer(new StubIStockFeed()
            {
               GetSharePriceString = (company) => 
                  { 
                     // Store the parameter value:
                     companyCodeUsed = company;
                     // Return the value prescribed by this test:
                     return priceToReturn;
                  };
            };
        // Set the value that will be returned by the stub:
        priceToReturn = 345;

     // Act:
        int actualResult = componentUnderTest.GetContosoPrice(priceToReturn);

     // Assert:
        // Verify the correct result in the usual way:
        Assert.AreEqual(priceToReturn, actualResult);

        // Verify that the component made the correct call:
        Assert.AreEqual("COOO", companyCodeUsed);
    }
...}
<TestClass()> _
Class TestMyComponent
    <TestMethod()> _
    Public Sub TestVariableContosoPrice()
        ' Arrange:
        Dim priceToReturn As Integer
        Dim companyCodeUsed As String = ""
        Dim stockFeed As New StockAnalysis.Fakes.StubIStockFeed()
        With stockFeed
            ' Implement the interface's method:
            .GetSharePriceString = _
                Function(company)
                    ' Store the parameter value:
                    companyCodeUsed = company
                    ' Return a fixed result:
                    Return priceToReturn
                End Function
        End With
        ' Create an object to test:
        Dim componentUnderTest As New StockAnalyzer(stockFeed)
        ' Set the value that will be returned by the stub:
        priceToReturn = 345

        ' Act:
        Dim actualResult As Integer = componentUnderTest.GetContosoPrice()

        ' Assert:
        ' Verify the correct result in the usual way:
        Assert.AreEqual(priceToReturn, actualResult)
        ' Verify that the component made the correct call:
        Assert.AreEqual("COOO", companyCodeUsed)
    End Sub
...
End Class

Заглушки для различных типов членов типа

Hh549174.collapse_all(ru-ru,VS.110).gifМетоды

Как описано в примере, методы могут быть заштырить можно вложить делегат на экземпляр класса заглушки.Имя типа заглушки производится от имен методов и параметров.Например, даны следующие интерфейс IMyInterface и метод MyMethod:

// application under test
interface IMyInterface 
{
    int MyMethod(string value);
}

Мы присоединим заглушку к MyMethod, которая всегда возвращает 1:

// unit test code
  var stub = new StubIMyInterface ();
  stub.MyMethodString = (value) => 1;

Если вы не предоставите заглушку для функции, фальшивки предоставляют функцию, возвращающую значение по умолчанию возвращаемого типа.Для чисел значение по умолчанию 0, и для типов классов это null (C-#) или Nothing (Visual Basic).

Hh549174.collapse_all(ru-ru,VS.110).gifСвойства

Методы считывания и присвоения значений представлены как отдельные делегаты и могут быть заменены заглушкой отдельно.В качестве примера рассмотрите свойство Value объекта IMyInterface:

// code under test
interface IMyInterface 
{
    int Value { get; set; }
}

Мы присоединим делегаты к методам считывания и присваивания свойства Value для имитации автоматического свойства:

// unit test code
int i = 5;
var stub = new StubIMyInterface();
stub.ValueGet = () => i;
stub.ValueSet = (value) => i = value;

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

Hh549174.collapse_all(ru-ru,VS.110).gifСобытия

События представлены как поля делегата.В результате любые события, замененные заглушками, могут генерироваться просто путем вызова резервного поля события.Рассмотрим следующий интерфейс для замены на заглушку:

// code under test
interface IWithEvents 
{
    event EventHandler Changed;
}

Чтобы вызвать событие Changed достаточно просто вызвать делегат резервного поля:

// unit test code
  var withEvents = new StubIWithEvents();
  // raising Changed
  withEvents.ChangedEvent(withEvents, EventArgs.Empty);

Hh549174.collapse_all(ru-ru,VS.110).gifУниверсальные методы

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

// code under test
interface IGenericMethod 
{
    T GetValue<T>();
}

можно написать тест, заштырит создание GetValue<int>:

// unit test code
[TestMethod]
public void TestGetValue() 
{
    var stub = new StubIGenericMethod();
    stub.GetValueOf1<int>(() => 5);

    IGenericMethod target = stub;
    Assert.AreEqual(5, target.GetValue<int>());
}

Если код является вызов метода GetValue<T> с любым другим созданием, заглушка просто вызыватьTfа бы расширение функциональности.

Hh549174.collapse_all(ru-ru,VS.110).gifЗаглушки виртуальных классов

В предыдущих примерах, заглушки созданные из интерфейсов.Также можно создать заглушки от класса с виртуальными или абстрактные члены.Например:

// Base class in application under test
    public abstract class MyClass
    {
        public abstract void DoAbstract(string x);
        public virtual int DoVirtual(int n)
        { return n + 42; }
        public int DoConcrete()
        { return 1; }
    }

В заглушке, созданных из этого класса можно использовать методы делегата набора для DoAbstract() и DoVirtual(), но не DoConcrete().

// unit test
  var stub = new Fakes.MyClass();
  stub.DoAbstractString = (x) => { Assert.IsTrue(x>0); };
  stub.DoVirtualInt32 = (n) => 10 ;
  

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

// unit test code
var stub = new Fakes.MyClass();
stub.CallBase = false;
// No delegate set – default delegate:
Assert.AreEqual(0, stub.DoVirtual(1));

stub.CallBase = true;
//No delegate set - calls the base:
Assert.AreEqual(43,stub.DoVirtual(1));

Отладка заглушек

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

Ограничения заглушки

  1. Сигнатуры методов с указателями не поддерживаются.

  2. Запечатанные классы или статические методы не могут быть заменены заглушками, поскольку типы заглушки полагаются на диспетчер виртуальных методов.В таких случаях следует использовать типы оболочки, как описано в Использование оболочек совместимости для изоляции приложения от других сборок при модульном тестировании

Изменение функциональности по умолчанию для заглушки

Каждый созданный тип заглушки содержит экземпляр интерфейса IStubBehavior (через свойство IStub.InstanceBehavior).Функциональность вызывается, когда клиент вызывает член без присоединенного пользовательского делегата.Если функциональность не была установлена, то будет использоваться экземпляр, возвращаемый свойством StubsBehaviors.Current.По умолчанию это свойство возвращает функциональность, которая создает исключение NotImplementedException.

Эта функциональность может быть изменена в любое время, присвоив свойству InstanceBehavior любой экземпляр заглушки.Например, следующий фрагмент изменяет функциональность, которая ничего не делает или возвращает значение по умолчанию для типа возвращаемого значения: default(T):

// unit test code
var stub = new StubIFileSystem();
// return default(T) or do nothing
stub.InstanceBehavior = StubsBehaviors.DefaultValue;

Функциональность можно также изменить глобально для всех объектов заглушки, для которых функциональность не была установлена, путем задания свойства StubsBehaviors.Current:

// unit test code
//change default behavior for all stub instances
//where the behavior has not been set
StubBehaviors.Current = 
    BehavedBehaviors.DefaultValue;

Внешние ресурсы

Hh549174.collapse_all(ru-ru,VS.110).gifРуководство

Проверка непрерывной работы с Visual Studio 2012 – Chapter 2: Модульное тестирование: Тестирование внутри

См. также

Основные понятия

Изоляция тестируемого кода с помощью Microsoft Fakes