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


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

Типы оболочки - одна из 2, Fakes технологий Microsoft .NET Framework используются, позволяя легко определить компоненты тестируемого из среды.Оболочки отвлекают вызовы к отдельным методам в код, написанный в рамках теста.Множество различных результатов методов, зависимых от внешних условиях, но оболочкой под элементом управления теста и могут возвращать последовательные результаты при каждом вызове.Это позволяет намного проще создавать тесты.

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

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

Требования

  • Visual Studio Ultimate

В разделе Видео (1h16). Тестирование отменить тестируемых код с фальшивками в Visual Studio 2012

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

В этом разделе:

Пример: ошибка Y2K

Использование оболочки

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

  • Используйте ShimsContext

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

Оболочки для различных типов методов

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

Обнаружение вызова окружения

Параллельность

Вызов метода из исходного метода обертки

Ограничения

Пример: ошибка Y2K

Рассмотрим метод, который создает исключение 1-ого января 2000-го года:

// code under test
public static class Y2KChecker {
    public static void Check() {
        if (DateTime.Now == new DateTime(2000, 1, 1))
            throw new ApplicationException("y2kbug!");
    }
}

Тестирование этого метода особенно затруднительно, поскольку программа завесит от DateTime.Now, а метод, зависимый от часов компьютера — среда-зависимый, недетерминированный метод.Кроме того, DateTime.Now — статическое свойство, поэтому его нельзя заменить заглушкой.Эта проблема является симптомом проблемы изоляции в модульном тестировании: программы, непосредственно вызывающих API базы данных, взаимодействующие с веб-службами и т. д. сложно протестировать с помощью модульных тестов, поскольку они зависят от логики среды.

Именно здесь должны использоваться типы обертки.Типы обертки предоставляют механизм для обхода любого .NET метода с помощью определенного пользователем делегата.Типы обертки — это код, сгенерированный Fake генератором, он используют делегаты, которые называются типами обертки, чтобы определить новые реализации метода.

Следующий тест показывает, как использовать тип обертки ShimDateTime для предоставления пользовательской реализации DateTime.Now:

//unit test code
// create a ShimsContext cleans up shims 
using (ShimsContext.Create()
    // hook delegate to the shim method to redirect DateTime.Now
    // to return January 1st of 2000
    ShimDateTime.NowGet = () => new DateTime(2000, 1, 1);
    Y2KChecker.Check();
}

Использование оболочки

Hh549176.collapse_all(ru-ru,VS.110).gifДобавление сборки фальшивок

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

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

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

Hh549176.collapse_all(ru-ru,VS.110).gifИспользуйте ShimsContext

При использовании обертки типов в среде модульного тестирования, необходимо обернуть код теста в ShimsContext для наблюдения за время существования оболочек.Если этого не требуется, то обертки будут существовать до завершения работы домена приложения.Самым простым способом создания ShimsContext является его создание с помощью статического метода Create() как показано в следующем коде:

//unit test code
[Test]
public void Y2kCheckerTest() {
  using(ShimsContext.Create()) {
    ...
  } // clear all shims
}

Важно правильно освобождать каждый контекст обертки.Как правило, всегда вызывайте ShimsContext.Create внутри выражения using, чтобы обеспечить правильное удаление зарегистрированных оболочек.Например, можно зарегистрировать оболочку для метода теста, который заменяет DateTime.Now методом с делегатом, который всегда возвращает первое января 2000 года.Если забыть очистить зарегистрированную оболочку в методе теста, остальная часть тестового запуска всегда будет возвращать первое января 2000 года в качестве значения DateTime.Now.Это может вызвать удивление и запутать.

Hh549176.collapse_all(ru-ru,VS.110).gifЗапись теста с оболочками

В тестовом коде, вставьте крюковину для метода требуется подмена идентификаторов.Например:

[TestClass]
public class TestClass1
{ 
        [TestMethod]
        public void TestCurrentYear()
        {
            int fixedYear = 2000;

            using (ShimsContext.Create())
            {
              // Arrange:
                // Detour DateTime.Now to return a fixed date:
                System.Fakes.ShimDateTime.NowGet = 
                () =>
                { return new DateTime(fixedYear, 1, 1); };

                // Instantiate the component under test:
                var componentUnderTest = new MyComponent();

              // Act:
                int year = componentUnderTest.GetTheCurrentYear();

              // Assert: 
                // This will always be true if the component is working:
                Assert.AreEqual(fixedYear, year);
            }
        }
}
<TestClass()> _
Public Class TestClass1
    <TestMethod()> _
    Public Sub TestCurrentYear()
        Using s = Microsoft.QualityTools.Testing.Fakes.ShimsContext.Create()
            Dim fixedYear As Integer = 2000
            ' Arrange:
            ' Detour DateTime.Now to return a fixed date:
            System.Fakes.ShimDateTime.NowGet = _
                Function() As DateTime
                    Return New DateTime(fixedYear, 1, 1)
                End Function

            ' Instantiate the component under test:
            Dim componentUnderTest = New MyComponent()
            ' Act:
            Dim year As Integer = componentUnderTest.GetTheCurrentYear
            ' Assert: 
            ' This will always be true if the component is working:
            Assert.AreEqual(fixedYear, year)
        End Using
    End Sub
End Class

Имена классов оболочки строятся с помощью префикса Fakes.Shim на имя исходного типа.

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

Обратите внимание, что крюковины создаются и удаляются во время выполнения.Всегда следует создать крюковина в течение жизненного цикла ShimsContext.После удаления все созданные оболочки, пока он был активен удаляется.Лучшим способ сделать это выписки внутри using.

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

Оболочки для различных типов методов

Типы оболочки позволяют заменять любой метод .NET, включая статические методы или методы метода ", собственными делегатам.

Hh549176.collapse_all(ru-ru,VS.110).gifСтатические методы

Свойства для присоединения обертки к статическим методам помещаются в типе обертки.Каждое свойство имеет только метод присваивания, который можно использовать, чтобы присоединить делегат необходимому методу.Примером служит класс MyClass со статическим методом MyMethod:

//code under test
public static class MyClass {
    public static int MyMethod() {
        ...
    }
}

Можно присоединить оболочку к MyMethod, которая всегда возвращает 5.

// unit test code
ShimMyClass.MyMethod = () =>5;

Hh549176.collapse_all(ru-ru,VS.110).gifМетоды экземпляра (для всех экземпляров)

Аналогично статическим методам, методы экземпляра можно заменить для всех экземпляров.Свойства для присоединения эти оболочек располагаются во вложенном типе AllInstances, чтобы избежать путаницы.Примером служит класс MyClass с методом экземпляра MyMethod:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

Можно вложить в оболочку MyMethod, всегда возвращает значение 5, независимо от экземпляра:

// unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;

Созданная структура типа ShimMyClass выглядит следующим образом:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public static class AllInstances {
        public static Func<MyClass, int>MyMethod {
            set {
                ...
            }
        }
    }
}

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

Hh549176.collapse_all(ru-ru,VS.110).gifМетоды экземпляра (для одного экземпляра среды выполнения)

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

Примером служит класс MyClass с методом экземпляра MyMethod:

// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}

Можно создать два типа обертки MyMethod — тот, что первый всегда возвращает значение 5, и тот, который всегда возвращает 10.

// unit test code
var myClass1 = new ShimMyClass()
{
    MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };

Созданная структура типа ShimMyClass выглядит следующим образом:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public Func<int> MyMethod {
        set {
            ...
        }
    }
    public MyClass Instance {
        get {
            ...
        }
    }
}

К экземпляру завернутого типа фактически можно получить доступ через свойство экземпляра:

// unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;

Тип обертки также содержит неявное преобразование к завернутому типу, поэтому обычно можно просто использовать тип обертки следующим образом:

// unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // implicit cast retrieves the runtime
                         // instance

Hh549176.collapse_all(ru-ru,VS.110).gifКонструкторы

Конструкторы могут быть также завернуты оболочку, чтобы присоединить типы обертки будущим объектам.Каждый конструктор представляется как конструктор статического метода в типе обертки.Примером служит класс MyClass, конструктор которого принимает целое число:

// code under test
public class MyClass {
    public MyClass(int value) {
        this.Value = value;
    }
    ...
}

Создание типа обертки конструктора означает, что каждый будущий экземпляр будет возвращать -5, когда будет вызван метод считывания значения, независимо от значения в конструкторе.

// unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
    var shim = new ShimMyClass(@this) {
        ValueGet = () => -5
    };
};

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

// unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }

Созданная структура типа ShimMyClass выглядит следующим образом:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
    public static Action<MyClass, int> ConstructorInt32 {
        set {
            ...
        }
    }

    public ShimMyClass() { }
    public ShimMyClass(MyClass instance) : base(instance) { }
    ...
}

Hh549176.collapse_all(ru-ru,VS.110).gifБазовые элементы

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

Примером служит класс MyBase с методом экземпляра MyMethod и подтипом MyChild:

public abstract class MyBase {
    public int MyMethod() {
        ...
    }
}

public class MyChild : MyBase {
}

Можно создать оболочку на MyBase путем создания новой обертки ShimMyBase:

// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };

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

Созданные структуры типа ShimMyChild и ShimMyBase выглядят следующим образом:

// Fakes generated code
public class ShimMyChild : ShimBase<MyChild> {
    public ShimMyChild() { }
    public ShimMyChild(Child child)
        : base(child) { }
}
public class ShimMyBase : ShimBase<MyBase> {
    public ShimMyBase(Base target) { }
    public Func<int> MyMethod
    { set { ... } }
}

Hh549176.collapse_all(ru-ru,VS.110).gifСтатические конструкторы

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

Hh549176.collapse_all(ru-ru,VS.110).gifМетоды завершения

Методы завершения не поддерживаются в Fakes.

Hh549176.collapse_all(ru-ru,VS.110).gifЗакрытые методы

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

Hh549176.collapse_all(ru-ru,VS.110).gifПривязка интерфейсов

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

Примером служит класс MyClass, реализующий IEnumerable<int>:

public class MyClass : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        ...
    }
    ...
}

Можно обернуть реализацию IEnumerable<int> в MyClass путем вызова метода привязки.

// unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });

Созданная структура типа ShimMyClass выглядит следующим образом:

// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public ShimMyClass Bind(IEnumerable<int> target) {
        ...
    }
}

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

Каждый созданный тип обертки содержит экземпляр интерфейса IShimBehavior, доступный через свойство ShimBase<T>.InstanceBehavior.Это поведение используется, когда клиент вызывает член экземпляра, который не был явно обернут.

Если поведение явно не было установлено, то будет использоваться экземпляр, возвращаемый статическим свойством ShimsBehaviors.Current.По умолчанию это свойство возвращает функциональность, которая создает исключение NotImplementedException.

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

// unit test code
var shim = new ShimMyClass();
//return default(T) or do nothing
shim.InstanceBehavior = ShimsBehaviors.DefaultValue;

Поведение также можно также изменить глобально для всех обернутых экземпляров, для которых свойство InstanceBehavior явно не было установлено с помощью статического свойства ShimsBehaviors.Current:

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

Обнаружение вызова окружения

Можно присоединить функциональности всем элементам , включая статические методы, путем присвоения поведения ShimsBehaviors.NotImplemented статическому свойству Behavior соответствующего типа обертки.

// unit test code
// assigning the not implemented behavior
ShimMyClass.Behavior = ShimsBehaviors.NotImplemented;
// shorthand
ShimMyClass.BehaveAsNotImplemented();

Параллельность

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

Вызов метода из исходного метода обертки

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

Первый подходом для решения этой проблемы является создание обертки, вызывающей исходный метод с помощью делегата и ShimsContext.ExecuteWithoutShims() следующим образом:

// unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
  ShimsContext.ExecuteWithoutShims(() => {

      Console.WriteLine("enter");
      File.WriteAllText(fileName, content);
      Console.WriteLine("leave");
  });
};

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

// unit test code
ShimsDelegates.Action<string, string> shim = null;
shim = (fileName, content) => {
  try {
    Console.WriteLine("enter”);
    // remove shim in order to call original method
    ShimFile.WriteAllTextStringString = null;
    File.WriteAllText(fileName, content);
  }
  finally
  {
    // restore shim
    ShimFile.WriteAllTextStringString = shim;
    Console.WriteLine("leave");
  }
};
// initialize the shim
ShimFile.WriteAllTextStringString = shim;

Ограничения

обертки нельзя использовать для всех типов из библиотек базовых классов .NET mscorlib и System.

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

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

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

См. также

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

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

Другие ресурсы

Блог ректора peter: Visual Studio 2012 оболочки

Видео (1h16). Тестирование отменить тестируемых код с фальшивками в Visual Studio 2012