在C#的单元测试场景中,当被测试的代码依赖外部资源或者难以直接构造的依赖对象时,测试的执行会变得复杂且不稳定。Microsoft Fakes框架提供了垫片(Shim)和存根(Stub)两种隔离机制,能够在测试运行时动态替换依赖的行为,让测试可以脱离真实依赖独立运行。

Microsoft Fakes核心概念
Stub(存根)
Stub是基于接口或抽象类生成的替代实现,适用于被依赖项是接口或者抽象类的场景。它可以在测试时自定义接口方法的返回值,模拟不同的依赖响应,且不需要修改原有代码的逻辑。
Shim(垫片)
Shim可以替换任何.NET方法的实现,包括静态方法、密封类的方法、非虚方法等,即使被依赖项没有接口或抽象类也可以进行隔离。它通过在运行时修改方法调用入口来实现替换,灵活性更高。
环境准备
Microsoft Fakes框架目前仅支持Visual Studio企业版,使用前需要确认开发环境满足要求。首先需要在项目中安装单元测试框架,比如MSTest,然后为需要隔离的项目添加Fakes程序集。
操作步骤:在解决方案资源管理器中,右键点击被测试项目的引用,选择添加Fakes程序集,Visual Studio会自动生成对应的Fakes程序集,通常会在项目的Fakes目录下看到生成的文件。
Stub使用示例
假设我们有一个依赖IUserRepository接口的用户服务类,接口定义如下:
public interface IUserRepository
{
User GetUserById(int id);
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class UserService
{
private readonly IUserRepository _userRepository;
public UserService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
public string GetUserName(int id)
{
var user = _userRepository.GetUserById(id);
return user == null ? "未知用户" : user.Name;
}
}
我们需要测试GetUserName方法,使用Stub隔离IUserRepository的实现:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyProject.Fakes; // 生成的Fakes命名空间
using MyProject.Services;
using MyProject.Models;
[TestClass]
public class UserServiceTests
{
[TestMethod]
public void GetUserName_用户存在_返回用户名()
{
// 创建Stub实例
var stubRepo = new StubIUserRepository
{
// 自定义GetUserById方法的实现
GetUserByIdInt32 = (id) => new User { Id = id, Name = "测试用户" }
};
var service = new UserService(stubRepo);
// 执行测试
var result = service.GetUserName(1);
// 断言
Assert.AreEqual("测试用户", result);
}
[TestMethod]
public void GetUserName_用户不存在_返回未知用户()
{
var stubRepo = new StubIUserRepository
{
GetUserByIdInt32 = (id) => null
};
var service = new UserService(stubRepo);
var result = service.GetUserName(1);
Assert.AreEqual("未知用户", result);
}
}
Shim使用示例
如果被依赖的是静态类或者没有接口的类,就需要使用Shim。比如我们有一个依赖DateTime.Now的工具类,需要测试不同时间下的逻辑:
public class TimeHelper
{
public static string GetCurrentTimeGreeting()
{
var hour = DateTime.Now.Hour;
if (hour < 12)
{
return "上午好";
}
else if (hour < 18)
{
return "下午好";
}
else
{
return "晚上好";
}
}
}
使用Shim替换DateTime.Now的返回值:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Fakes;
using MyProject.Helpers;
[TestClass]
public class TimeHelperTests
{
[TestMethod]
public void GetCurrentTimeGreeting_上午时间_返回上午好()
{
using (ShimsContext.Create())
{
// 替换DateTime.Now的返回值
ShimDateTime.NowGet = () => new DateTime(2023, 1, 1, 8, 0, 0);
var result = TimeHelper.GetCurrentTimeGreeting();
Assert.AreEqual("上午好", result);
}
}
[TestMethod]
public void GetCurrentTimeGreeting_下午时间_返回下午好()
{
using (ShimsContext.Create())
{
ShimDateTime.NowGet = () => new DateTime(2023, 1, 1, 14, 0, 0);
var result = TimeHelper.GetCurrentTimeGreeting();
Assert.AreEqual("下午好", result);
}
}
}
注意使用Shim时需要创建ShimsContext,它会在作用域内生效,离开作用域后替换会失效,避免影响其他测试。
使用注意事项
- Stub仅适用于接口和抽象类的场景,优先使用Stub,因为它的侵入性更低,更符合面向对象的设计原则。
- Shim通过运行时修改方法调用来实现,可能会对测试性能有一定影响,且过度使用会让测试逻辑变得难以理解,非必要场景不要使用。
- 添加Fakes程序集后,每次被依赖项目重新编译,可能需要重新生成Fakes程序集,避免版本不匹配的问题。
- 测试类需要标记
[TestClass]特性,测试方法需要标记[TestMethod]特性,否则测试框架无法识别。
常见问题处理
如果在生成Fakes程序集时出现找不到类型的错误,需要检查被依赖项目的目标框架是否和当前项目兼容,Microsoft Fakes支持大部分.NET Framework和.NET Core/.NET 5+版本,但部分旧版本可能存在兼容问题。
当使用Shim替换方法时,如果替换没有生效,需要确认方法是否是静态方法、是否是密封类的方法,以及是否在ShimsContext的作用域内执行了测试代码。
C#Microsoft_Fakes隔离测试单元测试修改时间:2026-06-18 02:39:26