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

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

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

Например, рассмотрим схему, которая иллюстрирует задействованные компоненты:

Diagram of Real and Stub classes of StockAnalyzer.

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

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

Ниже приведен пример использования внедрения зависимостей в коде:

public int GetContosoPrice(IStockFeed feed) => feed.GetSharePrice("COOO");

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

Ознакомьтесь со следующими ограничениями для заглушки.

Создание заглушки: пошаговое руководство

Давайте начнем это упражнение с мотивирующим примером: одним из них, показанным на предыдущей схеме.

Создание библиотеки классов

Выполните следующие действия, чтобы создать библиотеку классов.

  1. Откройте Visual Studio и создайте проект библиотеки классов.

    Screenshot of Class Library project in Visual Studio.

  2. Настройте атрибуты проекта:

    • Задайте для имени проекта значение StockAnalysis.
    • Задайте для именирешения значение StubsTutorial.
    • Задайте для целевой платформы проекта значение .NET 8.0.
  3. Удалите файл по умолчанию Class1.cs.

  4. Добавьте новый файл с именем IStockFeed.cs и скопируйте его в следующее определение интерфейса:

    // IStockFeed.cs
    public interface IStockFeed
    {
        int GetSharePrice(string company);
    }
    
  5. Добавьте еще один новый файл с именем StockAnalyzer.cs и скопируйте его в следующее определение класса:

    // StockAnalyzer.cs
    public class StockAnalyzer
    {
        private IStockFeed stockFeed;
        public StockAnalyzer(IStockFeed feed)
        {
            stockFeed = feed;
        }
        public int GetContosoPrice()
        {
            return stockFeed.GetSharePrice("COOO");
        }
    }
    

Создание тестового проекта

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

  1. Щелкните правой кнопкой мыши решение и добавьте новый проект с именем MSTest Test Project.

  2. Задайте для имени проекта значение TestProject.

  3. Задайте для целевой платформы проекта значение .NET 8.0.

    Screenshot of Test project in Visual Studio.

Добавление сборки Fakes

Добавьте сборку Fakes для проекта.

  1. Добавьте ссылку на StockAnalyzerпроект.

    Screenshot of the command Add Project Reference.

  2. Добавьте сборку Fakes.

    1. В Обозреватель решений найдите ссылку на сборку:

      • Для более старого проекта .NET Framework (не в стиле пакета SDK) разверните узел Ссылки проекта модульного теста.

      • Для проекта в стиле SDK, предназначенного для .NET Framework, .NET Core или .NET 5.0 или более поздних версий, разверните узел Зависимости, чтобы найти сборку, которую нужно имитировать в разделе Сборки, Проекты или Пакеты.

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

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

    3. В контекстном меню щелкните Добавить сборку Fakes.

      Screenshot of the command Add Fakes Assembly.

Создание модульного теста

Теперь создайте модульный тест.

  1. Измените файл UnitTest1.cs по умолчанию, чтобы добавить следующее Test Method определение.

    [TestClass]
    class UnitTest1
    {
        [TestMethod]
        public void TestContosoPrice()
        {
            // Arrange:
            int priceToReturn = 345;
            string companyCodeUsed = "";
            var componentUnderTest = new StockAnalyzer(new StockAnalysis.Fakes.StubIStockFeed()
            {
                GetSharePriceString = (company) =>
                {
                    // Store the parameter value:
                    companyCodeUsed = company;
                    // Return the value prescribed by this test:
                    return priceToReturn;
                }
            });
    
            // Act:
            int actualResult = 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);
        }
    }
    

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

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

    Screenshot of Solution Explorer showing all files.

  2. Откройте Обозреватель теста и запустите тест.

    Screenshot of Test Explorer.

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

Существуют заглушки для различных типов элементов типа.

Методы

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

// IStockFeed.cs
interface IStockFeed
{
    int GetSharePrice(string company);
}

Прикрепляем заглушку к GetSharePrice использованию:GetSharePriceString

// unit test code
var componentUnderTest = new StockAnalyzer(new StockAnalysis.Fakes.StubIStockFeed()
        {
            GetSharePriceString = (company) =>
            {
                // Store the parameter value:
                companyCodeUsed = company;
                // Return the value prescribed by this test:
                return priceToReturn;
            }
        });

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

Свойства

Методы получения свойств и методы задания предоставляются в виде отдельных делегатов и могут быть помечены по отдельности. Например, рассмотрим свойство Value интерфейса IStockFeedWithProperty.

interface IStockFeedWithProperty
{
    int Value { get; set; }
}

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

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

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

События

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

interface IStockFeedWithEvents
{
    event EventHandler Changed;
}

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

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

Универсальные методы

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

interface IGenericMethod
{
    T GetValue<T>();
}

Вы можете заглушить GetValue<int> экземпляр следующим образом:

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

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

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

Заглушки виртуальных классов

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

// 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 ;

Если вы не предоставляете делегат для виртуального метода, Fakes может предоставить поведение по умолчанию или вызвать метод в базовом классе. Чтобы вызывался базовый метод, задайте свойство 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));

Изменение поведения заглушек по умолчанию

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

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

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

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

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