当前位置: 首页 > article >正文

掌握 .NET 8 中最小 API 的单元和集成测试:高质量代码的最佳实践

在 .NET 8 中开发最小 API 时,测试是确保 API 可靠、可扩展且可维护的关键步骤。结构良好的单元和集成测试可以显著提高 API 的质量,帮助您及早发现错误,并保证您的代码在各种场景中都能按预期运行。

在这篇博文中,我们将介绍如何在 .NET 8 中为最小 API 实现单元和集成测试,并介绍帮助您交付高质量 API 的最佳实践。


为什么测试最少的 API 是必不可少的

最小的 API 虽然轻量级且易于实现,但需要彻底测试,就像任何其他 API 一样。单元测试验证单个组件或方法的行为,而集成测试确保整个系统(包括数据库或其他 API 等外部依赖项)正常工作。

测试您的 Minimal API 有几个好处:

  • 在进行更新或重构时防止回归
  • 通过确保每个组件按预期工作来提高可维护性
  • 通过在开发过程的早期捕获问题来减少 bug
  • 确保不同环境和用例的可靠性。

1. 为最小 API 设置单元测试

单元测试涉及测试应用程序中的最小部分(通常是单个方法或函数),这些部分与其依赖项隔离开来。

步骤 1:创建单元测试项目

首先,在解决方案中创建一个单元测试项目。如果您使用的是 xUnit(最流行的 .NET 测试框架之一),则可以使用以下命令创建测试项目:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>dotnet new xunit <span style="color:var(--syntax-error-color)">-n</span> MinimalApiTests
</code></span></span>

在测试项目中添加对 Minimal API 项目的引用:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>dotnet add reference ../MinimalApiProject/MinimalApiProject.csproj
</code></span></span>
第 2 步:模拟依赖项

由于单元测试应该独立运行,因此您需要模拟依赖项,例如数据库访问或外部服务。对于模拟,您可以使用 Moq,一个流行的模拟库:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>dotnet add package Moq
</code></span></span>

下面是一个如何模拟服务并测试依赖于它的方法的示例:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">interface</span> <span style="color:var(--syntax-name-color)">IWeatherService</span>
<span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-text-color)">Task</span><span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-declaration-color)">string</span><span style="color:var(--syntax-text-color)">></span> <span style="color:var(--syntax-name-color)">GetWeatherAsync</span><span style="color:var(--syntax-text-color)">();</span>
<span style="color:var(--syntax-text-color)">}</span>

<span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">class</span> <span style="color:var(--syntax-name-color)">WeatherApi</span>
<span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">private</span> <span style="color:var(--syntax-declaration-color)">readonly</span> <span style="color:var(--syntax-text-color)">IWeatherService</span> <span style="color:var(--syntax-text-color)">_weatherService</span><span style="color:var(--syntax-text-color)">;</span>

    <span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-name-color)">WeatherApi</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">IWeatherService</span> <span style="color:var(--syntax-text-color)">weatherService</span><span style="color:var(--syntax-text-color)">)</span>
    <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-text-color)">_weatherService</span> <span style="color:var(--syntax-text-color)">=</span> <span style="color:var(--syntax-text-color)">weatherService</span><span style="color:var(--syntax-text-color)">;</span>
    <span style="color:var(--syntax-text-color)">}</span>

    <span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">async</span> <span style="color:var(--syntax-text-color)">Task</span><span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-declaration-color)">string</span><span style="color:var(--syntax-text-color)">></span> <span style="color:var(--syntax-name-color)">GetWeather</span><span style="color:var(--syntax-text-color)">()</span>
    <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-declaration-color)">await</span> <span style="color:var(--syntax-text-color)">_weatherService</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">GetWeatherAsync</span><span style="color:var(--syntax-text-color)">();</span>
    <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-text-color)">}</span>
</code></span></span>

对于单元测试:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">class</span> <span style="color:var(--syntax-name-color)">WeatherApiTests</span>
<span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-text-color)">Fact</span><span style="color:var(--syntax-text-color)">]</span>
    <span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">async</span> <span style="color:var(--syntax-text-color)">Task</span> <span style="color:var(--syntax-name-color)">GetWeather_ReturnsExpectedWeather</span><span style="color:var(--syntax-text-color)">()</span>
    <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-comment-color)">// Arrange</span>
        <span style="color:var(--syntax-declaration-color)">var</span> <span style="color:var(--syntax-text-color)">mockWeatherService</span> <span style="color:var(--syntax-text-color)">=</span> <span style="color:var(--syntax-declaration-color)">new</span> <span style="color:var(--syntax-text-color)">Mock</span><span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-text-color)">IWeatherService</span><span style="color:var(--syntax-text-color)">>();</span>
        <span style="color:var(--syntax-text-color)">mockWeatherService</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">Setup</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">service</span> <span style="color:var(--syntax-text-color)">=></span> <span style="color:var(--syntax-text-color)">service</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">GetWeatherAsync</span><span style="color:var(--syntax-text-color)">()).</span><span style="color:var(--syntax-name-color)">ReturnsAsync</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"Sunny"</span><span style="color:var(--syntax-text-color)">);</span>
        <span style="color:var(--syntax-declaration-color)">var</span> <span style="color:var(--syntax-text-color)">weatherApi</span> <span style="color:var(--syntax-text-color)">=</span> <span style="color:var(--syntax-declaration-color)">new</span> <span style="color:var(--syntax-name-color)">WeatherApi</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">mockWeatherService</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-text-color)">Object</span><span style="color:var(--syntax-text-color)">);</span>

        <span style="color:var(--syntax-comment-color)">// Act</span>
        <span style="color:var(--syntax-declaration-color)">var</span> <span style="color:var(--syntax-text-color)">result</span> <span style="color:var(--syntax-text-color)">=</span> <span style="color:var(--syntax-declaration-color)">await</span> <span style="color:var(--syntax-text-color)">weatherApi</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">GetWeather</span><span style="color:var(--syntax-text-color)">();</span>

        <span style="color:var(--syntax-comment-color)">// Assert</span>
        <span style="color:var(--syntax-text-color)">Assert</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">Equal</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"Sunny"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-text-color)">result</span><span style="color:var(--syntax-text-color)">);</span>
    <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-text-color)">}</span>
</code></span></span>

此测试模拟并确保该方法返回预期结果,而无需实际调用真实的 weather 服务。IWeatherServiceWeatherApi.GetWeather()


2. 为最少的 API 编写集成测试

与单元测试不同,集成测试可确保系统的不同部分协同工作,包括外部依赖项,例如数据库、第三方服务或文件系统。集成测试的范围通常更广,对于测试 Minimal API 的真实行为至关重要。

第 1 步:使用 WebApplicationFactory 进行集成测试

.NET 使使用 WebApplicationFactory 为 Minimal API 编写集成测试变得容易。此工厂设置一个测试服务器来模拟实际的 API 环境,允许您针对 API 运行请求并验证响应。

安装必要的测试包:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package Microsoft.EntityFrameworkCore.InMemory
</code></span></span>

然后,创建集成测试:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">class</span> <span style="color:var(--syntax-name-color)">WeatherApiIntegrationTests</span> <span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-text-color)">IClassFixture</span><span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-text-color)">WebApplicationFactory</span><span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-text-color)">Program</span><span style="color:var(--syntax-text-color)">>></span>
<span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">private</span> <span style="color:var(--syntax-declaration-color)">readonly</span> <span style="color:var(--syntax-text-color)">HttpClient</span> <span style="color:var(--syntax-text-color)">_client</span><span style="color:var(--syntax-text-color)">;</span>

    <span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-name-color)">WeatherApiIntegrationTests</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">WebApplicationFactory</span><span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-text-color)">Program</span><span style="color:var(--syntax-text-color)">></span> <span style="color:var(--syntax-text-color)">factory</span><span style="color:var(--syntax-text-color)">)</span>
    <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-text-color)">_client</span> <span style="color:var(--syntax-text-color)">=</span> <span style="color:var(--syntax-text-color)">factory</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">CreateClient</span><span style="color:var(--syntax-text-color)">();</span>
    <span style="color:var(--syntax-text-color)">}</span>

    <span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-text-color)">Fact</span><span style="color:var(--syntax-text-color)">]</span>
    <span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">async</span> <span style="color:var(--syntax-text-color)">Task</span> <span style="color:var(--syntax-name-color)">GetWeather_ReturnsExpectedResult</span><span style="color:var(--syntax-text-color)">()</span>
    <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-comment-color)">// Act</span>
        <span style="color:var(--syntax-declaration-color)">var</span> <span style="color:var(--syntax-text-color)">response</span> <span style="color:var(--syntax-text-color)">=</span> <span style="color:var(--syntax-declaration-color)">await</span> <span style="color:var(--syntax-text-color)">_client</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">GetAsync</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"/weather"</span><span style="color:var(--syntax-text-color)">);</span>
        <span style="color:var(--syntax-text-color)">response</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">EnsureSuccessStatusCode</span><span style="color:var(--syntax-text-color)">();</span>
        <span style="color:var(--syntax-declaration-color)">var</span> <span style="color:var(--syntax-text-color)">result</span> <span style="color:var(--syntax-text-color)">=</span> <span style="color:var(--syntax-declaration-color)">await</span> <span style="color:var(--syntax-text-color)">response</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-text-color)">Content</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">ReadAsStringAsync</span><span style="color:var(--syntax-text-color)">();</span>

        <span style="color:var(--syntax-comment-color)">// Assert</span>
        <span style="color:var(--syntax-text-color)">Assert</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">Equal</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"Sunny"</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-text-color)">result</span><span style="color:var(--syntax-text-color)">);</span>
    <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-text-color)">}</span>
</code></span></span>

在此示例中,WebApplicationFactory 会启动 API 的测试实例,您可以使用 .HttpClient

第 2 步:在集成测试中模拟外部依赖项

对于集成测试,模拟数据库等外部依赖项非常重要,以确保您的测试一致运行,而无需真正的数据库连接。您可以使用 InMemory 数据库或模拟服务。

配置 InMemory 数据库的示例:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">class</span> <span style="color:var(--syntax-name-color)">StartupTest</span>
<span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">public</span> <span style="color:var(--syntax-declaration-color)">void</span> <span style="color:var(--syntax-name-color)">ConfigureServices</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">IServiceCollection</span> <span style="color:var(--syntax-text-color)">services</span><span style="color:var(--syntax-text-color)">)</span>
    <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-text-color)">services</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-text-color)">AddDbContext</span><span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-text-color)">AppDbContext</span><span style="color:var(--syntax-text-color)">>(</span><span style="color:var(--syntax-text-color)">options</span> <span style="color:var(--syntax-text-color)">=></span>
            <span style="color:var(--syntax-text-color)">options</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">UseInMemoryDatabase</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"TestDb"</span><span style="color:var(--syntax-text-color)">));</span>
    <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-text-color)">}</span>
</code></span></span>

这允许您在隔离环境中测试 API 与数据库的交互。


3. 单元和集成测试的最佳实践

为确保您的测试有效且可维护,请遵循以下最佳实践:

1. 为 Happy 和 Unhappy paths 编写测试
  • 不仅要测试预期的场景 (满意路径),还要测试边缘情况、错误处理和无效输入 (不满意路径)。
2. 保持单元测试的隔离
  • 通过使用模拟对象和依赖项注入,确保单元测试与外部系统隔离。
3. 在 CI/CD 中自动执行测试
  • 使用持续集成 (CI) 工具(如 GitHub ActionsAzure Pipelines 或 Jenkins)在每次提交时自动运行测试。
4. 确保快速反馈
  • 保持单元测试的轻量级和快速性。集成测试可能需要更长的时间,但通过专注于核心交互来确保它们高效运行。
5. 关注可读性和可维护性
  • 编写清晰、描述性的测试名称,并保持测试小而有重点。这提高了可读性,并帮助未来的开发人员了解每个测试的目的。
6. 使用内存数据库进行集成测试
  • 为避免在集成测试中对数据库进行复杂的设置,请使用 SQLite 或 Entity Framework InMemory 等内存中数据库来模拟真实的数据库行为。
7. 确定测试覆盖率的优先级
  • 以高测试覆盖率为目标,但也要确保您的测试有意义。专注于关键业务逻辑和 API 终端节点。

4. 运行测试并分析结果

使用 dotnet test 命令在 .NET 中运行测试非常简单:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>dotnet <span style="color:var(--syntax-text-color)">test</span>
</code></span></span>

此命令将运行您的所有单元和集成测试,并在控制台中显示结果。对于详细报告,您可以与提供测试结果图形视图的测试报告工具或 CI/CD 平台集成。


结论

测试是构建高质量 API 的一个重要方面。通过遵循本指南中概述的单元和集成测试策略,您可以确保 .NET 8 中的最小 API 可靠、可维护并准备好用于生产。从模拟单元测试中的依赖项到使用 WebApplicationFactory 进行集成测试,每一层测试在 API 的稳定性中都起着至关重要的作用。

确保在您的项目中采用这些最佳实践,您将看到代码质量和 API 部署的信心得到提高。


http://www.kler.cn/a/385860.html

相关文章:

  • Chrome和Chromium的区别?浏览器引擎都用的哪些?浏览器引擎的作用?
  • Word_小问题解决_1
  • 大数据实验9:Spark安装和编程实践
  • Redis在高性能缓存中的应用
  • 第三百二十三节 Java线程教程 - Java同步器
  • Mac终端字体高亮、提示插件
  • CTF —— 网络安全大赛
  • ABAP开发-12、Dialog屏幕开发_1
  • 设计模式-构建者
  • IO作业day4
  • redis:list列表命令和内部编码
  • jenkins使用slave节点进行node打包报错问题处理
  • 【JAVA毕业设计】基于Vue和SpringBoot的技术交流分享平台
  • C++ 游戏开发:打造高效、性能优越的游戏世界
  • 【观察】华为持续投入开源开放“结硕果”,openEuler走出操作系统“创新路”...
  • 怎么对 PDF 添加权限密码或者修改密码-免费软件分享
  • .NET中通过C#实现Excel与DataTable的数据互转
  • Docker使用相关记录
  • 【PGCCC】postgresql 缓存池并发设计
  • 如何准备验厂及验厂证书有效期
  • 开源模型应用落地-glm模型小试-glm-4-9b-chat-Gradio集成(三)
  • 什么是jQuery
  • 光耦合器在现代电子学中的关键应用
  • H5播放器EasyPlayer.js 流媒体播放器是否支持npm(yarn) install 安装?
  • 设置agetty启动参数登录Linux
  • 大模型自动构建知识图谱/GraphRAG/neo4j可视化/问答系统探索