原文:https://fiodar.substack.com/p/integration-testing-dotnet-aspire-apps

对于软件开发来说,拥有自动化的覆盖测试非常重要。尽管手工测试在有些场合存在其价值,自动化测试对于支持我们验证应用的处理逻辑比任何手工测试更加高效。

自动化测试还支持我们检查在某一个地方的变更不会意外地破坏其它的任何部分,这是另一个手工测试复杂应用几乎不可能做到的领域,尽管我们对任何新的功能实现添加了测试 (甚至使用了 TDD 模式,在实现功能之前就增加了测试用例),但我们仍然会拥有之前添加的所有先前功能的所有先前添加的测试。

集成测试是一类特殊的自动化测试,它允许我们验证应用程序或模块是否按预期与其他组件一起工作。这些类型的测试可能使用真实的网络、真实的 HTTP 请求、真实的数据库连接等。

传统上,集成测试相当难以应用于编排的分布式应用程序。这是因为此类应用程序有许多必须相互协调的移动组件。这些组件的计算成本通常很高;因此,集成测试通常是为了模拟有限数量的服务间交互。

但是,.NET Aspire 非常巧妙地解决了这个问题。Microsoft 的设计人员提前考虑了集成测试。因此,针对它运行集成测试几乎与运行低级组件/单元测试一样简单。

此外,Aspire 甚至附带了一个集成测试项目的模板,因此我们不必在每次需要将测试添加到新的分布式应用程序时都搜索 Internet。这就是我们今天要讨论的内容。

在 Aspire 中设置集成测试

最为简单的设置一个测试项目的途径是,在通过 Visual Studio 中创建 Aspire Starter Project 应用的时候,直接选择上 Create test project 选项

如果您是 dotnet CLI 用户,则可以执行以下命令来创建具有所有依赖项的新 Aspire 测试项目:

dotnet new aspire-xunit

默认情况下,Aspire 测试项目模板使用 xUnit。但是,我们将讨论的所有概念也可以在 NUnit 和 MSTest 中实现。Aspire 测试库与框架无关,可与其中任何一个一起使用。

我们甚至不必使用默认测试模板。我们可以使用任何测试框架在 .NET 中创建标准测试项目,然后安装特定于 Aspire 集成测试的依赖项。

安装必须的依赖

在 此 GitHub 存储库中,我们有一个从 Aspire 入门项目模板创建的 Aspire 项目示例。它添加了一个名为 AspireApp.Tests 的测试项目。如果我们打开 AspireApp.Tests.csproj 文件,我们将发现其中引用了 Aspire.Hosting.Testing NuGet 包。这是包含 Aspire 集成测试所需的所有组件的软件包。

项目引用的其余包与测试框架相关,并不特定于 Aspire。如果我们使用不同的测试框架,我们会看到引用不同的包。

因此,我们拥有开始编写测试所需的所有依赖项。当我们创建新的 Aspire 入门项目时,最好从默认提供的测试方法开始。它很简单,很容易让我们了解,但它也演示了 Aspire 测试库的核心功能。

研究 Aspire 的测试方法

如果我们开发 AspireApp.Test 项目中的 WebTests.cs 代码文件,我们将会看到如下的测试方法:

[Fact]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
await using var app = await appHost.BuildAsync();
await app.StartAsync(); // Act
var httpClient = app.CreateHttpClient("webfrontend");
var response = await httpClient.GetAsync("/"); // Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

这是 arrange-act-assert 测试模式的一个相当标准的示例。此模式规定每个测试方法都包含以下不同的部分:

  • Arrange 安排:我们设置了运行测试所需的所有服务。
  • Act 行动:我们执行我们正在测试的操作。
  • Assert 断言:我们将验证此操作的结果。

在此示例中(通常在 C# 中应用),我们用注释标记每个部分,以使测试方法尽可能易于理解。

让我们逐行介绍此方法以了解它在做什么。我们采取的第一个操作如下:

var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();

这段代码使用 Aspire.Hosting.Testing 库中的 DistributedApplicationTestingBuilder 类型。我们调用静态 CreateAsync<T>() 方法来初始化分布式和编排的 Aspire 应用程序。

我们在调用此方法时指定的类型是 Projects.AspireApp_AppHost,它是我们 Aspire Host 项目的自动生成的表示形式。从本质上讲,我们正在创建整个 Aspire 应用程序的测试版本。

我们不需要太深入研究 test host 和 normal host 之间的区别,但了解测试 host 要轻量级得多是有帮助的。但不要被它很轻的事实所欺骗。我们仍然能够测试 Aspire 应用程序的所有核心功能,以及其编排的服务如何相互操作。测试版本的 Host 仍然使用真实网络和其他真实依赖项。

随后的 2 行代码,构建 Host 并启动它。

await using var app = await appHost.BuildAsync();
await app.StartAsync();

请注意,我们将 app 对象(表示整个 Aspire 应用程序)包装在 using 范围内。这是因为它是一个一次性对象,依赖于各种非托管资源,例如网络连接。一旦代码退出 using 范围,它就会完全释放这些资源。

随后的代码是这样的:

var httpClient = app.CreateHttpClient("webfrontend");

这段代码从 Aspire 测试主机 Host 中来创建 HttpClient 类的实例。因为我们从 Aspire 主机 Host 中创建它,所以我们使用 Aspire 服务发现来设置其基本 URL。在此示例中,我们在 webfrontend 的名称下注册一个 Blazor 应用。我们的 HTTP 客户端将使用该应用程序的基本 URL 进行配置。

接下来,我们向应用程序的基址发送一个 GET HTTP 请求,该地址用正斜杠表示:

var response = await httpClient.GetAsync("/");

最后,我们验证我们收到的是 HTTP OK (200) 的响应码。

Assert.Equal(HttpStatusCode.OK, response.StatusCode);

这是我们对使用 xUnit 实现的基本示例测试方法的概述。现在让我们看看如何使用替代测试框架来实现相同的方法。

使用其它的测试框架

如果我们希望使用 NUnit 来替换掉 xUnit 测试框架,我们的测试方法将会变成这样。

[Test]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
await using var app = await appHost.BuildAsync();
await app.StartAsync(); // Act
var httpClient = app.CreateHttpClient("webfrontend");
var response = await httpClient.GetAsync("/"); // Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}

在这种情况下,xUnit 和 NUnit 之间的主要区别在于使用 [Test] 而不是 [Fact],使用 Assert.AreEqual() 而不是 Assert.Equal()。否则,测试的结构和逻辑将保持不变。

如果我们首选的测试框架是 MSTest,则方法将如下所示:

[TestMethod]
public async Task GetWebResourceRootReturnsOkStatusCode()
{
// Arrange
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
await using var app = await appHost.BuildAsync();
await app.StartAsync(); // Act
var httpClient = app.CreateHttpClient("webfrontend");
var response = await httpClient.GetAsync("/"); // Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
}

在 MSTest 中,属性 [TestMethod] 用于指示测试方法。断言是使用 Assert.AreEqual() 完成的,类似于 NUnit。测试的其余部分与原始测试的结构和逻辑保持一致。

扩展测试功能

在前面我们研究的测试方法中,您可能已经注意到的一点是它非常简单。事实上,它是如此简单,以至于它的用处可能非常有限。我们所做的只是向应用程序的主页发送一个非常基本的 HTTP 请求,并验证我们是否收到了 200 响应代码。

改进测试的一种方法是查看响应中返回的内容。这适用于具有用户界面的浏览器内应用程序和无头 API 应用程序。在 .NET 中有很多方法可以做到这一点,每种方法都有其优点和缺点。

我们将使用 AngleSharp NuGet 包,它允许我们解析网页上的 HTML 并轻松搜索其内容。从测试项目中引用此 NuGet 包后,我们可以创建以下帮助程序类,其中包含允许我们读取 HTTP 响应对象并分析其内容的方法:

AngleSharp 是一个 .NET 库,使您能够解析基于尖括号的超文本,如 HTML、SVG 和 MathML。该库还支持未经验证的 XML。AngleSharp 的一个重要方面是 CSS 也可以被解析。包含的解析器基于官方 W3C 规范构建。这将生成给定源代码的完美可移植 HTML5 DOM 表示,并确保与常青浏览器中的结果兼容。此外,标准的 DOM 功能(如 querySelector 或 querySelectorAll)也适用于树遍历。

using AngleSharp;
using AngleSharp.Html.Dom;
using AngleSharp.Io;
using System.Net.Http.Headers; namespace AspireApp.Tests.Helpers; public class HtmlHelpers
{
public static async Task<IHtmlDocument> GetDocumentAsync(HttpResponseMessage response)
{
var content = await response.Content.ReadAsStringAsync();
var document = await BrowsingContext.New()
.OpenAsync(ResponseFactory, CancellationToken.None);
return (IHtmlDocument)document; void ResponseFactory(VirtualResponse htmlResponse)
{
htmlResponse
.Address(response.RequestMessage.RequestUri)
.Status(response.StatusCode); MapHeaders(response.Headers);
MapHeaders(response.Content.Headers); htmlResponse.Content(content); void MapHeaders(HttpHeaders headers)
{
foreach (var header in headers)
{
foreach (var value in header.Value)
{
htmlResponse.Header(header.Key, value);
}
}
}
}
}
}

该方法将一个 HttpResponseMessage 转化为一个 AngleSharp 的 IHtmlDocument 对象,以方便后继的处理。

在我们这个分布式示例应用中,Aspire 应用的前端是一个 Blazor 应用,通过访问独立的 REST API 应用来获得数据,填充到它的气象页面中。

我们开发一个测试用来来检查气象页面是否拥有我们所期望的内容

[Fact]
public async Task GetWeatherReturnsRightContent()
{
// Arrange
var appHost = await DistributedApplicationTestingBuilder
.CreateAsync<Projects.AspireApp_AppHost>();
await using var app = await appHost.BuildAsync();
await app.StartAsync(); // Act
var httpClient = app.CreateHttpClient("webfrontend"); var response = await httpClient.GetAsync("/weather");
var responseBody = await HtmlHelpers.GetDocumentAsync(response);
var descriptionElement = responseBody.QuerySelector("p"); // Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.NotNull(descriptionElement);
Assert.Equal(
"This component demonstrates showing data loaded from a backend API service.",
descriptionElement.InnerHtml);
}

在这个测试方法中,我们不是简单地检查页面是否存在并返回适当的响应代码。我们还通过将页面内容传递给我们之前创建的 helper 方法来读取页面的内容。然后我们搜索第一个段落元素 p 的 HTML 元素,并检查其内容是否为 This component demonstrates showing data loaded from a backend API service.

现在,我们的测试更加有用。

组件测试呢?

虽然集成测试是有益的,而且我们在分布式应用程序中可能需要它们,但我们也可以从测试单个组件中受益。这些类型的测试要快得多,因为它们不依赖于任何真正的网络。他们直接测试代码。

针对 Blazor 应用程序编写组件测试的最佳方法之一是使用 Bunit NuGet 包。这个包甚至得到了 Microsoft 的认可,并在其页面上被提及,尽管它是由开源社区制作的。

我们需要在应用程序中安装两个 NuGet 包:Bunit 和 Bunit.TestDoubles。现在,让我们看看如何使用这个包来测试这个页面:

bUnit is a testing library for Blazor Components. Its goal is to make it easy to write comprehensive, stable unit tests.

这是 Blazor 项目模板中的标准 Counter 页面。它有一个按钮,单击该按钮时,屏幕上的计数会增加。我们可以使用 Bunit 来测试此功能。以下是我们为它编写的测试:

public class CounterTests : BunitTestContext
{
[Fact]
public void CounterStartsAtZero()
{
// Arrange
var cut = RenderComponent<Counter>(); // Assert
cut.Find("p")
.MarkupMatches(
"<p role=\"status\">Current count: 0</p>");
} [Fact]
public void ClickingButtonIncrementsCounter()
{
// Arrange
var cut = RenderComponent<Counter>(); // Act
cut.Find("button").Click(); // Assert
cut.Find("p")
.MarkupMatches(
"<p role=\"status\">Current count: 1</p>");
}
}

第一个测试方法 CounterStartsAtZero() 检查初始计数器值是否为零。它通过直接呈现 Counter 组件并检查相应的 HTML 是否包含 0 作为 count 值来实现这一点。

另一个测试方法 ClickingButtonIncrementsCounter() 检查在单击按钮时计数器是否递增。

总结

与其他等效技术相比,.NET Aspire 不仅允许开发人员相对容易地构建分布式编排应用程序,而且还使针对这些应用程序编写集成测试变得容易。

下次,我们将讨论保护 Aspire 应用程序。我们将讨论如何保护控制面板和托管应用程序,因此请关注此空间。

.NET Aspire Apps 集成测试的更多相关文章

  1. ASP.NET Core 中文文档 第五章 测试(5.2)集成测试

    原文: Integration Testing 作者: Steve Smith 翻译: 王健 校对: 孟帅洋(书缘) 集成测试确保应用程序的组件组装在一起时正常工作. ASP.NET Core支持使用 ...

  2. iOS---The maximum number of apps for free development profiles has been reached.

    真机调试免费App ID出现的问题The maximum number of apps for free development profiles has been reached.免费应用程序调试最 ...

  3. SQLite.Net-PCLUSING SQLITE IN WINDOWS 10 UNIVERSAL APPS

    USING SQLITE IN WINDOWS 10 UNIVERSAL APPS 1.下载SQLite VSIX package并安装 http://sqlite.org/download.html ...

  4. QuanbenSoft Windows Runtime (Windows Store)Apps 应用及其框架总览

    Parrot Simple audio repeater for language learners http://www.windowsphone.com/en-au/store/app/parro ...

  5. 小型文件数据库 (a file database for small apps) SharpFileDB

    小型文件数据库 (a file database for small apps) SharpFileDB For english version of this article, please cli ...

  6. SharpFileDB - a file database for small apps

    SharpFileDB - a file database for small apps 本文中文版在此处. I'm not an expert of database. Please feel fr ...

  7. 重新想象 Windows 8.1 Store Apps (81) - 控件增强: WebView 之加载本地 html, 智能替换 html 中的 url 引用, 通过 Share Contract 分享 WebView 中的内容, 为 WebView 截图

    [源码下载] 重新想象 Windows 8.1 Store Apps (81) - 控件增强: WebView 之加载本地 html, 智能替换 html 中的 url 引用, 通过 Share Co ...

  8. SharePoint 2013技巧分享系列 - 隐藏Blog和Apps左侧导航菜单

    企业内部网中,不需要员工创建Blog或者创建,安装SharePoint应用,因此需要在员工个人Web页面需要隐藏Blog或者Apps导航菜单, 其步骤设置如下: 该技巧适合SharePoint 201 ...

  9. 在线文档预览方案-office web apps续篇

    上一篇在线文档预览方案-office web apps发布后收到很多网友的留言提问,所以准备再写一篇,一来介绍一下域控服务器安装,总结一下大家问的多的问题,二来宣传预览服务安装与技术支持的事情. 阅读 ...

  10. Quick Apps for Sharepoint小型BI解决方案

    Quick Apps for Sharepoint介绍 Quick Apps for Sharepoint前身是Quest Webpart ,由企业软件开发商QuestSoftware开发,Quest ...

随机推荐

  1. Nuxt.js 应用中的 link:prefetch 钩子详解

    title: Nuxt.js 应用中的 link:prefetch 钩子详解 date: 2024/10/7 updated: 2024/10/7 author: cmdragon excerpt: ...

  2. 【Simpleperf】Android的CPU分析,性能优化利器

    很多时候,写代码是一件很爽的事情,但最后需要对APP进行瘦身.性能分析却是一件很棘手的事情.当需要对APP的性能进行分析时,Simpleperf是一个简单快捷的选择. 正文开始前,先奉上官方的资料: ...

  3. 2024年5月中国数据库排行榜:OP持续领跑,GoldenDB稳步上升进前八

    入夏时节,2024年5月的中国流行度排行榜排行榜如期发布.在这个骄阳似火的季节,各大数据库产品之间的竞争愈发激烈,名次间的细微变动展示了市场的动态和活力.各家厂商不断创新,通过技术升级和性能优化,力求 ...

  4. 【首场重磅亮相】KaiwuDB 1.0 时序数据库线上发布会明日开启!邀您共同见证

    首场重磅亮相 KaiwuDB 是浪潮集团控股的数据库企业,以多模数据库为核心,面向工业物联网.数字能源.交通车联网.智慧城市.数字政务等多种场景,提供领先创新的数据服务软件. 新生代数据库,扬帆起航正 ...

  5. PostgreSQL 15 新特性解读 | 墨天轮优质文章合集

    5月19日,PostgreSQL 全球开发组宣布 PostgreSQL 15 的第一个 beta 版本,这一新版本在开发者体验.性能表现等方面都有提升.为了帮助大家更快速了解到PostgreSQL 1 ...

  6. vue前端开发仿钉图系列(2)左侧图层列表的开发详解

    项目开发前还是特别说明一下组件库的重要性,谢谢饿了么团队分享的element组件库,大大节省了页面的开发成本.左侧图层列表核心功能有1.根据图层类型展示点线面2.开关控制右侧地图上点线面的展示和隐藏3 ...

  7. Vue3 的watch 监视属性

    1. 监听单个 watch(变量,(新值,老值)=>{}) 2. 监听多个 watch([变量1,变量2],(新值,老值)=>{}) 3. 监听对象 watch(()=>对象,(新值 ...

  8. 强化学习笔记之【SAC算法】

    强化学习笔记之[SAC算法] 前言: 本文为强化学习笔记第四篇,第一篇讲的是Q-learning和DQN,第二篇DDPG,第三篇TD3 TD3比DDPG少了一个target_actor网络,其它地方有 ...

  9. vue3实现多层级的动态表单增减

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  10. Minio安装以及使用

    Minio介绍 MinIO 是一个基于Apache License v2.0开源协议的对象存储服务.适合于存储大容量非结构化的数据,例如图片.视频.日志文件.备份数据和容器/虚拟机镜像等,而一个对象文 ...