本文讨论 Fakes 代码生成和编译中的选项和问题,并介绍了 Fakes 生成的类型、成员和参数的命名约定。
要求
Visual Studio Enterprise
.NET Framework 项目
在 Visual Studio 2019 更新 6 中预览的 .NET Core、.NET 5.0 或更高版本及 SDK 样式项目支持功能,并在更新 8 中默认启用。 有关详细信息,请参阅 Microsoft Fakes for .NET Core 和 SDK 样式项目。
代码生成和编译
配置代码生成存根
在扩展名为 .fakes 的 XML 文件中,配置存根类型生成。 Fakes 框架通过自定义 MSBuild 任务集成到生成过程中,并在生成时检测这些文件。 Fakes 代码生成器将存根类型编译到一个程序集,并将该程序集的引用添加到项目中。
以下示例演示 了在FileSystem.dll中定义的存根类型:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">
<Assembly Name="FileSystem"/>
</Fakes>
类型筛选
可以在 .fakes 文件中设置筛选器,以限制应阻止哪些类型。 可以添加一个无限数量的 Clear、Add、Remove 元素,在 StubGeneration 元素下生成所选类型的列表。
例如,以下 .fakes 文件为 System 和 System.IO 命名空间下的类型生成存根,但排除系统中包含“Handle”的任何类型:
<Fakes xmlns="http://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>
筛选器字符串使用简单的语法来定义应如何完成匹配:
默认情况下,筛选器不区分大小写;筛选器执行子字符串匹配:
el匹配“hello”向筛选器末尾添加
!会使其成为区分大小写的精确匹配:el!不匹配“hello”hello!匹配“hello”添加到
*筛选器末尾会使它与字符串的前缀匹配:el*不匹配“hello”he*与“hello”匹配多个筛选器在分号分隔的列表中合并为析取:
el;wo用于匹配“hello”和“world”
存根类和虚拟方法
默认情况下,为所有非密封类生成存根类型。 可以通过 .fakes 配置文件将存根类型限制为抽象类:
<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">
<Assembly Name="mscorlib" />
<!-- user code -->
<StubGeneration>
<Types>
<Clear />
<Add AbstractClasses="true"/>
</Types>
</StubGeneration>
<!-- /user code -->
</Fakes>
内部类型
Fakes 代码生成器为生成的 Fakes 程序集可见的类型生成填充码类型和存根类型。 若要使填充的程序集的内部类型对 Fakes 和测试程序集可见,请在填充的程序集代码中添加 InternalsVisibleToAttribute 属性,赋予生成的 Fakes 程序集和测试程序集的可见性。 下面是一个示例:
// FileSystem\AssemblyInfo.cs
[assembly: InternalsVisibleTo("FileSystem.Fakes")]
[assembly: InternalsVisibleTo("FileSystem.Tests")]
强命名程序集中的内部类型
如果该填充程序集具有强名称,且你想访问程序集的内部类型:
测试程序集和 Fakes 程序集都必须具有强名称。
将测试和 Fakes 程序集的公钥添加到填充程序集中的 InternalsVisibleToAttribute 属性。 下面是填充程序集代码中的示例属性在填充程序集强名称时的外观:
// FileSystem\AssemblyInfo.cs [assembly: InternalsVisibleTo("FileSystem.Fakes", PublicKey=<Fakes_assembly_public_key>)] [assembly: InternalsVisibleTo("FileSystem.Tests", PublicKey=<Test_assembly_public_key>)]
如果填充的程序集具有强名称,则 Fakes 框架会自动对生成的 Fakes 程序集进行强签名。 必须对测试程序集进行强签名。 请参阅 Strong-Named 程序集。
Fakes 框架使用相同的密钥对所有生成的程序集进行签名,因此可以将此代码片段用作起点,将 fakes 程序集的 InternalsVisibleTo 属性添加到填充的程序集代码中。
[assembly: InternalsVisibleTo("FileSystem.Fakes, PublicKey=0024000004800000940000000602000000240000525341310004000001000100e92decb949446f688ab9f6973436c535bf50acd1fd580495aae3f875aa4e4f663ca77908c63b7f0996977cb98fcfdb35e05aa2c842002703cad835473caac5ef14107e3a7fae01120a96558785f48319f66daabc862872b2c53f5ac11fa335c0165e202b4c011334c7bc8f4c4e570cf255190f4e3e2cbc9137ca57cb687947bc")]
可以通过指定包含备用键的 .snk 文件的完整路径作为 KeyFileFakes 文件的元素中的\Compilation属性值,为 Fakes 程序集指定不同的公钥,例如为填充程序集创建的键。 例如:
<-- FileSystem.Fakes.fakes -->
<Fakes ...>
<Compilation KeyFile="full_path_to_the_alternate_snk_file" />
</Fakes>
然后,必须使用备用 .snk 文件的公钥作为 Shimmed 程序代码中 Fakes 程序集的 InternalVisibleTo 属性的第二个参数:
// FileSystem\AssemblyInfo.cs
[assembly: InternalsVisibleTo("FileSystem.Fakes",
PublicKey=<Alternate_public_key>)]
[assembly: InternalsVisibleTo("FileSystem.Tests",
PublicKey=<Test_assembly_public_key>)]
在上面的示例中,值Alternate_public_key和值Test_assembly_public_key可以相同。
优化生成时间
Fakes 程序集的编译可以显著增加构建时间。 可以通过在单独的集中式项目中为 .NET 系统程序集和第三方程序集生成 Fakes 程序集来最大程度地减少生成时间。 由于此类程序集很少在计算机上更改,因此可以在其他项目中重复使用生成的 Fakes 程序集。
在单元测试项目中,添加对编译的 Fakes 程序集的引用,这些程序集放置在项目文件夹中的 FakesAssemblies 下。
使用与测试项目匹配的 .NET 运行时版本创建新的类库。 让我们称之为 Fakes.Prebuild。 从项目中删除 class1.cs 文件,不需要。
添加对需要 Fakes 的所有系统和第三方程序集的引用。
为每个程序集添加一个 .fakes 文件并进行编译。
从您的测试项目中
请确保对 Fakes 运行时 DLL 具有引用:
%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\Microsoft.QualityTools.Testing.Fakes.dll
对于已为其创建 Fakes 的每个程序集,请在项目的 Fakes.Prebuild\FakesAssemblies 文件夹中添加对相应 DLL 文件的引用。
避免程序集名称冲突
在团队生成环境中,所有生成输出都合并到单个目录中。 如果多个项目使用 Fakes,则可能会发生来自不同版本的 Fakes 程序集互相替代的情况。 例如,TestProject1 假冒自 .NET Framework 2.0 的 mscorlib.dll,而 TestProject2 假冒自 .NET Framework 4 的 mscorlib.dll,两者都会生成一个 mscorlib.Fakes.dll 假对象程序集。
为了避免此问题,在添加 .fakes 文件时,Fakes 应该自动为非项目引用创建版本限定的 Fakes 程序集名称。 创建 Fakes 程序集名称时,版本限定的 Fakes 程序集名称将嵌入版本号:
给定程序集 MyAssembly 和版本 1.2.3.4,Fakes 程序集名称为 MyAssembly.1.2.3.4.Fakes。
可以通过编辑 .fakes 中 Assembly 元素的 Version 属性来更改或删除此版本:
attribute of the Assembly element in the .fakes:
<Fakes ...>
<Assembly Name="MyAssembly" Version="1.2.3.4" />
...
</Fakes>
Fakes 命名约定
垫片类型和存根类型命名约定
命名空间
.Fakes 后缀将添加到命名空间。
例如,
System.Fakes命名空间包含 System 命名空间中的填充类型。Global.Fakes 包含空命名空间的 shim 类型。
类型名称
垫片前缀将添加到类型名称,以构建垫片类型名称。
例如,ShimExample 是 Example 类型的垫片类型。
存根前缀被添加到类型名称,以构建存根类型名称。
例如,StubIExample 是 IExample 类型的存根类型。
类型参数和嵌套类型结构
泛型类型参数已被复制。
为垫片类型复制嵌套结构类型。
填充委托属性或存根委托字段命名约定
字段命名的基本规则,从空名称开始:
方法名称追加。
如果方法名称是显式接口的实现,则去掉点号。
如果方法是泛型方法,
Of则追加 n,其中 n 是泛型方法参数的数目。属性 getter 或 setter 等特殊方法名称将按下表中所述进行处理:
| 如果方法为... | Example | 已附加方法名称 |
|---|---|---|
| 构造函数 | .ctor |
Constructor |
| 静态 构造函数 | .cctor |
StaticConstructor |
| 方法名为由“_”分隔的两个部分组成的访问器(例如属性获取函数) | kind_name (常见案例,但未由 ECMA 强制执行) | NameKind,其中两个部分都换了位置并且首字母大写。 |
属性Prop的取值器 |
PropGet |
|
属性的设置器 Prop |
PropSet |
|
| 事件添加器 | Add |
|
| 事件删除器 | Remove |
|
| 由两个部分组成的运算符 | op_name |
NameOp |
| 例如:+ 运算符 | op_Add |
AddOp |
| 对于 转换运算符,将追加返回类型。 | T op_Implicit |
ImplicitOpT |
注释
-
索引器的 Getter 和 setter 与属性类似。 索引器的默认名称为
Item。 - 参数类型 的名称被转换并连接。
- 除非存在重载歧义,否则将忽略返回类型。 如果存在重载歧义,则返回类型将追加到名称末尾。
参数类型命名约定
| 给定值 | 追加字符串为... |
|---|---|
类型T |
T 将删除命名空间、嵌套结构和泛型标记。 |
out 参数out T |
TOut |
ref 参数ref T |
TRef |
数组类型T[] |
TArray |
多维数组类型T[ , , ] |
T3 |
指针类型T* |
TPtr |
泛型类型T<R1, ...> |
TOfR1 |
类型为泛型类型参数!iC<TType> |
Ti |
方法的泛型参数!!iM<MMethod> |
Mi |
嵌套类型N.T |
N 追加,然后 T |
递归规则
以递归方式应用以下规则:
由于 Fakes 使用 C# 生成 Fakes 程序集,因此生成无效 C# 令牌的任何字符都将转义为“_”(下划线)。
如果生成的名称与声明类型中的任何成员发生冲突,则会使用一种编号方案,从 01 开始,追加一个两位数字的计数器。
在持续集成中使用 Microsoft Fakes
Microsoft Fakes 程序集生成
Microsoft Fakes 是 Visual Studio Enterprise 中独家提供的一项功能。 因此,生成 Fakes 程序集需要在生成项目时使用 Visual Studio 生成任务 。
注释
另一种策略涉及将 Fakes 程序集直接提交到持续集成(CI)系统,并使用MSBuild 任务。 如果选择此方法,则需要确保在测试项目中包括对生成的 Fakes 程序集的程序集引用,如以下代码片段所示:
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Reference Include="FakesAssemblies\System.Fakes.dll"/>
</ItemGroup>
</Project>
必须手动添加此引用,特别是 SDK 样式项目(即 .NET Core、.NET 5+和 .NET Framework),因为这些项目现在隐式添加程序集引用。 如果决定使用此方法,请确保在父程序集发生更改时更新 Fakes 程序集。