Unity光照探针自动化:Magic Light Probes原理与性能优化实战
2026/8/4 7:43:17
csharppublic interface IUserRepository{ User GetById(int id); bool Save(User user);}### 1.2 创建测试项目与Fakes程序集1. 在VS2012中创建“单元测试项目”。2. 在测试项目中添加对包含接口的程序集的引用。3. 右键点击引用,选择“生成Fakes程序集”,VS会生成IUserRepository.Fakes命名空间。## 第二部分:第一个Fakes模拟——基础Stub### 2.1 编写第一个测试我们假设业务类UserService依赖IUserRepository,待测方法为GetUserDisplayName。csharp// UserService.cspublic class UserService{ private readonly IUserRepository _repo; public UserService(IUserRepository repo) { _repo = repo; } public string GetUserDisplayName(int id) { var user = _repo.GetById(id); return user == null ? "Unknown" : $"{user.FirstName} {user.LastName}"; }}### 2.2 使用Stub模拟接口在测试方法中,我们创建StubIUserRepository,并指定GetById的行为。csharp// UserServiceTests.csusing Microsoft.QualityTools.Testing.Fakes;using IUserRepository.Fakes; // 自动生成[TestMethod]public void GetUserDisplayName_WhenUserExists_ReturnsFullName(){ // Arrange var stubRepo = new StubIUserRepository() { GetByIdInt32 = (id) => new User { FirstName = "John", LastName = "Doe" } }; var service = new UserService(stubRepo); // Act var result = service.GetUserDisplayName(1); // Assert Assert.AreEqual("John Doe", result);}代码解析:-StubIUserRepository是自动生成的类,所有接口方法都变为可赋值的委托属性。-GetByIdInt32对应GetById(int),Lambda表达式定义了返回值。- 测试完全隔离了数据库,速度快且稳定。## 第三部分:进阶——验证调用与异常模拟### 3.1 验证方法是否被调用TDD不仅关注结果,还关注交互。比如,我们想确保Save方法在特定条件下被调用。csharp[TestMethod]public void SaveUser_WhenValid_ShouldCallRepositorySave(){ // Arrange var saveCalled = false; var stubRepo = new StubIUserRepository() { SaveUser = (user) => { saveCalled = true; return true; } }; var service = new UserService(stubRepo); // Act service.CreateUser(new User { Id = 1, FirstName = "Jane" }); // Assert Assert.IsTrue(saveCalled, "Save should be called");}### 3.2 模拟异常以便测试错误路径接口可能抛异常,我们需要验证业务层是否会正确处理。csharp[TestMethod][ExpectedException(typeof(ArgumentException))]public void GetUserDisplayName_WhenRepoThrows_ShouldWrapException(){ var stubRepo = new StubIUserRepository() { GetByIdInt32 = (id) => { throw new InvalidOperationException("DB down"); } }; var service = new UserService(stubRepo); // 假设业务层会包装异常 service.GetUserDisplayName(1);}技巧:在Fakes中,每个方法属性都可以赋值一个委托,因此异常模拟和返回值模拟一样简单。## 第四部分:Shim(填充)——处理静态方法与密封类### 4.1 为什么需要Shim?有时候我们依赖DateTime.Now或File.ReadAllText等静态方法。Shim允许我们修改这些静态方法的运行时行为,但需在ShimContext中使用。### 4.2 Shim实战示例假设UserService内部调用Logger.Log(静态方法)。我们想验证日志被写入。csharpusing Microsoft.QualityTools.Testing.Fakes.Shims;[TestMethod]public void GetUserDisplayName_WhenUserNotFound_ShouldLogWarning(){ using (ShimsContext.Create()) { // 模拟静态方法 ShimLogger.LogString = (msg) => { loggedMessage = msg; }; var stubRepo = new StubIUserRepository() { GetByIdInt32 = (id) => null }; var service = new UserService(stubRepo); var result = service.GetUserDisplayName(1); Assert.AreEqual("Unknown", result); Assert.IsTrue(loggedMessage.Contains("User 1 not found")); }}注意:Shim必须在ShimsContext中创建,并且所有模拟仅在using块内有效。## 第五部分:TDD实践——从红到绿### 5.1 编写失败的测试(红)假设新需求:UserService需要发送欢迎邮件。我们先写测试,模拟邮件发送接口。csharppublic interface IEmailSender { void Send(string address, string content); }[TestMethod]public void CreateUser_ShouldSendWelcomeEmail(){ var emailSent = false; var stubRepo = new StubIUserRepository() { SaveUser = (u) => true }; var stubEmail = new StubIEmailSender() { SendStringString = (addr, content) => emailSent = true }; var service = new UserService(stubRepo, stubEmail); service.CreateUser(new User { Email = "a@b.com", Id = 1 }); Assert.IsTrue(emailSent);}### 5.2 实现功能(绿)在UserService中,我们添加邮件发送逻辑,测试通过。csharppublic void CreateUser(User user){ if (_repo.Save(user)) { _emailSender.Send(user.Email, "Welcome!"); }}### 5.3 重构此时可以优化代码,比如将发送逻辑提取成独立方法。Fakes让我们在重构时依然保持测试绿。## 第六部分:Fakes高级技巧——行为组合与递归模拟### 6.1 组合多个Stub如果接口方法很多,可以用CallBase属性或自定义辅助方法。csharpprivate static StubIUserRepository CreateDefaultStub(){ var stub = new StubIUserRepository() { GetByIdInt32 = id => new User { Id = id }, SaveUser = u => true, DeleteInt32 = id => true }; return stub;}### 6.2 递归Fakes当接口返回另一个接口时,Fakes可以自动生成嵌套Stub。例如:csharppublic interface IOrderRepository { IInvoice GetInvoice(int orderId); }// 在测试中:var stubOrder = new StubIOrderRepository(){ GetInvoiceInt32 = id => new StubIInvoice() { Amount = 100 }};## 总结本文从Fakes框架的基础概念出发,逐步演示了如何使用Stub和Shim进行接口模拟。我们不仅学会了设置返回值、验证调用、模拟异常,还实践了TDD的红绿循环。VS2012 Fakes框架虽然略有过时,但其“隔离外部依赖”的思想至今仍适用于现代测试框架(如Moq、NSubstitute)。掌握它,你将能在任何遗留系统中快速进行单元测试,让代码质量稳步提升。记住:模拟不是目的,而是手段——它帮助我们聚焦业务逻辑,写出可靠且可维护的测试。