Code generation, compilation, and naming conventions in Microsoft Fakes
This topic discusses options and issues in Fakes code generation and compilation, and describes the naming conventions for Fakes generated types, members, and parameters.
Requirements
- Visual Studio Ultimate •
In this topic
Code generation and compilation
- Configuring code generation of stubs • Type filtering • Stubbing concrete classes and virtual methods • Internal types • Optimizing build times • Avoiding assembly name clashing
Fakes naming conventions
- Shim type and stub type naming conventions • Shim delegate property or stub delegate field naming conventions • Parameter type naming conventions • Recursive rules
External resources
- Guidance
Code generation and compilation
Configuring code generation of stubs
The generation of stub types is configured in an XML file that has the .fakes file extension. The Fakes framework integrates in the build process through custom MSBuild tasks and detects those files at build time. The Fakes code generator compiles the stub types into an assembly and adds the reference to the project.
The following example illustrates stub types defined in FileSystem.dll:
<Fakes xmlns="https://schemas.microsoft.com/fakes/2011/">
<Assembly Name="FileSystem"/>
</Fakes>
Type filtering
Filters can be set in the .fakes file to restrict which types should be stubbed. You can add an unbounded number of Clear, Add, Remove elements under the StubGeneration element to build the list of selected types.
For example, this .fakes file generates stubs for types under the System and System.IO namespaces, but excludes any type containing "Handle" in System:
<Fakes xmlns="https://schemas.microsoft.com/fakes/2011/">
<Assembly Name="mscorlib" />
<!-- user code -->
<StubGeneration>
<Clear />
<Add Namespace="System!" />
<Add Namespace="System.IO!"/>
<Remove TypeName="Handle" />
</StubGeneration>
<!-- /user code -->
</Fakes>
The filter strings use a simple grammar to define how the matching should be done:
Filters are case-insensitive by default; filters perform a substring matching:
el matches "hello"
Adding ! to the end of the filter will make it a precise case-sensitive match:
el! does not match "hello"
hello! matches "hello"
Adding * to the end of the filter will make it match the prefix of the string:
el* does not match "hello"
he* matches "hello"
Multiple filters in a semicolon-separated list are combined as a disjunction:
el;wo matches "hello" and "world"
Stubbing concrete classes and virtual methods
By default, stub types are generated for all non-sealed classes. It is possible to restrict the stub types to abstract classes through the .fakes configuration file:
<Fakes xmlns="https://schemas.microsoft.com/fakes/2011/">
<Assembly Name="mscorlib" />
<!-- user code -->
<StubGeneration>
<Types>
<Clear />
<Add AbstractClasses="true"/>
</Types>
</StubGeneration>
<!-- /user code -->
</Fakes>
Internal types
The Fakes code generator will generate shim types and stub types for types that are visible to the generated Fakes assembly. To make internal types of a shimmed assembly visible to Fakes and your test assembly, add InternalsVisibleToAttribute attributes to the shimmed assembly code that gives visibility to the generated Fakes assembly and to the test assembly. Here's an example:
// FileSystem\AssemblyInfo.cs
[assembly: InternalsVisibleTo("FileSystem.Fakes")]
[assembly: InternalsVisibleTo("FileSystem.Tests")]
Internal types in strongly named assemblies
If the shimmed assembly is strongly named and you want access internal types of the assembly:
Both your test assembly and the Fakes assembly must be strongly named.
You must add the public keys of the test and Fakes assembly to the InternalsVisibleToAttribute attributes in the shimmed assemblies. Here's how our example attributes in the shimmed assembly code would look when the shimmed assembly is strongly named:
// FileSystem\AssemblyInfo.cs [assembly: InternalsVisibleTo("FileSystem.Fakes", PublicKey=<Fakes_assembly_public_key>)] [assembly: InternalsVisibleTo("FileSystem.Tests", PublicKey=<Test_assembly_public_key>)]
If the shimmed assembly is strongly named, the Fakes framework will automatically strongly sign the generated Fakes assembly. You have to strong sign the test assembly. See Creating and Using Strong-Named Assemblies.
The Fakes framework uses the same key to sign all generated assemblies, so you can use this snippet as a starting point to add the InternalsVisibleTo attribute for the fakes assembly to your shimmed assembly code.
[assembly: InternalsVisibleTo("FileSystem.Fakes, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e92decb949446f688ab9f6973436c535bf50acd1fd580495aae3f875aa4e4f663ca77908c63b7f0996977cb98fcfdb35e05aa2c842002703cad835473caac5ef14107e3a7fae01120a96558785f48319f66daabc862872b2c53f5ac11fa335c0165e202b4c011334c7bc8f4c4e570cf255190f4e3e2cbc9137ca57cb687947bc")]
You can specify a different public key for the Fakes assembly, such as a key you have created for the shimmed assembly, by specifying the full path to the .snk file that contains the alternate key as the KeyFile attribute value in the Fakes\Compilation element of the .fakes file. For example:
<-- FileSystem.Fakes.fakes -->
<Fakes ...>
<Compilation KeyFile="full_path_to_the_alternate_snk_file" />
</Fakes>
You then have to use the public key of the alternate .snk file as the second parameter of the InternalVisibleTo attribute for the Fakes assembly in the shimmed assembly code:
// FileSystem\AssemblyInfo.cs
[assembly: InternalsVisibleTo("FileSystem.Fakes",
PublicKey=<Alternate_public_key>)]
[assembly: InternalsVisibleTo("FileSystem.Tests",
PublicKey=<Test_assembly_public_key>)]
In the example above, the values Alternate_public_key and the Test_assembly_public_key can be the same.
Optimizing build times
The compilation of Fakes assemblies can significantly increase your build time. You can minimize the build time by generating the Fakes assemblies for .NET System assemblies and third-party assemblies in a separate centralized project. Because such assemblies rarely change on your machine, you can reuse the generated Fakes assemblies in other projects.
From your unit test projects, you can simply take a reference to the compiled Fakes assemblies that are placed under the FakesAssemblies in the project folder.
Create a new Class Library with the .NET runtime version matching your test projects. Let’s call it Fakes.Prebuild. Remove the class1.cs file from the project, not needed.
Add reference to all the System and third-party assemblies you need Fakes for.
Add a .fakes file for each of the assemblies and build.
From your test project:
Make sure that you have a reference to the Fakes runtime DLL:
C:\Program Files\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\Microsoft.QualityTools.Testing.Fakes.dll
For each assembly that you have created Fakes for, add a reference to the corresponding DLL file in the Fakes.Prebuild\FakesAssemblies folder of your project.
Avoiding assembly name clashing
In a Team Build environment, all build outputs are merged into a single directory. In the case of multiple projects using Fakes, it might happen that Fakes assemblies from different version override each other. For example, TestProject1 fakes mscorlib.dll from the .NET Framework 2.0 and TestProject2 fakes mscorlib.dll for the .NET Framework 4 would both yield to a mscorlib.Fakes.dll Fakes assembly.
To avoid this issue, Fakes should automatically create version qualified Fakes assembly names for non-project references when adding the .fakes files. A version-qualified Fakes assembly name embeds a version number when you create the Fakes assembly name:
Given an assembly MyAssembly and a version 1.2.3.4, the Fakes assembly name is MyAssembly.1.2.3.4.Fakes.
You can change or remove this version by the editing the Version attribute of the Assembly element in the .fakes:
attribute of the Assembly element in the .fakes:
<Fakes ...>
<Assembly Name="MyAssembly" Version="1.2.3.4" />
...
</Fakes>
Fakes naming conventions
Shim type and stub type naming conventions
Namespaces
.Fakes suffix is added to the namespace.
For example, System.Fakes namespace contains the shim types of System namespace.
Global.Fakes contains the shim type of the empty namespace.
Type names
Shim prefix is added to the type name to build the shim type name.
For example, ShimExample is the shim type of the Example type.
Stub prefix is added to the type name to build the stub type name.
For example, StubIExample is the stub type of the IExample type.
Type Arguments and Nested Type Structures
Generic type arguments are copied.
Nested type structure is copied for shim types.
Shim delegate property or stub delegate field naming conventions
Basic rules for field naming, starting from an empty name:
The method name is appended.
If the method name is an explicit interface implementation, the dots are removed.
If the method is generic, Ofn is appended where n is the number of generic method arguments.
Special method names such as property getter or setters are treated as described in the following table.
If method is… |
Example |
Method name appended |
---|---|---|
A constructor |
.ctor |
Constructor |
A static constructor |
.cctor |
StaticConstructor |
An accessor with method name composed of two parts separated by "_" (such as property getters) |
kind_name (common case, but not enforced by ECMA) |
NameKind, where both parts have been capitalized and swapped |
Getter of property Prop |
PropGet |
|
Setter of property Prop |
PropSet |
|
Event adder |
Add |
|
Event remover |
Remove |
|
An operator composed of two parts |
op_name |
NameOp |
For example: + operator |
op_Add |
AddOp |
For a conversion operator, the return type is appended. |
T op_Implicit |
ImplicitOpT |
Notes
Getters and setters of indexers are treated similarly to the property. The default name for an indexer is Item.
Parameter type names are transformed and concatenated.
Return type is ignored unless there’s an overload ambiguity. If this is the case, the return type is appended at the end of the name
Parameter type naming conventions
Given |
Appended string is… |
---|---|
A typeT |
T The namespace, nested structure, and generic tics are dropped. |
An out parameterout T |
TOut |
A ref parameter ref T |
TRef |
An array typeT[] |
TArray |
A multi-dimensional array type T[ , , ] |
T3 |
A pointer type T* |
TPtr |
A generic typeT<R1, …> |
TOfR1 |
A generic type argument!i of type C<TType> |
Ti |
A generic method argument!!i of method M<MMethod> |
Mi |
A nested typeN.T |
N is appended, then T |
Recursive rules
The following rules are applied recursively:
Because Fakes uses C# to generate the Fakes assemblies, any character that would produce an invalid C# token is escaped to "_" (underscore).
If a resulting name clashes with any member of the declaring type, a numbering scheme is used by appending a two-digit counter, starting at 01.