测试夹具(Fixture)在单元测试和集成测试中的应用

测试夹具(Fixture)不仅在单元测试中非常有用,而且其概念也可以在集成测试中复用。本文将解释什么是测试夹具,以及如何将其应用于单元测试和集成测试。测试夹具有一个共同点:它们为管理测试的“安排”部分,这对于各种类型的测试都非常有用,不仅仅是单元测试。

如果有.NET Core应用程序的测试驱动开发(TDD)经验,包括模拟,并且最好使用.NET Core 3.1,那么本文将对非常有帮助。有测试夹具的经验也很有用,但不是必需的。本文中展示的示例基于xUnit,但这些概念和技术也可以应用于其他单元测试框架。

使用代码

首先,来看一下想要测试的代码。代码如下:

public class SearchEngineController : ControllerBase { private readonly ISearchEngineService _searchEngineService; public SearchEngineController(ISearchEngineService searchEngineService) { _searchEngineService = searchEngineService; } [HttpGet("{queryEntry}", Name = "GetNumberOfCharacters")] public async Task> GetNumberOfCharacters(string queryEntry) { var numberOfCharacters = await _searchEngineService.GetNumberOfCharactersFromSearchQuery(queryEntry); return Ok(numberOfCharacters); } }

从上面的代码可以看出,它只是一个调用某个接口方法并返回结果的控制器方法,状态码为OK(200)。下面是该接口的实现:

public class SearchEngineService : ISearchEngineService { private readonly HttpClient _httpClient; public SearchEngineService(HttpClient httpClient) { _httpClient = httpClient; } public async Task GetNumberOfCharactersFromSearchQuery(string toSearchFor) { var result = await _httpClient.GetAsync($"/search?q={toSearchFor}"); var content = await result.Content.ReadAsStringAsync(); return content.Length; } }

这段代码也非常简单。它执行一个网络请求并返回结果的长度。

依赖项的添加

在Startup类中添加依赖项:

public void ConfigureServices(IServiceCollection services) { services.AddControllers(); var googleLocation = Configuration["Google"]; services.AddHttpClient(c => c.BaseAddress = new Uri(googleLocation)) .SetHandlerLifetime(TimeSpan.FromMinutes(5)) .AddPolicyHandler(GetRetryPolicy()); }

现在很明显,有两个需要模拟的依赖项:

  • 内部依赖:这是注入到控制器中的ISearchEngineService实例。
  • 外部依赖:这是搜索引擎(url)作为HttpClient的基地址添加的。对于集成测试,需要模拟它,因为无法控制它是否给出相同的响应并且保持在线。

内部依赖的模拟

内部依赖的模拟通常是为了单元测试目的,使用Moq进行模拟,如本文所解释。外部依赖的模拟通常是为了集成测试目的,使用WireMock进行模拟,其中一些代码来自本文,所有代码也可以在GitHub上找到。

带有测试夹具的单元测试示例

[Fact] public async Task GetTest() { // arrange var fixture = new Fixture().Customize(new AutoMoqCustomization()); var service = fixture.Freeze>(); service.Setup(a => a.GetNumberOfCharactersFromSearchQuery(It.IsNotNull())) .ReturnsAsync(8); var controller = fixture.Build().OmitAutoProperties().Create(); // act var response = await controller.GetNumberOfCharacters("Hoi"); // assert Assert.Equal(8, ((OkObjectResult)response.Result).Value); service.Verify(s => s.GetNumberOfCharactersFromSearchQuery("Hoi"), Times.Once); }

在这里,可以看到:

  • 第一步是创建测试夹具。
  • 然后调用Freeze方法(在Create方法之前)以指定希望在测试结束时进行验证的内部依赖。
  • 内部依赖需要使用Setup方法进行设置。
  • 在测试方法的断言部分,验证对内部依赖(service实例)的调用。

集成测试的实现

由于.NET Core的最近版本,集成测试变得更加容易。与单元测试的主要区别在于集成测试更加真实(因此不那么可配置)。Startup类和Program类被覆盖。使用真实的内部依赖项而不是模拟/桩。只有外部依赖项需要模拟,因为这些可能会离线,行为可能会改变或不稳定,这些都不应该破坏测试。以下是如何使用测试夹具进行集成测试:

[Fact] public async Task GetTest() { // arrange using (var fixture = new Fixture()) { using (var mockServer = fixture.FreezeServer("Google")) { SetupStableServer(mockServer, "Response"); var controller = fixture.Create(); // act var response = await controller.GetNumberOfCharacters("Hoi"); // assert var request = mockServer.LogEntries.Select(a => a.RequestMessage).Single(); Assert.Contains("Hoi", request.RawQuery); Assert.Equal(8, ((OkObjectResult)response.Result).Value); } } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485