原文: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. Kubernetes基础(基本概念、架构)(十)

    一.介绍 Kubernetes(简称 K8S) 的出现是容器化技术发展的必然结果,容器化是应用程序级别的虚拟化,运行单个内核上有多个独立的用户空间实例,这些实例就是容器:容器提供了将应用程序的代码.运 ...

  2. Android Qcom USB Driver学习(十四)

    UDC-Gadget UDC:(USB Device Controller)用于管理和控制USB设备与主机之间的通信. Gadget:Android在此层实现了adb,mtp(Media Transf ...

  3. NICE与静态优先级的关系

    在Linux系统中,nice值和静态优先级用于控制进程调度的优先级,但它们的范围和含义有所不同.让我们详细解释一下两者的区别和联系. 1. Nice值 范围:nice值的范围是从 -20 到 19. ...

  4. 2021年6月国产数据库排行榜:OceanBase、PolarDB会师TiDB、openGauss,入局开源阵营,逐鹿生态建设

    "首夏犹清和,芳草亦未歇",时至六月,百花齐放.百家争鸣的国产数据库市场依旧延续着如骄阳般火热的态势.不过从最新一期的 国产数据库流行度排行榜 Top 10 中不难发现,一个词足以 ...

  5. 小程序的json文件

    json文件是页面的描述文件,对本页面的窗口外观设置,页面的配置可以覆盖全局的配置 (app.json);

  6. 009 Pycharm的使用(各种骚操作和快捷键)

    博客配套视频链接: https://space.bilibili.com/383551518?spm_id_from=333.1007.0.0 b 站直接看 配套 github 链接:https:// ...

  7. 斜率优化DP简单总结&&“土地购买”题解

    今天刚刷完了斜率优化DP,简单从头回顾一下. \[首先,能写出DP方程应该是最重要的,毕竟斜率只是用来优化的 \] 那么一个DP方程能用斜率优化,具备一种形式: \[f[i]+s1[i]+A[i]*B ...

  8. KubeSphere 社区双周报|2024.03.29-04.11

    KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...

  9. 5.15 相约上海!2021 年度首届云原生 Meetup | KubeSphere & Friends

    时至今日,Kubernetes 虽然变成了云原生这套系统化方法论和开源技术的核心一环,但已经无法独立存在,而是与云原生生态中所有的技术形态息息相关.为了将云原生生态中的各个技术形态结合起来,帮助企业最 ...

  10. FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库

    ijkplayer是一款由B站研发的移动端国产播放器,它基于FFmpeg3.4版本,同时兼容Android和iOS两大移动操作系统.ijkplayer的源码托管地址为https://github.co ...