测试夹具(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());
}
现在很明显,有两个需要模拟的依赖项:
内部依赖的模拟通常是为了单元测试目的,使用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);
}
在这里,可以看到:
由于.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);
}
}
}