Prig is a lightweight framework for test indirections in .NET Framework.
Prig(PRototyping jIG) is a framework that generates a Test Double like Microsoft Fakes/Typemock Isolator/Telerik JustMock based on Unmanaged Profiler APIs. This framework enables that any methods are replaced with mocks. For example, a static property, a private method, a non-virtual member and so on.
As of Mar 16, 2015, Developing V2.0.0(Since this version, Prig is going to support Visual Studio integrated environment. More details can be found here).
As of Dec 31, 2014, Released V1.1.0.
Let's say you want to test the following code:
using System;
namespace ConsoleApplication
{
public static class LifeInfo
{
public static bool IsNowLunchBreak()
{
var now = DateTime.Now;
return 12 <= now.Hour && now.Hour < 13;
}
}
}You probably can't test this code, because DateTime.Now returns the value that depends on an external environment. To make be testable, you should replace DateTime.Now to the Test Double that returns the fake information. If you use Prig, it will enable you to generate a Test Double by the following steps without any editing the product code:
Run Visual Studio 2013(Express for Windows Desktop or more) as Administrator, add test project(e.g. ConsoleApplicationTest) and run the following command in the Package Manager Console:
PM> Install-Package PrigNOTE: Installation will mostly go well. However, it doesn't go well if performing just after installing Visual Studio. See also this issue's comment.
Run the following command in the Package Manager Console:
PM> Add-PrigAssembly -Assembly "mscorlib, Version=4.0.0.0"The command means to create the indirection stub settings for the test. The reason to specify mscorlib is that DateTime.Now belongs mscorlib. After the command is invoked, you will get the confirmation message that the project has been modified externally, so reload the project.
You can find the setting file <assembly name>.<runtime version>.v<assembly version>.prig in the project(in this case, it is mscorlib.v4.0.30319.v4.0.0.0.prig). Modify the setting in accordance with the comment, then build all projects:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="prig" type="Urasandesu.Prig.Framework.PilotStubberConfiguration.PrigSection, Urasandesu.Prig.Framework" />
</configSections>
<!--
The content of tag 'add' is generated by the command 'Get-IndirectionStubSetting'.
Specifically, you can generate it by the following PowerShell script in the Package Manager Console:
========================== EXAMPLE 1 ==========================
PM> $methods = Find-IndirectionTarget datetime get_Now
PM> $methods
Method
======
System.DateTime get_Now()
PM> $methods[0] | Get-IndirectionStubSetting | clip
PM>
Then, paste the clipboard content to between the tags 'stubs'.
-->
<prig>
<stubs>
<add name="NowGet" alias="NowGet">
<RuntimeMethodInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:x="http://www.w3.org/2001/XMLSchema" z:Id="1" z:FactoryType="MemberInfoSerializationHolder" z:Type="System.Reflection.MemberInfoSerializationHolder" z:Assembly="0" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns="http://schemas.datacontract.org/2004/07/System.Reflection">
<Name z:Id="2" z:Type="System.String" z:Assembly="0" xmlns="">get_Now</Name>
<AssemblyName z:Id="3" z:Type="System.String" z:Assembly="0" xmlns="">mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</AssemblyName>
<ClassName z:Id="4" z:Type="System.String" z:Assembly="0" xmlns="">System.DateTime</ClassName>
<Signature z:Id="5" z:Type="System.String" z:Assembly="0" xmlns="">System.DateTime get_Now()</Signature>
<Signature2 z:Id="6" z:Type="System.String" z:Assembly="0" xmlns="">System.DateTime get_Now()</Signature2>
<MemberType z:Id="7" z:Type="System.Int32" z:Assembly="0" xmlns="">8</MemberType>
<GenericArguments i:nil="true" xmlns="" />
</RuntimeMethodInfo>
</add>
</stubs>
</prig>
</configuration>In the test code, it becomes testable through the use of the stub and the replacement to Test Double that returns the fake information:
using NUnit.Framework;
using ConsoleApplication;
using System;
using System.Prig;
using Urasandesu.Prig.Framework;
namespace ConsoleApplicationTest
{
[TestFixture]
public class LifeInfoTest
{
[Test]
public void IsNowLunchBreak_should_return_false_when_11_oclock()
{
using (new IndirectionsContext())
{
// Arrange
PDateTime.NowGet().Body = () => new DateTime(2013, 12, 13, 11, 00, 00);
// Act
var result = LifeInfo.IsNowLunchBreak();
// Assert
Assert.IsFalse(result);
}
}
[Test]
public void IsNowLunchBreak_should_return_true_when_12_oclock()
{
using (new IndirectionsContext())
{
// Arrange
PDateTime.NowGet().Body = () => new DateTime(2013, 12, 13, 12, 00, 00);
// Act
var result = LifeInfo.IsNowLunchBreak();
// Assert
Assert.IsTrue(result);
}
}
[Test]
public void IsNowLunchBreak_should_return_false_when_13_oclock()
{
using (new IndirectionsContext())
{
// Arrange
PDateTime.NowGet().Body = () => new DateTime(2013, 12, 13, 13, 00, 00);
// Act
var result = LifeInfo.IsNowLunchBreak();
// Assert
Assert.IsFalse(result);
}
}
}
}In fact, to enable any profiler based mocking tool, you have to set the environment variables. Therefore, such libraries - Microsoft Fakes/Typemock Isolator/Telerik JustMock provide small runner to satisfy the requisition, also it is true at Prig. Use prig.exe and run the test as follows(continue in the Package Manager Console):
PM> cd <Your Test Project's Output Directory(e.g. cd .\ConsoleApplicationTest\bin\Debug)>
PM> prig run -process "C:\Program Files (x86)\NUnit 2.6.3\bin\nunit-console.exe" -arguments "ConsoleApplicationTest.dll /domain=None /framework=v4.0"
NUnit-Console version 2.6.3.13283
Copyright (C) 2002-2012 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.
Runtime Environment -
OS Version: Microsoft Windows NT 6.2.9200.0
CLR Version: 2.0.50727.8000 ( Net 3.5 )
ProcessModel: Default DomainUsage: None
Execution Runtime: v4.0
...
Tests run: 3, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0.0934818542535837 seconds
Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0
PM>
If tests have been created, you can refactor illimitably! For example, you probably can find the result of refactoring as follows:
using System;
namespace ConsoleApplication
{
public static class LifeInfo
{
public static bool IsNowLunchBreak()
{
// 1. Add overload to isolate from external environment then call it from original method.
return IsNowLunchBreak(DateTime.Now);
}
public static bool IsNowLunchBreak(DateTime now)
{
// 2. Also, I think the expression '12 <= now.Hour && now.Hour < 13' is too complex.
// Better this way, isn't it?
return now.Hour == 12;
}
// 3. After refactoring, no longer need to use Prig, because you can test this overload.
}
}As just described, Prig helps the code that depends on an untestable library gets trig back. I guarantee you will enjoy your development again!!
For more information, see also Prig's wiki.
To build this project needs the following dependencies:
- Visual Studio 2013(more than Professional Edition because it uses ATL)
- Boost 1.55.0
Extract to C:\boost_1_55_0, and will build with the following options(x86 and x64 libs are required):
CMD boost_1_55_0>cd
C:\boost_1_55_0
CMD boost_1_55_0>bootstrap.bat
Building Boost.Build engine
Bootstrapping is done. To build, run:
.\b2
To adjust configuration, edit 'project-config.jam'.
Further information:
...
CMD boost_1_55_0>.\b2 link=static threading=multi variant=debug,release runtime-link=shared,static -j 4
Building the Boost C++ Libraries.
Performing configuration checks
...
CMD boost_1_55_0>.\b2 link=static threading=multi variant=debug,release runtime-link=shared,static -j 4 --stagedir=.\stage\x64 address-model=64
Building the Boost C++ Libraries.
Performing configuration checks
...
- Google Test 1.6
Extract to C:\gtest-1.6.0, and upgrade C:\gtest-1.6.0\msvc\gtest.sln to Visual Studio 2013. Choose theBuildmenu, and openConfiguration Manager.... OnConfiguration Managerdialog box, in theActive Solution Platformdrop-down list, select the<New...>option. After theNew Solution Platformdialog box is opened, in theType or select the new platformdrop-down list, select a 64-bit platform. Then build all(Debug/Release) configurations. - NUnit 2.6.3.13283
Install using with the installer(NUnit-2.6.3.msi). - Modeling SDK for Microsoft Visual Studio 2013
Install using with the installer(VS_VmSdk.exe). - Microsoft Visual Studio 2013 SDK
Install using with the installer(vssdk_full.exe). - Chocolatey NuGet
Install the instructions in accordance with the page. - NAnt
You can also install in accordance with the help, but the easiest way is using Chocolatey:choco install nant.
After preparing all dependencies, you can build this project in the following steps:
- Run Visual Studio as Administrator, and open Prig.sln(This sln contains some ATL projects, so the build process will modify registry).
- According to the version of the product to use, change the solution configuration and the solution platform and build.
- The results are output to
$(SolutionDir)$(Configuration)\$(PlatformTarget)\.
Run Developer Command Prompt for VS2013 as Administrator, and register dlls that were output to $(SolutionDir)$(Configuration)\$(PlatformTarget)\ to registry and GAC as follows(these are the examples for x86/.NET 3.5, but also another environments are in the same manner):
CMD x86>cd
C:\Prig\Release\x86
CMD x86>regsvr32 /i Urasandesu.Prig.dll
CMD x86>cd "..\..\Release(.NET 3.5)\AnyCPU"
CMD AnyCPU>cd
C:\Prig\Release(.NET 3.5)\AnyCPU
CMD AnyCPU>gacutil /i Urasandesu.NAnonym.dll
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.33440
Copyright (c) Microsoft Corporation. All rights reserved.
Assembly successfully added to the cache
CMD AnyCPU>gacutil /i Urasandesu.Prig.Framework.dll
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.33440
Copyright (c) Microsoft Corporation. All rights reserved.
Assembly successfully added to the cache
CMD AnyCPU>
Unregistration operation is similar in the registration. Run Developer Command Prompt for VS2013 as Administrator and execute the following commands:
CMD x86>cd
C:\Prig\Release\x86
CMD x86>regsvr32 /u Urasandesu.Prig.dll
CMD x86>cd "..\..\Release(.NET 3.5)\AnyCPU"
CMD AnyCPU>cd
C:\Prig\Release(.NET 3.5)\AnyCPU
CMD AnyCPU>gacutil /u "Urasandesu.Prig.Framework, Version=0.1.0.0, Culture=neutral, PublicKeyToken=acabb3ef0ebf69ce, processorArchitecture=MSIL"
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.33440
Copyright (c) Microsoft Corporation. All rights reserved.
Assembly: Urasandesu.Prig.Framework, Version=0.1.0.0, Culture=neutral, PublicKeyToken=acabb3ef0ebf69ce, processorArchitecture=MSIL
Uninstalled: Urasandesu.Prig.Framework, Version=0.1.0.0, Culture=neutral, PublicKeyToken=acabb3ef0ebf69ce, processorArchitecture=MSIL
Number of items uninstalled = 1
Number of failures = 0
CMD AnyCPU>gacutil /u "Urasandesu.NAnonym, Version=0.2.0.0, Culture=neutral, PublicKeyToken=ce9e95b04334d5fb, processorArchitecture=MSIL"
Microsoft (R) .NET Global Assembly Cache Utility. Version 4.0.30319.33440
Copyright (c) Microsoft Corporation. All rights reserved.
Assembly: Urasandesu.NAnonym, Version=0.2.0.0, Culture=neutral, PublicKeyToken=ce9e95b04334d5fb, processorArchitecture=MSIL
Uninstalled: Urasandesu.NAnonym, Version=0.2.0.0, Culture=neutral, PublicKeyToken=ce9e95b04334d5fb, processorArchitecture=MSIL
Number of items uninstalled = 1
Number of failures = 0
CMD AnyCPU>