使用填充码针对单元测试将应用程序与程序集隔离

填充类型 是 Microsoft 伪造品 framework 使用可以轻松地向独立元素下从该环境测试这两种技术之一。填充牵制调用特定方法编写您的一部分编写测试。许多方法返回不同的结果依赖于外部条件,但是,填充受您的控件测试并且可以返回结果一致在每次调用。这使测试更容易编写。

使用填充独立需要从不是您的解决方案的一部分的程序集中的代码。若要确定您的彼此的解决方案组件,建议您使用存根。

有关概述和快速启动指南,请参见 用 Microsoft Fakes 隔离测试代码

要求

  • Visual Studio 旗舰版

请参见 视频 (1h16):测试与伪造品的非可测试的代码在 Visual Studio 2012

主题内容

这是本主题将了解:

示例:计算机 2000 年问题 bug

如何使用上述

  • 添加伪造品程序集

  • 使用 ShimsContext

  • 编写测试与上述

不同类型的上述方法

更改默认值行为

检测环境访问

并发

调用从上述方法的原始方法

限制

示例:计算机 2000 年问题 bug

我们考虑引发一月 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 的过程,与 web 服务通信,等等都很难单元测试,因为它们的逻辑依赖于该环境。

这是应使用的位置填充类型。填充类型提供一种机制绕道任何 .NET 方法为用户定义的委托。填充类型由伪造品生成器代码生成的,并且,它们使用委托,我们调用填充类型,指定新的方法实现。

以下测试演示如何使用填充类型,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(zh-cn,VS.110).gif添加伪造品程序集

  1. 在解决方案资源管理器中,展开您的单元测试项目的 引用

    • 如果使用的是 Visual Basic,必须选择在解决方案资源管理器工具栏中 显示所有文件,才能看到引用列表。
  2. 选择包含类定义要创建填充的程序集。例如,因此,如果要填 datetime,选择" System.dll

  3. 在快捷菜单上,选择 添加 Fakes 程序集

Hh549176.collapse_all(zh-cn,VS.110).gif使用 ShimsContext

当使用填充时键入单元测试框架,您必须包装在 ShimsContext 的测试代码管理生存期您的填充。如果我们不需要这种情况,您的填充将继续,直到 AppDomain 关闭。如下面的代码所示,可以方便地创建 ShimsContext 是使用静态 Create() 方法:

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

正确配置每填充环境非常重要。根据体验,始终调用在 using 语句中的 ShimsContext.Create 以确保注册的填充的相应清除。例如,您可以注册用替换为委托 DateTime.Now 方法始终返回 2000 年一月一天的测试方法的一个填充。如果忘记清除在测试方法中注册的填充,测试的其余部分总是返回一月 2000 日,因为 DateTime.Now 值。这可能是描述和混淆。

Hh549176.collapse_all(zh-cn,VS.110).gif编写与上述的测试

在测试代码,插入要集的方法的 detour。例如:

[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 前缀组成。

填充工作方式是插入 detour 绑定 到应用程序的测试代码。对原始方法的调用时,系统执行 detour 的伪造品,因此,而不是调用实际方法,则填充代码调用。

通知 detour 创建和删除运行时。您必须始终在创建 ShimsContext的生存期内 detour。当它时,您创建的任何填充,则处于活动状态中移除时。最好的方法就是在 using 语句中。

您可能会发现编译错误,指出伪造品命名空间不存在。此错误有时会出现在具有其他生成错误。修复这些错误,它将消失。

不同类型的上述方法

填充类型允许用自己的委托替换所有 .NET 方法,包括静态方法或非虚方法。

Hh549176.collapse_all(zh-cn,VS.110).gif静态方法

附加填充的属性设置为静态方法在填充类型放置。每个属性都有可用于将委托附加到目标方法仅的 setter。例如命名选件类与静态方法 MyMethod的 MyClass :

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

我们可以附加始终返回 5 的填充到 MyMethod :

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

Hh549176.collapse_all(zh-cn,VS.110).gif实例方法 (针对所有实例)

同样于静态方法,实例方法可用于所有实例中填。附加这些填充的属性在名为 AllInstances 的嵌套类型将避免混淆。例如命名选件类与实例方法 MyMethod的 MyClass :

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

无论实例,可以将总是返回 5 的填充到 MyMethod,例如:

// 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 {
                ...
            }
        }
    }
}

请注意伪造品在这种情况下将运行时实例作为委托的第一个参数。

Hh549176.collapse_all(zh-cn,VS.110).gif实例方法 (对于一个运行时实例)

实例方法可能由不同的委托还填,根据调用的接收器。这使同一实例方法具有不同的行为每个该类型的实例。将那些填充的属性是上述类型的实例方法。每个实例化的填充类型也与已填的类型的原始实例。

例如命名选件类与实例方法 MyMethod的 MyClass :

// 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(zh-cn,VS.110).gif构造函数

构造函数还可以填为了附加填充类型到将来的对象。每个构造函数显示为在上述类型的静态方法构造函数。例如命名选件类与采用整数的构造函数的 MyClass :

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

我们为构造函数设置填充类型,以便每将来实例返回 -5;当值 getter 调用时,无论在构造函数中,的值:

// 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 的生成类型结构类似于 followoing 的代码:

// 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(zh-cn,VS.110).gif基成员

基成员填充属性可以通过创建了基类型的一个填充和工作项实例获取作为参数传递给基本填充选件类的构造函数。

例如命名选件类与实例方法 MyMethod 和子类型 MyChild的 MyBase :

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

public class MyChild : MyBase {
}

我们可以通过创建新的 ShimMyBase 填充设置 MyBase 填充:

// 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(zh-cn,VS.110).gif静态构造函数

填充类型显示静态方法 StaticConstructor 填类型的静态构造函数。因为静态构造函数仅执行一次,您需要确保配置填充,在该类型的所有成员的访问之前。

Hh549176.collapse_all(zh-cn,VS.110).gif终结器

终结器在伪造品不受支持。

Hh549176.collapse_all(zh-cn,VS.110).gif私有方法

伪造品代码生成器将创建只有可见输入该签名,IE.. 参数类型和返回可见类型的私有方法的填充属性。

Hh549176.collapse_all(zh-cn,VS.110).gif绑定接口

将一个标记填的类型实现接口时,代码生成器发出允许它立即将该接口的所有成员的方法。

例如命名选件该类的 MyClass 实现 IEnumerable<int>:

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

我们可以通过调用 bind 方法填 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) {
        ...
    }
}

更改默认值行为

每个生成的填充类型通过 ShimBase<T>.InstanceBehavior 属性包含 IShimBehavior 接口的实例。使用该行为,只要客户端调用未被显式填的实例成员。

如果该行为未显式设置时,它将使用静态 ShimsBehaviors.Current 属性返回的实例。默认情况下,此属性返回引发 NotImplementedException 异常行为。

此行为可将任何填充实例的 InstanceBehavior 属性在 + 任何 + 时间更改。例如,下面的代码段更改填充到不执行任何操作或返回返回的行为类型为,默认值的默认值 (of 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();

并发

填充类型应用于 AppDomain 中的所有线程,并且不具有线程关联。这是一个重要情况,如果您计划使用支持并发的测试运行程序:测试中涉及填充类型不能同时运行。此属性不是运行时的伪造品 enfored。

调用从上述方法的原始方法

假设我们实际需要将文本设置文件系统在验证该文件名后会传递给方法。在这种情况下,我们需要在填充其参数调用原始方法。

解决此问题的第一种方法是包装对原始方法使用委托和 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系统的所有类型不能使用。

外部资源

Hh549176.collapse_all(zh-cn,VS.110).gif指南

测试使用 Visual Studio 进行附带的 2012 版–第 2 章:单元测试:测试。

请参见

概念

用 Microsoft Fakes 隔离测试代码

其他资源

针对于学院长的博客:Visual Studio 2012 个填充

视频 (1h16):测试与伪造品的非可测试的代码在 Visual Studio 2012