2013년 1월 29일 화요일

Shim types

Shim types



Using shims to isolate your application from other assemblies for unit testing

Visual Studio 2012
5 out of 10 rated this helpful Rate this topic
Shim types are one of two technologies that the Microsoft Fakes Framework uses to let you easily isolate components under test from the environment. Shims divert calls to specific methods to code that you write as part of your test. Many methods return different results dependent on external conditions, but a shim is under the control of your test and can return consistent results at every call. This makes your tests much easier to write.
Use shims to isolate your code from assemblies that are not part of your solution. To isolate components of your solution from each other, we recommend that you use stubs.
For an overview and quick start guidance, see Isolating Code under Test with Microsoft Fakes
Requirements
  • Visual Studio Ultimate
Let’s consider a method that throws an exception on January 1st of 2000:
// code under test
public static class Y2KChecker {
    public static void Check() {
        if (DateTime.Now == new DateTime(2000, 1, 1))
            throw new ApplicationException("y2kbug!");
    }
}
Testing this method is particularly problematic because the program depends on DateTime.Now, a method that depends on the computer's clock, an environment-dependent, non-deterministic method. Furthermore, the DateTime.Now is a static property so a stub type can’t be used here. This problem is symptomatic of the isolation issue in unit testing: programs that directly call into database APIs, communicate with web services, and so on are hard to unit test because their logic depends on the environment.
This is where shim types should be used. Shim types provide a mechanism to detour any .NET method to a user defined delegate. Shim types are code-generated by the Fakes generator, and they use delegates, which we call shim types, to specify the new method implementations.
The following test shows how to use the shim type, ShimDateTime, to provide a custom implementation of 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();
}
  1. In Solution Explorer, expand your unit test project’s References.
    • If you are working in Visual Basic, you must select Show All Files in the Solution Explorer toolbar, in order to see the References list.
  2. Select the assembly that contains the classes definitions for which you want to create shims. For example, if you want to shim DateTime, select System.dll
  3. On the shortcut menu, choose Add Fakes Assembly.
When using shim types in a unit test framework, you must wrap the test code in a ShimsContext to control the lifetime of your shims. If we didn’t require this, your shims would last until the AppDomain shut down. The easiest way to create a ShimsContext is by using the static Create() method as shown in the following code:
//unit test code
[Test]
public void Y2kCheckerTest() {
  using(ShimsContext.Create()) {
    ...
  } // clear all shims
}
It is critical to properly dispose each shim context. As a rule of thumb, always call the ShimsContext.Create inside of a using statement to ensure proper clearing of the registered shims. For example, you might register a shim for a test method that replaces the DateTime.Now method with a delegate that always returns the first of January 2000. If you forget to clear the registered shim in the test method, the rest of the test run would always return the first of January 2000 as the DateTime.Now value. This might be suprising and confusing.
In your test code, insert a detour for the method you want to fake. For example:
[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);
            }
        }
}
Shim class names are made up by prefixing Fakes.Shim to the original type name.
Shims work by inserting detours into the code of the application under test. Wherever a call to the original method occurs, the Fakes system performs a detour, so that instead of calling the real method, your shim code is called.
Notice that detours are created and deleted at run time. You must always create a detour within the life of a ShimsContext. When it is disposed, any shims you created while it was active are removed. The best way to do this is inside a using statement.
You might see a build error stating that the Fakes namespace does not exist. This error sometimes appears when there are other compilation errors. Fix the other errors and it will vanish.
Shim types allow you to replace any .NET method, including static methods or non-virtual methods, with your own delegates.
The properties to attach shims to static methods are placed in a shim type. Each property has only a setter that can be used to attach a delegate to the target method. For example, given a class MyClass with a static method MyMethod:
//code under test
public static class MyClass {
    public static int MyMethod() {
        ...
    }
}
We can attach a shim to MyMethod that always returns 5:
// unit test code
ShimMyClass.MyMethod = () =>5;
Similarly to static methods, instance methods can be shimmed for all instances. The properties to attach those shims are placed in a nested type named AllInstances to avoid confusion. For example, given a class MyClass with an instance method MyMethod:
// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}
You can attach a shim to MyMethod that always returns 5, regardless of the instance:
// unit test code
ShimMyClass.AllInstances.MyMethod = () => 5;
The generated type structure of ShimMyClass looks like the following code:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public static class AllInstances {
        public static Func<MyClass, int>MyMethod {
            set {
                ...
            }
        }
    }
}
Notice that Fakes passes the runtime instance as the first argument of the delegate in this case.
Instance methods can also be shimmed by different delegates, based on the receiver of the call. This enables the same instance method to have different behaviors per instance of the type. The properties to set up those shims are instance methods of the shim type itself. Each instantiated shim type is also associated with a raw instance of a shimmed type.
For example, given a class MyClass with an instance method MyMethod:
// code under test
public class MyClass {
    public int MyMethod() {
        ...
    }
}
We can set up two shim types of MyMethod such that the first one always returns 5 and the second always returns 10:
// unit test code
var myClass1 = new ShimMyClass()
{
    MyMethod = () => 5
};
var myClass2 = new ShimMyClass { MyMethod = () => 10 };
The generated type structure of ShimMyClass looks like the following code:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public Func<int> MyMethod {
        set {
            ...
        }
    }
    public MyClass Instance {
        get {
            ...
        }
    }
}
The actual shimmed type instance can be accessed through the Instance property:
// unit test code
var shim = new ShimMyClass();
var instance = shim.Instance;
The shim type also has an implicit conversion to the shimmed type, so you can usually simply use the shim type as is:
// unit test code
var shim = new ShimMyClass();
MyClass instance = shim; // implicit cast retrieves the runtime
                         // instance
Constructors can also be shimmed in order to attach shim types to future objects. Each constructor is exposed as a static method Constructor in the shim type. For example, given a class MyClass with a constructor taking an integer:
// code under test
public class MyClass {
    public MyClass(int value) {
        this.Value = value;
    }
    ...
}
We set up the shim type of the constructor so that every future instance returns -5 when the Value getter is invoked, regardless of the value in the constructor:
// unit test code
ShimMyClass.ConstructorInt32 = (@this, value) => {
    var shim = new ShimMyClass(@this) {
        ValueGet = () => -5
    };
};
Note that each shim type exposes two constructors. The default constructor should be used when a fresh instance is needed, while the constructor taking a shimmed instance as argument should be used in constructor shims only:
// unit test code
public ShimMyClass() { }
public ShimMyClass(MyClass instance) : base(instance) { }
The generated type structure of ShimMyClass resembles the followoing code:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass>
{
    public static Action<MyClass, int> ConstructorInt32 {
        set {
            ...
        }
    }

    public ShimMyClass() { }
    public ShimMyClass(MyClass instance) : base(instance) { }
    ...
}
The shim properties of base members can be accessed by creating a shim for the base type and passing the child instance as a parameter to the constructor of the base shim class.
For example, given a class MyBase with an instance method MyMethod and a subtype MyChild:
public abstract class MyBase {
    public int MyMethod() {
        ...
    }
}

public class MyChild : MyBase {
}
We can set up a shim of MyBase by creating a new ShimMyBase shim:
// unit test code
var child = new ShimMyChild();
new ShimMyBase(child) { MyMethod = () => 5 };
Note that the child shim type is implicitly converted to the child instance when passed as a parameter to the base shim constructor.
The generated type structure of ShimMyChild and ShimMyBase resembles the following code:
// 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 { ... } }
}
Shim types expose a static method StaticConstructor to shim the static constructor of a type. Since static constructors are executed once only, you need to ensure that the shim is configured before any member of the type is accessed.
Finalizers are not supported in Fakes.
The Fakes code generator will create shim properties for private methods that only have visible types in the signature, i.e. parameter types and return type visible.
When a shimmed type implements an interface, the code generator emits a method that allows it to bind all the members from that interface at once.
For example, given a class MyClass that implements IEnumerable<int>:
public class MyClass : IEnumerable<int> {
    public IEnumerator<int> GetEnumerator() {
        ...
    }
    ...
}
We can shim the implementations of IEnumerable<int> in MyClass by calling the Bind method:
// unit test code
var shimMyClass = new ShimMyClass();
shimMyClass.Bind(new List<int> { 1, 2, 3 });
The generated type structure of ShimMyClass resembles the following code:
// Fakes generated code
public class ShimMyClass : ShimBase<MyClass> {
    public ShimMyClass Bind(IEnumerable<int> target) {
        ...
    }
}
Each generated shim type holds an instance of the IShimBehavior interface, through the ShimBase<T>.InstanceBehavior property. The behavior is used whenever a client calls an instance member that was not explicitly shimmed.
If the behavior has not been explicitly set, it will use the instance returned by the static ShimsBehaviors.Current property. By default, this property returns a behavior that throws a NotImplementedException exception.
This behavior can be changed at any time by setting the InstanceBehavior property on any shim instance. For example, the following snippet changes the shim to a behavior that does nothing or returns the default value of the return type—that is, default(T):
// unit test code
var shim = new ShimMyClass();
//return default(T) or do nothing
shim.InstanceBehavior = ShimsBehaviors.DefaultValue;
The behavior can also be changed globally for all shimmed instances for which the InstanceBehavior property was not explicitly set by setting the staticShimsBehaviors.Current property:
// unit test code
// change default shim for all shim instances
// where the behavior has not been set
ShimsBehaviors.Current = 
    ShimsBehaviors.DefaultValue;
It is possible to attach a behavior to all the members, including static methods, of a particular type by assigning the ShimsBehaviors.NotImplemented behavior to the static property Behavior of the corresponding shim type:
// unit test code
// assigning the not implemented behavior
ShimMyClass.Behavior = ShimsBehaviors.NotImplemented;
// shorthand
ShimMyClass.BehaveAsNotImplemented();
Shim types apply to all threads in the AppDomain and don’t have thread affinity. This is an important fact if you plan to use a test runner that support concurrency: tests involving shim types cannot run concurrently. This property is not enfored by the Fakes runtime.
Imagine that we wanted to actually write the text to the file system after validating the file name passed to the method. In that case, we would want to call the original method in the middle of the shim method.
The first approach to solve this problem is to wrap a call to the original method using a delegate and ShimsContext.ExecuteWithoutShims() as in the following code:
// unit test code
ShimFile.WriteAllTextStringString = (fileName, content) => {
  ShimsContext.ExecuteWithoutShims(() => {

      Console.WriteLine("enter");
      File.WriteAllText(fileName, content);
      Console.WriteLine("leave");
  });
};
Another approach is to set the shim to null, call the original method and restore the shim.
// 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;
Shims cannot be used on all types from the .NET base class library mscorlib and System.


http://msdn.microsoft.com/en-us/library/hh549176.aspx

댓글 없음: