集成测试:比 AutoFixture 更多的 Fixture





5.00/5 (1投票)
解释 Fixture 的概念以及它如何用于单元测试和集成测试。
引言
正如在AutoFixture中使用的 Fixture 一样,它已被证明是测试自动化中非常有用的概念。Fixture 不仅对单元测试有用,而且这个概念在集成测试中也是可重复使用的。本文解释了 Fixture 是什么以及如何在单元测试和集成测试中使用这个概念。Fixture 总有一个共同点:它们为你管理测试的“准备”部分,这对各种测试都有用,而不仅仅是单元测试。
背景
如果你有一些 .NET Core 应用程序 TDD 的经验,包括模拟,最好是使用本文中使用的 .NET Core 3.1,这将非常有帮助。拥有 Fixture 的经验也很有用,但不是必需的。本文中显示的示例基于 xUnit,但这些概念和技术也可以应用于其他单元测试框架。
Using the Code
首先,我们来看看我们要测试的代码。代码如下:
[Route("api/[controller]")]
[ApiController]
public class SearchEngineController : ControllerBase
{
private readonly ISearchEngineService _searchEngineService;
public SearchEngineController(ISearchEngineService searchEngineService)
{
_searchEngineService = searchEngineService;
}
[HttpGet("{queryEntry}", Name = "GetNumberOfCharacters")]
public async Task<ActionResult<int>> 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<int> 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<ISearchEngineService, SearchEngineService>(c =>
c.BaseAddress = new Uri(googleLocation))
.SetHandlerLifetime(TimeSpan.FromMinutes(5))
.AddPolicyHandler(GetRetryPolicy());
}
现在可以清楚地看到,我们需要模拟两个依赖项
- **内部依赖项:**这是注入到控制器的
ISearchEngineService
实例。 - **外部依赖项:**这是作为添加的
HttpClient
的基地址设置的搜索引擎(URL)。对于集成测试,我们需要模拟它,因为我们无法控制它是否给出相同的响应以及是否保持在线。
内部依赖项的模拟通常用于单元测试目的,使用 Moq,如这篇文章中所述。外部依赖项的模拟通常用于集成测试目的,使用 WireMock,如这篇文章中所述,这里介绍的一些代码就来自这篇文章。这里显示的所有代码也都在GitHub上可用。
使用 Fixture 的单元测试如下所示
[Fact]
public async Task GetTest()
{
// arrange
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var service = fixture.Freeze<Mock<ISearchEngineService>>();
service.Setup(a => a.GetNumberOfCharactersFromSearchQuery(It.IsNotNull<string>()))
.ReturnsAsync(8);
var controller = fixture.Build<SearchEngineController>().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);
}
在这里,我们看到以下内容
- 第一步是创建 Fixture。
- 之后,调用
Freeze
方法(在Create
方法之前)来指定我们想要在测试结束时对其进行一些验证的**内部**依赖项。 - 需要使用
Setup
方法设置**内部**依赖项。 - 在测试方法的断言部分,我们验证对**内部**依赖项(
service
实例)的**调用**。
实现这样的测试需要一个 NuGet 包:AutoFixture.AutoMoq。
自从 .NET Core 的最新版本以来,集成测试变得容易多了。与单元测试的主要区别在于集成测试更真实(因此可配置性更低)。Startup
类和Program
类都被包含在内。使用真实的内部依赖项而不是模拟/存根。只有外部依赖项需要模拟,因为这些依赖项可能脱机、行为变化或不稳定,而所有这些都不应该破坏测试。以下是使用 Fixture 进行集成测试的方法
[Fact]
public async Task GetTest()
{
// arrange
using (var fixture = new Fixture<Startup>())
{
using (var mockServer = fixture.FreezeServer("Google"))
{
SetupStableServer(mockServer, "Response");
var controller = fixture.Create<SearchEngineController>();
// 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);
}
}
}
private void SetupStableServer(FluentMockServer fluentMockServer, string response)
{
fluentMockServer.Given(Request.Create().UsingGet())
.RespondWith(Response.Create().WithBody(response, encoding: Encoding.UTF8)
.WithStatusCode(HttpStatusCode.OK));
}
在这里,我们看到类似的内容
- 第一步是使用
Startup
类创建 Fixture,以便在调用Create
方法后初始化所有依赖项。 - 之后,调用
FreezeServer
方法(在Create
方法之前)来设置我们想要在测试结束时对其进行一些验证的**外部**依赖项。 - 需要使用自编写的
SetupStableServer
方法设置内部依赖项,该方法描述了特定请求需要特定响应。这将在此处更详细地解释。 - 在测试方法的断言部分,我们验证发送到**外部**依赖项(模拟服务)的**网络请求**。
实现这样的测试需要另一个 NuGet 包:ConnectingApps.IntegrationFixture。
从上面的代码可以清楚地看出,使用 Fixture 进行集成测试与使用 Fixture 进行单元测试基本上是相同的。但是,需要用外部依赖项来安排和验证外部依赖项,而不是内部依赖项。这听起来很明显,但是当在不使用 Fixture 的情况下设置集成测试时,需要很多样板代码,如此处所述。
关注点
在我学习 Fixture、使用 Fixture 和撰写本文时,我清楚地认识到 Fixture 的概念可以以多种方式使用。AutoFixture 以其在编写单元测试的准备部分时的实用性而闻名,但 Fixture 也可用于其他类型的测试(的准备部分)。它可以帮助我们开发者节省编写大量样板代码的工作。
历史
- 2020年5月13日:初始版本