任何应用的成功都取决于用户使用它的难易程度。任何软件产品的寿命都直接取决于产品的质量。
测试是软件开发生命周期 ( SDLC )的一个重要方面,确保产品满足客户的要求和质量要求。随着我们进入 SDLC 的后期阶段,修复 bug 的成本增加,测试也很重要。
在本章中,我们将了解不同类型的测试和 Visual Studio 为测试提供的工具,以及我们可以用来确保我们构建的产品质量的第三方工具.NET 5。
在本章中,我们将了解以下内容:
- 测试类型
- 理解单元测试
- 理解功能测试
- 理解负载测试的重要性
到本章结束时,您将了解确保产品质量所需了解的一切。
您将需要 Visual Studio 2019 企业版。这只是付费版,可以从https://visualstudio.microsoft.com/下载。
你还需要对微软. NET 有一个基本的了解
软件测试是检查应用是否按照预期运行的一种方式。这些期望可能与功能性、响应性或软件运行时消耗的资源有关。
根据软件测试的执行方式,软件测试可以大致分为以下两类:
- 手动测试:在手动测试中,测试人员通过使用被测应用并验证预期结果,手动执行测试用例。手动测试需要比替代方法更多的努力。
- 自动化测试:自动化测试由专门的自动化测试软件执行。这个自动化的软件在一个专门的环境中运行在被测试的应用上,并验证预期的输出。自动化测试节省了大量的时间和人力。在某些情况下,要实现 100%自动化并以少得多的投资回报来维护汽车,可能需要付出很大的努力。
根据关于被测应用内部的已知信息,如代码流、相关模块集成等,软件测试也可以用以下方式进行广泛分类:
- 黑盒测试:在黑盒测试中,负责测试的个人没有关于系统内部的信息。这里的重点是系统的行为。
- 白盒测试:在白盒测试中,测试仪有关于系统内部结构、设计和实现的信息。白盒测试的重点是测试实现中存在的替代路径。
在软件测试中,我们验证应用的不同方面。
软件测试也有以下变体,基于它验证的应用的方面和它使用的工具或框架:
- 单元测试:单元测试关注于应用的最小单元。这里我们验证单个类或函数。这主要是在开发阶段完成的。
- 功能测试:这通常被称为集成测试。其主要目标是确保应用按照要求运行。
- 回归测试:回归测试确保任何最近的更改没有对应用性能产生不利影响,并且现有的功能不会受到任何更改的影响。在回归测试中,根据应用中引入的变化,执行全部或部分功能测试用例。
- 冒烟测试 : A 冒烟测试在每次部署后进行,以确保应用稳定并准备好部署。这是也称为构建验证测试 ( BVT )。
- 负载测试:负载测试用于确定系统的整体有效性。在负载测试期间,我们模拟集成系统上的预计负载。
- 压力测试:在压力测试中,我们将系统推到预期的容量或负载之外。这有助于我们识别系统中的瓶颈并识别故障点。性能测试是用于压力和负载测试的总称。
- 安全测试:安全测试是进行的,以确保应用的完美执行。在安全测试中,我们重点评估安全方面的各种元素,例如完整性、保密性和真实性等。
- 可访问性测试:可访问性测试旨在确定不同能力的个人是否能够使用某个应用。
既然我们已经看到了不同类型的测试,在接下来的部分中,我们将详细介绍单元测试、功能测试和负载测试,因为它们对于确保应用的稳定性至关重要。
注意
要进一步了解安全性,请尝试使用静态代码分析工具 : 进行安全性测试。更多关于无障碍的信息可以在这里找到:无障碍测试:https://accessibilityinsights.io/。
性能测试、可访问性测试和安全性测试是我们用来评估应用的非功能方面的测试,如性能、可用性、可靠性、安全性和可访问性。
现在让我们看看如何为我们的电子商务应用执行单元测试。
单元测试是一种测试应用最小隔离单元的方法。这是软件开发中的一个重要步骤,有助于尽早隔离问题。
单元测试对我们构建的软件质量有直接影响。总是建议一写任何方法就把单元测试写成。如果我们遵循测试驱动开发 ( TDD )的方法论,我们首先编写测试用例,然后继续实现功能。
在下一节中,我们将学习如何创建单元测试并从 Visual Studio 中运行它们。
我们选择使用 Visual Studio,因为它有强大的工具来创建和管理测试用例。
使用 Visual Studio,我们可以创建、调试和运行单元测试用例。我们还可以检查执行的测试的代码覆盖率。此外,它还有一个实时单元测试功能,在我们修改代码时运行单元测试用例,并实时显示结果。
我们将在后续章节中探讨所有这些特性。
让我们继续并创建一个单元测试项目来在Packt.ECommerce.Order
项目上执行单元测试。
执行以下步骤来创建单元测试用例:
-
Add a new project of the
MSTest
Test Project (.NET Core) type to the solution under theTests
folder and name the projectPackt.ECommerce.Order.UnitTest
:图 15.1–带有的 Visual Studio MSTest 测试项目.NET Core
-
Once the project is added, the Solution structure will look like the following screenshot:
图 15.2–测试项目创建后的解决方案结构
向新创建的测试项目添加
Packt.ECommerce.Order
的项目引用。 -
给测试项目增加一个新类,并命名为
OrdersControllerTest
。我们将在这个类中添加所有与OrderController
相关的测试用例。 -
For the
Test
framework to detect the test class, the class should be attributed withTestClass
, as follows:[TestClass] public class OrdersControllerTest { }
现在我们添加一个简单的测试来测试
OrderController
控制器的构造器。我们将要执行的测试是断言OrderController
控制器的成功创建。现在让我们添加测试,如下面的代码所示:[TestMethod] public async Task OrderController_Constructor() { OrdersController testObject = new OrdersController(null); Assert.IsNotNull(testObject); }
OrderController_Constructor
测试方法归于TestMethod
;这是测试框架发现测试方法所必需的。这里我们通过检查创建的对象的空条件来断言。 -
Visual Studio 提供测试资源管理器来管理和运行测试。我们去测试 | 测试浏览器打开,如图图 15.3 。
-
构建解决方案,查看测试资源管理器中的测试。
-
In Test Explorer, we can see all the tests that were present in the solution. We can see the
OrderController_Constructor
test we created here:图 15.3–Visual Studio 测试资源管理器窗口
-
Next, run the test by right-clicking on the test case and selecting Run from the context menu:
图 15.4–测试资源管理器窗口中的测试运行上下文菜单
-
Once the test is executed, we can see the test result in the right pane. From the result, we can see that the test executed and runs successfully, as follows:
图 15.5–测试资源管理器的测试结果
我们在 Visual Studio 中创建和执行了一个简单的测试。在下一节中,我们将学习如何模拟OrdersController
的依赖关系来验证功能。
通常,被测试的方法调用其他外部方法或服务,我们称之为依赖。为了确保被测方法的功能,我们通过为依赖关系创建模拟对象来隔离依赖关系的行为。
在应用中,类可能依赖于其他类;例如,我们的OrdersController
依赖于OrderService
。在测试OrdersController
的时候,我们应该隔离OrderService
的行为。
为了理解嘲讽,让我们为OrdersController
的GetOrdersAsync
动作方法创建单元测试。
让我们来看看我们正在为其编写单元测试用例的GetOrderById
方法:
//This is the GetOrderById action method in OrdersController.cs
[HttpGet]
[Route(“{id}”)]
public async Task<IActionResult> GetOrderById(string id)
{
var order = await this.orderService.GetOrderByIdAsync(id).ConfigureAwait(false);
if (order != null)
{
return this.Ok(order);
}
else
{
return this.NotFound();
}
}
在这种方法中,调用orderService
的GetOrderByIdAsync
,以便根据传入的id
实例获取订单。控制器动作将返回从OrderService
取回的订单id
;否则,返回NotFound
动作。
正如我们所看到的,代码流有两条路径:一条路径用于订单存在时,另一条路径用于订单不存在时。通过单元测试,我们应该能够涵盖这两条路径。所以,现在出现的问题是,我们如何模拟这两种情况?
我们在这里想要的是嘲笑OrderService
的回应。要嘲笑OrderService
的反应,我们可以利用 Moq 库。
为了利用 Moq,我们需要在Packt.ECommerce.Order.UnitTest
测试项目中添加对Moq
包的 NuGet 引用。
让我们将中的测试方法添加到OrdersControllerTest
类中,如下代码所示,测试OrdersController
的GetOrderById
,以验证OrderService
返回订单对象的情况:
[TestMethod]
public async Task When_GetOrdersAsync_with_ExistingOrder_receive_OkObjectResult()
{
var stub = new Mock<IOrderService>();
stub.Setup(x => x.GetOrderByIdAsync(It.IsAny<string>())).Returns(Task.FromResult(new OrderDetailsViewModel { Id = “1” }));
OrdersController testObject = new OrdersController(stub.Object);
var order = await testObject.GetOrderById(“1”).ConfigureAwait(false);
Assert.IsInstanceOfType(order, typeof(OkObjectResult));
}
从代码中,我们可以观察到以下内容:
- 由于
IOrderService
是通过控制器注入注入到OrderController
的,所以我们可以向OrderController
注入一个经过模拟的OrderService
,这将有助于我们通过改变模拟对象行为来测试OrderController
的所有代码路径。 - 我们利用
Mock
类为IOrderService
创建一个存根(也称为模拟)并覆盖GetOrderByIdAsync
行为,如前面的代码所示。 - 我们为
IOrderService
界面创建Mock
对象的实例,并通过调用Mock
对象上的Setup
方法来设置GetOrderByIdAync
的行为。 GetOrderByIdAsync
方法被模拟为,对于它接收到的任何参数值,mock
对象将返回OrderDetailsViewModel
的对象,其中Id
为1
。- 由于我们通过构造函数注入将被模拟的对象注入到
OrderService
中,所以每当IOrderService
中有对任何方法的调用时,该调用都会转到IOrderService
的被模拟的实现中。 - 最后,通过验证从
OrderController
返回到OkObjectResult
的结果类型来断言测试结果。
现在,让我们添加一个测试用例来验证行为,如果订单不存在,我们将收到NotFound
结果,如以下代码所示:
[TestMethod]
public async Task When_GetOrdersAsync_with_No_ExistingOrder_receive_NotFoundResult()
{
var stub = new Mock<IOrderService>();
stub.Setup(x => x.GetOrderByIdAsync(It.IsAny<string>())).Returns(Task.FromResult<OrderDetailsViewModel>(null));
OrdersController testObject = new OrdersController(stub.Object);
var order = await testObject.GetOrderById(“1”).ConfigureAwait(false);
Assert.IsInstanceOfType(order, typeof(NotFoundResult));
}
在这个测试案例中,我们通过从OrderService
存根返回一个null
值来模拟订单不存在的行为。这将使OrdersController
的GetOrderById
动作方法返回NotFoundResult
,这在测试用例中得到了验证。
注意
OrderService
级依赖于IHttpClientFactory
、IOptions
、Mapper
和DistributedCacheService
。所以,为了增加一个单元测试,我们应该嘲笑他们所有人。更多细节可以看看之前代码的OrderServiceTest
测试类中的When_GetOrderByIdAsync_with_ExistingOrder_receive_Order
测试方法。
在本节中,我们已经看到了如何利用MSTest
框架来创建单元测试。有许多其他的测试框架可以用来创建单元测试.NET Core。这里值得一提的两个这样的框架是 xUnit 和 nUnit。虽然 xUnit 和 nUnit 在执行测试的方式上有一些不同,但这两个框架都很出色,并且提供了模仿和并行执行等功能。
在单元测试中,我们的目标是通过模仿依赖类的行为来测试特定的类。如果我们测试这些类和其他相关类,我们称之为集成测试。我们在不同的层次上编写集成测试:在特定模块或组件的层次上,在微服务层次上,或者在整个应用层次上。
既然我们已经将单元测试用例添加到了我们的电子商务解决方案中,在下一节中,我们将检查这些测试的代码覆盖率。
代码覆盖率是描述我们的测试用例覆盖了多少代码的度量。Visual Studio 提供了一个工具来查找单元测试的代码覆盖率。我们可以对所有测试运行测试 | 分析代码覆盖率,如下所示:
图 15.6–文本资源管理器中的分析代码覆盖率上下文选项
这也可以从测试浏览器中的上下文菜单中完成。
这将运行所有测试用例,并识别任何未测试的代码块。我们可以在下面的代码覆盖结果窗口中看到代码覆盖结果:
图 15.7–Visual Studio 代码覆盖窗口
代码覆盖结果将显示覆盖块的百分比和未覆盖块的百分比。因为我们覆盖了GetOrderByIdAsync
的所有块,所以该方法的代码覆盖是 100% 。GetOrdersAsync
的覆盖率是 0.00% ,因为我们没有任何测试用例来测试它。代码覆盖率很好地表明了我们的单元测试有多有效。
注意
MSTest
提供了一个名为假货的模拟框架,可以用来创建模拟,但是这样做的限制是我们将无法获得代码覆盖率.NET Core。微软承诺在未来的版本中增加这项功能。
建议为解决方案中的所有类创建单元测试用例。通过添加单元测试来验证所有的类和功能,单元测试用例将覆盖更高比例的代码。有了更高的代码覆盖率,我们将能够在开发的早期捕捉更多的错误,同时对解决方案进行更改。在我们提交变更之前,我们应该确保所有的测试用例都通过。在下一章 第 16 章在 Azure 中部署应用,我们将学习如何将运行测试用例与 Azure DevOps 管道集成。
到目前为止,我们已经通过模仿依赖关系和编写单元测试用例来测试单个模块或类。在集成和部署整个解决方案后测试功能也很重要。在下一节,我们将学习如何为我们的电子商务应用执行功能测试。
小费
Visual Studio 的代码度量和代码分析工具对于确保我们编写的代码的可维护性和可读性非常有用。你可以在这里找到代码度量的细节:https://docs . Microsoft . com/en-us/visualstudio/code-quality/code-metrics-values?view=vs-2019 。
代码分析,请到这里:https://docs . Microsoft . com/en-us/dotnet/基本面/代码-分析/概述。
在功能测试中,我们根据功能需求验证我们构建的应用。功能测试是通过提供一些输入并断言应用的响应或输出来执行的。在执行功能测试时,我们将应用视为一个整体;我们不验证单个内部组件。
功能测试可以分为三个任务:识别要测试的系统的功能,确定具有预期输出的输入,然后执行这些测试来评估系统是否根据预期做出响应。功能测试的执行可以通过在应用上执行测试步骤来手动完成,或者我们可以使用工具来自动化它们。自动化功能测试可以大大缩短应用的上市时间。
在下一节中,我们将学习自动化功能测试用例。
手动执行功能测试用例在应用测试中仍然是相关的。然而,考虑到较短的部署周期和客户对新特性的快速期望,手动测试在早期识别错误方面可能非常耗时和低效。使用自动化,我们可以获得新的效率,加速测试过程,并提高软件质量。有多种工具和框架可用于自动化功能测试用例。
在本节中,我们将学习最流行的自动化框架 Selenium。让我们开始吧:
-
首先,让我们创建一个
MSTest
项目并命名为Packt.ECommerce.FunctionalTest
。 -
在这个项目中,添加
Selenium.WebDriver
、Selenium.WebDriver.ChromeDriver
和WebDriverManager
NuGet 包。我们运行硒测试需要这些包。 -
让我们从一个简单的测试开始,验证我们的电子商务应用的标题。为此,创建一个
HomePageTest
测试类和一个When_Application_Launched_Title_Should_be_ECommerce_Packt
测试方法,就像我们在理解单元测试部分所做的那样,如下面的代码所示:[TestClass] public class HomePageTest { [TestMethod] public void When_Application_Launched_Title_Should_be_ECommerce_Packt() { } }
-
To execute our functional tests, we should launch a browser and use that browser to navigate to the e-commerce application. The
MSTest
framework provides a special function to perform the initialization and cleanup operations required for our tests. We will be creating a Chrome web driver to perform a functional test.让我们继续添加初始化和清理方法,如下代码所示:
[TestClass] public class HomePageTest { ChromeDriver _webDriver = null; [TestInitialize] public void InitializeWebDriver() { var d = new DriverManager(); d.SetUpDriver(new ChromeConfig()); _webDriver = new ChromeDriver(); } [TestMethod] public void When_Application_Launched_Title_Should_be_ECommerce_Packt() { } [TestCleanup] public void WebDriverCleanup() { _webDriver.Quit(); } }
在前面的代码中,
InitializeDriver
方法被赋予了TestInitialize
来通知框架这是测试初始化方法。在测试初始化中,我们正在创建ChromeDriver
并初始化类变量。测试用例完成后,我们应该关闭浏览器实例;我们在WebDriverCleanup
方法中通过调用Quit
方法来做到这一点。要通知测试框架它是清理方法,应该归结为TestCleanup
。 -
Now let’s go and add the test case to navigate to the e-commerce application and validate the title as shown in the following code:
[TestMethod] public void When_Application_Launched_Title_Should_be_ECommerce_Packt() { _webDriver.Navigate().GoToUrl(“https://localhost:44365/”); Assert.AreEqual(“Ecommerce Packt”, _webDriver.Title); }
在我们的 Chrome 网络驱动上调用
GoToUrl
导航到电子商务应用。导航后,我们可以通过声明 web 驱动程序的Title
属性来验证页面的标题。 -
通过右键单击
When_Application_Launched_Title_Should_be_ECommerce_Pact
测试用例并选择运行,从测试资源管理器运行测试用例。这将打开 Chrome 浏览器并导航到指定的电子商务网址,然后它将断言页面的标题。测试用例执行后,浏览器将关闭。我们在测试浏览器中看到结果,如下图截图所示:
图 15.8–测试项目创建后的解决方案结构
现在我们将扩展功能测试来验证搜索功能。为了测试这个功能,我们应该在搜索框中输入文本,然后点击搜索按钮。然后,检查结果,查看返回的测试结果是否仅属于搜索到的产品。
让我们通过添加When_Searched_For_Item
测试方法来自动化测试用例,如下面的代码所示:
[TestMethod]
public void When_Searched_For_Item()
{
_webDriver.Navigate().GoToUrl(“https://localhost:44365/”);
var searchTextBox = _webDriver.FindElement(By.Name(“SearchString”));
searchTextBox.SendKeys(“Orange Shirt”);
_webDriver.FindElement(By.Name(“searchButton”)).Click();
var items = _webDriver.FindElements(By.ClassName(“product-description”));
var invaidProductCout = items.Where(e => e.Text != “Orange Shirt”).Count();
Assert.AreEqual(0, invaidProductCout);
}
在这个测试案例中,导航到主页后,在搜索字符串字段中输入搜索文本,然后点击搜索按钮。通过验证搜索结果来确认是否有任何产品没有作为search
字符串返回。
硒使编写功能测试变得非常容易。我们应该尝试自动化所有的功能测试用例,比如用户管理、向购物车添加产品以及下订单。随着所有功能测试用例的自动化,我们将能够更好地测试和验证新版本的功能,并保持我们应用的质量。还有其他可用的功能测试工具,如 QTP 和 Visual Studio 编码用户界面测试。
我们已经看到了功能测试,它验证了应用的功能。同样重要的是评估应用的响应能力,看它如何响应特定的负载。在下一节中,我们将学习如何在电子商务应用上执行性能测试。我们可以利用自动化的功能测试用例来执行 BVT 或回归测试。
注意
参考文档了解更多关于硒检测的信息:https://www.selenium.dev/documentation/en/。
用户期望应用对他们的行为做出快速响应。任何反应迟缓都会导致用户沮丧,最终,我们会失去他们。即使一个应用在正常负载下工作正常,我们也应该知道当需求突然达到峰值时,我们的应用是如何工作的,并为此做好准备。
负载测试的主要目标不是发现错误,而是消除应用的性能瓶颈。进行负载测试是为了向涉众提供关于他们应用的速度、可伸缩性和稳定性的信息。在下一节中,我们将学习如何使用 JMeter 执行负载测试。
JMeter 是由 Apache 软件基金会构建的开源测试工具。它是执行负载测试最流行的工具之一。JMeter 可以通过创建 web 服务器的虚拟并发用户来模拟应用的繁重负载。
可以从这里下载配置 JMeter:https://jmeter.apache.org/download_jmeter.cgi。
让我们继续为我们的电子商务应用创建一个 JMeter 负载测试。
为了学习如何使用 JMeter 进行负载测试,我们将创建一个包含两个主页和产品搜索页面的测试。尝试以下步骤来创建负载测试:
-
Launch Apache JMeter from the download location. We will see the window as follows:
图 15.9–Apache JMeter
-
通过在左窗格中右键单击测试计划并选择添加 | 螺纹(用户) | 螺纹组来添加螺纹组。线程组定义了将对我们的应用执行测试用例的用户池。使用它,我们可以配置模拟的用户数量、启动所有用户的时间以及执行测试的次数。
-
Let’s name the thread group
Load and Query Products
and set the number of users to30
. Set Ramp-up period to5
seconds as shown in the following screenshot:图 15.10–在 Apache JMeter 中添加线程组
这将在
5
秒内模拟30
的用户负载。使用线程组,我们还可以控制测试应该运行的次数。 -
To add the test request, right-click on Thread Group and select Add | Sampler | HTTP Request.
让我们将协议设置为
https
、服务器名称或、将 IP 设置为localhost
、端口号设置为44365
(本地运行的电商门户的端口号)。命名本次测试Home Page
,如下图截图所示:图 15.11–在 JMeter 中添加主页 HTTP 请求
让我们再添加一个【HTTP 请求采样器来获取特定产品的细节。对于该请求,将
productId
查询参数设置为Cloth.3
,将productName
设置为Orange%20Shirt
,如下图所示:图 15.12–在 JMeter 中添加产品详细信息页面 HTTP 请求
-
点击保存按钮并将其命名为
ECommerce
,保存本次测试计划。 -
为了查看结果,我们应该在这个测试中添加一个监听器。右键单击测试组,选择添加 | 收听者 | 查看表格中的结果。
-
添加监听器后,选择运行 | 启动继续运行测试。
-
测试运行完成后,您将看到如下截图所示的结果。这将为我们提供每个请求的响应时间:
图 15.13–JMeter 中的测试结果表
JMeter 中有多个监听器可以查看结果,例如摘要报告和图形结果,这将给出测试结果的另一种表示。我们可以用 JMeter 轻松配置不同种类的采样器,也可以用不同的 HTTP 方法和动态测试配置请求,其中请求依赖于另一个应用编程接口的响应。一旦在 JMeter 中有了测试计划,我们就可以利用 JMeter 命令行实用程序从多个数据中心运行它来模拟跨地域的负载并整理结果。
JMeter 提供的灵活性,以及其丰富的文档,使其成为最常用的性能测试工具。JMeter 还可以用来执行功能测试。
建议以预期负载的 1.5 到 2 倍运行负载测试。运行性能测试后,建议使用 Application Insights 来分析请求的服务器响应时间、负载情况下相关 API 的响应情况,更重要的是,分析测试过程中出现的任何故障。
小费
建议使用 Azure DevOps 管道运行自动化测试。使用文档查看如何将测试与 Azure DevOps 管道集成。
JMeter 测试:https://github.com/Azure-Samples/jmeter-aci-terraform
在这一章中,我们探讨了软件开发的一个非常重要的方面:测试。我们已经了解了不同类型的测试以及在 SDLC 中使用它们的阶段。
我们学习了单元测试的概念,以及如何通过使用 Moq 框架模仿依赖关系来将测试集中在特定的调用上。我们还被介绍使用 Selenium 创建自动化功能测试,以在将我们的电子商务应用发布到生产之前测试其功能。
最后,我们了解了 JMeter,它是执行负载测试最常用的工具。下一章将重点讨论在 Azure 中部署应用。
-
True or False: We should only start to think about testing an application after the completion of its development?
a.真实的
b.错误的
-
Which of the following is a kind of software testing?
a.安全测试
b.功能测试
c.无障碍测试
d.上述全部
-
True or False: A higher code coverage percentage for unit tests is desirable to achieve a shorter time to market?
a.真实的
b.错误的