使用示例解释.NET中的Mocking是什么?

让我们踏上探索.NET软件开发中Mocking概念的旅程,让我们深入了解Mocking是多么简单易懂、易于访问。请与我一起穿越这个主题,我将涵盖以下内容:

  • 理解Mocking:为何它对于构建强大的测试策略至关重要。
  • 探索一些最常见的Mocking库:如Moq、NSubstitute、FakeItEasy和Rhino Mocks等。
  • 掌握Mocking技术:使用每个库,为您提供选择最佳Mocking工具的知识,以满足您的需求。

本教程的目的是让您具备足够的知识,以便自行决定您偏好的Mocking库,这样您就可以继续在应用程序中编写一些强大的测试。

(本文视频讲解:java567.com)

本教程的先决条件

  1. 理解C#编程
  2. 理解C#单元测试
  3. 一个IDE(Rider、Visual Studio等)或代码编辑器(VS Code、Sublime Text、Atom等)

入门/设置

为了加快进程,我已经为本教程提供了一个公共GitHub仓库。您可以将其克隆到本地计算机上以便跟随操作。

只需转到此链接并将仓库克隆到本地计算机即可。

如果您忘记了如何操作,请快速复习一下:转到上面的链接,在右上角点击“Code”,然后复制提供的URL。

image-2

image-3

找到您的本地git文件夹(如果没有,请在您的用户根文件夹中创建一个)。然后在您喜欢的终端中导航到您的git文件夹,并执行以下命令。

//(用URL替换<url>)
git clone <url> 

应用程序简要概述

您刚刚克隆的解决方案是一个基本的Web API项目,它引用了一个包含一些Todo领域对象和服务的类库,这些对象和服务将改变一个todo项目列表。

为了本教程的目的,这些元素存储在内存列表中,而不是连接到数据库。但您可以使用数据库或其他形式的数据持久化方法。

然而,对于本教程的目的,我们不太关心数据的持久化,而更关心的是对这个服务进行Mocking。

Mocking是什么?

带有文字“Real”和“Fake”的两个箭头通过Mocking,你无法区分真实与虚假

Mocking在软件开发中是模拟系统测试中某个部分依赖的类、方法或服务的行为或返回对象的概念。

在测试特定组件或代码单元时,通常需要将其与其依赖项(如数据库、Web服务或其他类)隔离开来,以确保测试仅专注于被测试单元的功能,而不必关注代码的外部方面。

Mocking允许开发人员创建这些依赖项的模拟对象或虚拟实现,以预定的方式行为,模仿真实对象的行为。

通过使用模拟对象或方法,我们可以控制依赖项的输入和输出。这使得测试不同的情景变得更加容易,其中业务逻辑依赖于依赖项的结果。

示例

假设我们有一个API端点调用一个连接到数据库的存储库。我们的API响应类型取决于存储库返回的值:我们从API返回一个400或200的响应。

我们可以简单地模拟存储库返回值,并测试我们的API在这两种情况下是否返回了正确的响应,而无需实际触及数据库。

实质上,Mocking通过隔离待测试的代码并为其依赖项提供可预测的行为,帮助开发人员编写更可靠、高效的测试,从而提高软件的整体质量。

Mocking的好处是什么?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传好处

代码隔离

正如我已经解释过的那样,Mocking依赖项允许对代码进行测试隔离。通过Mocking依赖项,您可以断言被测试代码中的失败点,而不是依赖项本身(除非您Mocking它们错误——希望本教程能帮助您避免这种情况)。

更快的测试速度

通过用Mocking实现替换真实依赖项,可以更快地进行测试。您不必与这些依赖项的不一致性、不可靠或不可预测的结果作斗争。这消除了设置和拆卸外部资源的需要,这有时可能会很复杂且耗时。

确定性测试

Mock对象提供了一个可控制的环境,使开发人员能够指定依赖项的确切行为和响应。这种方法意味着测试是一致的,从而更容易找到错误并调试问题,特别是采用TDD(测试驱动开发)方法的情况下。

并行测试

Mocking通过消除对可能在测试期间受限或无法访问的外部资源的依赖,实现了并行测试(同时运行多个测试)。

例如,如果您没有Mocking您的数据库连接层,尝试并行运行测试可能会导致不一致的通过/失败度量,因为另一个测试可能会影响您在另一个测试中使用的数据库表。Mocking这一层意味着您的测试现在与此层无关,可以同时运行。

减少测试维护工作

由于Mock对象封装了依赖项的行为,因此对这些依赖项的更改不一定需要更新测试本身。这减少了与不断发展的代码库和依赖项相关的维护开销。

增强的测试覆盖范围

Mocking允许开发人员模拟各种场景和边界情况。通过控制依赖项的行为,开发人员可以确保他们的测试覆盖了代码的所有相关路径,消除了任何现实生活或物理限制。

使用Mocking需要注意的事项

image-20

复杂性: 有时在对应用程序的复杂区域进行Mocking/测试时,Mocking也可能变得复杂。然而,如果系统过于难以Mocking,您可能需要重新评估您的架构。

学习曲线: 这需要理解Mocking库的语法和概念,这对于刚接触单元测试或特定框架的开发人员来说可能是具有挑战性的。

过度规范化: Mocking可能导致对被测试代码行为进行过度规范化。这意味着测试可能会与实现细节紧密耦合,使其变得脆弱,并在实现更改时容易中断。在验证行为和专注于期望结果之间保持平衡至关重要。

要注意错误的正测试: 虽然Mocking允许您隔离代码单元,但它也可能会产生一种虚假的安全感。Mock模拟依赖项,但它们可能无法完全复制真实依赖项的行为。仍然需要进行集成测试或端到端测试来验证系统的整体行为。

流行的.NET Mocking库

image-25

以下是一些流行的.NET测试库:

  • FakeItEasy
  • NSubstitute
  • Moq
  • Rhino Mocks

这只是在线可用的一些最常用的.NET Mocking库的列表,但还有许多其他库。我强烈建议使用其中之一,因为它们有着更大的社区、更值得信赖的代码库和良好的文档(在开始时至关重要)。

这些Mocking库都有自己创建对象的语法,但它们都遵循相同的原则。

  1. 声明要Mock的对象/类型/服务
  2. 您希望该对象/类型/服务如何运行(实现)
  3. 返回值是什么(在必要时)。

查看测试

如果您打开解决方案,并导航到“Test”项目,您会发现我们在那里有四个文件,每个文件中都包含不同的Mocking库测试。

  1. FakeItEasyApiTests.cs
  2. MoqApiTests.cs
  3. NSubstituteApiTests.cs
  4. RhinoMocksApiTests.cs

在这些文件中,您将看到我们有四个非常基本的XUnit测试。出于本教程的目的,我将它们保持简短和简单。

深入了解测试结构

每个测试文件都使用构造函数来初始化其各自服务的私有版本,您可以看到这些服务在不同库之间的差异,但仍遵循相同的概念。

// FakeItEasy_fakeTodoService = A.Fake<ITodoService>();// NSubstitute_substituteTodoService = Substitute.For<ITodoService>();// Moq_mockTodoService = new Mock<ITodoService>();// Rhino Mocks_mockTodoService = MockRepository.GenerateMock<ITodoService>();

选择“正确”的Mocking库完全取决于个人偏好,以及您认为哪种更容易编写、使用和阅读/理解。

有些人可能会发现使用类似于FakeSubstitute.For这样的词更容易理解或阅读。而其他人可能会觉得A.Fake不直观,更喜欢new Mock<type>更明显。

排列、执行和断言

遵循AAA(排列、执行和断言)测试原则,我们可以仔细构建我们的测试,使得我们正在做什么和在哪里做什么变得明显。

AAA测试方法包括三个步骤:

  1. 排列:设置测试环境,模拟的服务/外部依赖项。
  2. 执行:执行正在测试的操作。
  3. 断言:验证期望的结果。

使用模拟对象模拟返回项

让我们通过模拟TodoService.GetAllTodos方法返回一组存根任务来测试getAll(GetAllTodoItems)端点。

这种方法消除了为每个测试场景设置和填充数据库的需要,确保针对特定标准测试返回值的API端点。

模拟提供了一个理想的解决方案,允许我们模拟所需的行为,而不受其他测试的干扰。

我们可以这样做(记住完整的代码在存储库中可用):

// FakeItEasy
A.CallTo(() => _fakeTodoService.GetAllTodos()).Returns(expectedTodos);// NSubstitute_substituteTodoService.GetAllTodos().Returns(expectedTodos);// Moq_moqTodoService.Setup(s => s.GetAllTodos()).Returns(expectedTodos);// Rhino Mocks_mockTodoService.Stub(s => s.GetAllTodos()).Return(expectedTodos);

这些方法是做什么的?

您将在大多数库中看到的一个常见特性是使用lambda函数来指示要模拟的方法。

在设置方法中提供的lambda函数实际上是一个配置步骤,定义了调用模拟方法时应采取的操作。此配置在测试期间调用模拟方法时存储和应用。

让我们分解一下,我们正在做什么:

  1. Lambda指定了我们希望在模拟服务上配置/设置的方法。
  2. 我们传递的lambda不会立即由设置方法运行。我们没有要求测试立即运行该方法;我们的意思是,“记住这个指令/设置,以备在测试期间调用实际方法时使用。”
  3. 当我们模拟的方法在测试期间被调用时,它会检查调用签名是否与我们提供的设置配置相匹配。 如果匹配,测试会遵循设置期间给出的指示。

重要说明:

另一方面,NSubstitute允许开发人员直接在虚拟对象上模拟方法。这意味着您可以访问虚拟的GetAllTodos方法,并简单地将返回值设置为您期望的值。

虽然Moq使用了Setup方法,但与其他方法略有不同。Moq在内部创建一个代理对象,代表了被模拟的对象,并公开了.Object属性来访问它。我们将在下一部分中看到这是如何工作的。

如何调用被测试系统(SUT)

// FakeItEasy
var sut = new TodoController(_fakeTodoService);// NSubstitute
var sut = new TodoController(_substituteTodoService);// Moq -- 和其他的略有不同var sut = new TodoController(_moqTodoService.Object);// Rhino Mocks
var sut = new TodoController(_mockTodoService);

在四个库中的三个中,您可以直接传递模拟对象。然而,Moq需要在模拟上使用.Object属性才能使用它。因此,我们将moqTodoService.Object作为控制器的参数传递。

image-26一张图片显示所有测试都通过了

运行测试,您可以看到它们都通过了。如果您更改存储库中的任何代码,这不会有任何影响,因为这些测试是模拟的。试一试,尝试更改存储库功能并重新运行测试,它们将继续通过。

我们专注于端点的功能,而不是模拟存储库正在执行的操作,这就是模拟的美妙之处。

Mocking的选择是无穷无尽的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

模拟不仅允许我们设置模拟对象返回的内容,还可以允许我们模拟实现,包括抛出特定错误,以便测试我们的API错误处理和不正常路径。

这在每个库测试文件中的Delete_Returns500_AndErrorMessageThrown_WhenExceptionThrown测试中有所体现。

// FakeItEasy
A.CallTo(() => _fakeTodoService.Delete(1)).Throws(new Exception(errorMessage));// NSubstitute
_substituteTodoService.When(x => x.Delete(1)).Do(x => throw new Exception(errorMessage));// Moq
_moqTodoService.Setup(s => s.Delete(1)).Throws(new Exception(errorMessage));// Rhino Mocks_mockTodoService.Stub(s => s.Delete(1)).Throw(new Exception(errorMessage));

使用不同的库,我们可以使服务上的Delete方法抛出我们想要的任何异常。当您想要返回不同的状态代码或根据抛出的异常类型以不同的方式处理错误时,这是理想的。

例如,我们可以将Throws(new Exception(errorMessage)更改为Throws(new UnauthorizedAccessException(),并测试当抛出401状态代码时。

全局设置

您可以为同一方法分配多个配置。这在您想要在一个地方设置模拟对象的所有配置时非常有用。例如,在测试类构造函数中。

在某些测试框架(如NUnit)中,您可以在方法上方使用[OneTimeSetUp]属性,该属性在测试用例之前运行,或者简单地使用测试类构造函数。

在这种情况下,您可以像在Moq中那样做一些事情:

public MoqApiTests()
{_mockTodoService = new Mock<ITodoService>();_mockTodoService.Setup(x => x.Delete(1)).Throws(new Exception("This is a generic exception"));_mockTodoService.Setup(x => x.Delete(2)).Throws(new UnauthorizedAccessException("You cannot perform this action on this item"));
}

在这个例子中,我们演示了设置模拟服务调用相同方法的多个配置,每个配置导致抛出不同的异常。

这种方法有助于在不同的测试中测试不同异常发生时的不同结果,而不会使我们的测试代码因重复的设置而杂乱无章。

例如:

// 测试1
var result = TodoController.Delete(1);
// 断言处理一般异常// 测试2
var result = TodoController.Delete(2); 
// 断言处理UnauthorizedAccessException

我更喜欢在每个单独的测试中设置模拟,以确保没有外部因素影响模拟。

这样,我可以在测试中轻松识别被模拟的内容,而不必在其他地方搜索模拟对象和设置。

如果我不在乎我传递的是什么?

在我们的删除示例中,我们始终传递了一个ID给模拟实现。因此,如果我们通过TodoController.DeleteTodoItem调用传递了一个不同的ID,比如101,我们将不会收到相同的结果。

这是因为我们明确指示了模拟对象在调用存根方法时使用ID 1时抛出错误。

为了解决这个问题,我们可以更不具体。每个库都有自己的语法,使我们能够指定如果传递给方法的是任何整数,它将抛出特定的异常。

// FakeItEasy
A.CallTo(() => _fakeTodoService.Delete(A<int>._)).Throws( new Exception(errorMessage));// NSubstitute
_substituteTodoService.When(x => x.Delete(Arg.Any<int>())).Do(x => throw new Exception(errorMessage));// Moq
_mockTodoService.Setup(s => s.Delete(It.IsAny<int>())).Throws(new Exception(errorMessage));// Rhino Mocks
_mockTodoService.Stub(s => s.Delete(Arg<int>.Is.Anything)).Throw(new Exception(errorMessage));

此代码表示当传递任何int类型的参数时,它应该抛出此异常。

NSubstitute的语法略有不同:它需要在遇到这种情况时明确指示要抛出指定的错误,不像我们在通知它返回对象时那样。这种差异源于库的内部机制。

断言模拟对象被调用

在某些情况下,您可能希望验证模拟服务是否以正确的参数被调用。当处理“发出并忘记”服务时,这特别有用。

在这种情况下,您的API端点被调用,虽然它总是返回true,但它也会触发一个独立执行的服务执行某些操作,这不会影响API的返回类型(也许是一个异步通知服务)。

这是您可能希望执行一个快速的健全性检查以确保您的“发出并忘记”服务被调用的少数情况之一(尽管理想情况下,您会进行与该服务的集成测试)。

如果您查看DeleteTodoItem端点以及每个测试文件中的DeleteAPI_CallsNotificationService_WithTaskId_AndUserId测试,您可以看到如何完整地执行此操作的示例。

我们正在验证当我们调用DeleteTodoItem时,在我们的正常路径上,NotificationService.NotifyUserTaskCompleted被调用,传递了要删除的项目的ID和硬编码的用户ID。

作为练习,您可以创建一个用户服务,该服务返回已登录用户的ID,并将其用于传递ID给服务。这也可以在测试中进行模拟。

 // FakeItEasyA.CallTo(() => _fakeNotificationService.NotifyUserTaskCompleted(1,1)).MustHaveHappened(1, Times.Exactly);// NSubstitute _notificationService.Received().NotifyUserTaskCompleted(1,1);// Moq _moqNotificationService.Verify(x => x.NotifyUserTaskCompleted(1,1)); // Defaults to Times.AtLeastOnce// Rhino Mock
_mockNotificationService.AssertWasCalled(x=>x.NotifyUserTaskCompleted(1,1));

结论

总之,模拟对象的灵活性提供了许多应用场景,在测试单个代码单元时不可或缺。

尽管我已经涵盖了模拟的几个功能和可实现的验证,但还有更多,比如方法调用顺序和负验证。

在我看来,项目中选择模拟库是主观的,没有明确的正确或错误选项。虽然一些库可能提供更方便的扩展或更清晰的语法,但最终决定归根结底取决于个人偏好。

我希望这个教程为您提供了对模拟世界的一瞥,并阐明了不同库之间的语法差异。

(本文视频讲解:java567.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/690.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ansible组件说明

1.Ansible Inventory 工作当中有不同的业务主机&#xff0c;我们需要在把这些机器信息存放在inventory里面&#xff0c;ansible默认的inventory的文件是/etc/ansible/hosts&#xff0c;也可以通过ANSIBLE_HOSTS环境变量来指定或者运行ansible和ansible-playbook的时候用-i参数临…

视频教程下载:用ChatGPT玩转海外抖音TikTok

CHATGPT for TikTok是一门前沿课程&#xff0c;旨在帮助您充分发挥TikTok广告活动的全部潜力。随着数字营销的爆炸性增长&#xff0c;企业需要使用先进的工具来保持竞争优势。在这门课程中&#xff0c;您将学习如何利用CHATGPT——一种先进的人工智能工具——来创建与目标受众产…

有没有手机上使用的库存软件

库存软件是一种仓库的信息管理系统&#xff0c;它主要针对出库与入库这些数据进行管理&#xff0c;传统的库存管理都是在电脑上安装一个专门的数据库管理系统进行管理&#xff0c;这也是一种比较成熟的管理方式&#xff0c;那么有没有手机上使用的库存软件。 手机上使用的库存软…

k8s配置configmap指定到容器的指定文件

我们需要将名称为walletkey.properties的文件做成configmap&#xff0c;然后将walletkey.properties文件单独挂载出来到/data/walletkey.properties&#xff0c;且不能覆盖/data目录&#xff0c;具体如下 1、创建configmap configmap文件内容 其中walletkey.properties: >-引…

python语言零基础入门——注释、print()函数、input()函数

目录 一、注释 1.块注释 2.行内注释 3.多行注释 二、打印变量 1.print()函数&#xff1a;输出/打印指定内容 2.input()函数&#xff1a;输入指定内容 三、编程题&#xff1a;个人名片 一、注释 1.块注释 以#开始&#xff0c;直到本行结束都是注释为了保证代码的可读性…

科技的崛起:国内机器视觉蓬勃发展

文 | BFT机器人 在工业4.0的浪潮下&#xff0c;随着科技的蓬勃发展&#xff0c;机器视觉逐渐走入大众视野&#xff0c;机器视觉产品的普及范围也越来越广。 大家知道机器视觉的由来吗&#xff1f; 机器视觉的由来可以追溯到20世纪70年代&#xff0c;美国麻省理工学院&#xff…

机器学习鸢尾花各种模型准确率对比

流程 获取数据集导入需要的包读取数据划分训练集和测试集调用各种模型比较准确率 获取数据集 链接&#xff1a;https://pan.baidu.com/s/1RzZyXsaiJB3e611itF466Q?pwdj484 提取码&#xff1a;j484 --来自百度网盘超级会员V1的分享导入需要的包 import pandas as pd impo…

华为机考入门python3--(17)牛客17- 坐标移动

分类&#xff1a;字符串 知识点&#xff1a; 正则匹配 re.match(pattern, move) 格式字符串&#xff0c;可以在字符串中直接引用变量 f"{x},{y}" 题目来自【牛客】 import re def is_valid_coordinate(move): # 使用正则表达式验证移动是否合法 # ^: …

施耐德 PLC 及模块 ModbusTCP 通信配置方法

1. 通过【I/O扫描器】服务进行读写 相关文档&#xff1a;各模块说明书仅 NOE 网卡模块、部分 CPU 自带的网口支持 优点&#xff1a;不需要额外编程&#xff0c;系统自动周期型读写数据缺点&#xff1a;扫描周期不定&#xff0c;程序无法控制数据刷新的时序 2. 通过内部程序…

java扩展jmeter依赖

前置条件 创建一个maven项目&#xff0c; 引入依赖 <dependency><groupId>org.apache.jmeter</groupId><artifactId>ApacheJMeter_core</artifactId><version>3.2</version> </dependency> <dependency><groupId&g…

NIO学习

文章目录 前言一、主要模块二、使用步骤1.服务器端2.客户端 三、NIO零拷贝(推荐)四、NIO另一种copy总结 前言 NIO是JDK1.4版本带来的功能,区别于以往的BIO编程,同步非阻塞极大的节省资源开销&#xff0c;避免了线程切换和上下文切换带来的资源浪费。 一、主要模块 Selector&a…

【Linux学习】Linux编辑器-vim使用

这里写目录标题 1. &#x1f320;vim的基本概念&#x1f320;2. vim的基本操作&#x1f320;3.vim异常处理&#x1f320;4. vim正常模式的相关命令&#x1f320;5. vim末&#xff08;底&#xff09;行模式相关命令 vi/vim都是多模式编辑器&#xff0c;不同的是vim是vi的升级版本…

达梦数据库一体机树立金融解决方案标杆

达梦数据库一体机自问世以来&#xff0c;获得众多行业用户的高度关注&#xff0c;并率先在金融行业吹响冲锋号角&#xff0c;实现多个重大项目的落地应用。近日&#xff0c;珠海华润银行股份有限公司基于达梦数据库一体机 I 系列的《数据库一体机银行多业务系统集中部署解决方案…

(保姆级教学)跨站请求伪造漏洞

1. CSRF漏洞 CSRF&#xff08;Cross-site request forgery&#xff09;跨站请求伪造&#xff0c;也被称为One Click Attack 或者Session Riding&#xff0c;通常缩写为CSRF或者XSRF&#xff0c;是一种对网站的恶意利用。尽管听起来像跨站脚本&#xff08;XSS&#xff09;&…

数据结构和算法:动态规划

初探动态规划 动态规划&#xff08;dynamic programming&#xff09;是一个重要的算法范式&#xff0c;它将一个问题分解为一系列更小的子问题&#xff0c;并通过存储子问题的解来避免重复计算&#xff0c;从而大幅提升时间效率。 例题&#xff1a;爬楼梯 给定一个共有 &…

电机控制专题(三)——Sensorless之有功磁链Active Flux电压模型

文章目录 电机控制专题(三)——Sensorless之有功磁链Active Flux电压模型前言理论推导仿真验证总结参考文献 电机控制专题(三)——Sensorless之有功磁链Active Flux电压模型 前言 总结下电机控制中的有功磁链Active Flux(AF)模型。 纯小白&#xff0c;如有不当&#xff0c;轻…

OpenHarmony实战开发-Web自定义长按菜单案例。

介绍 本示例介绍了给Webview页面中可点击元素&#xff08;超链接/图片&#xff09;绑定长按/鼠标右击时的自定义菜单的方案。 效果预览图 使用说明 长按Web页面中的图片或者链接元素&#xff0c;弹出自定义的Menu菜单&#xff0c;创建自定义的操作&#xff0c;如复制图片、使…

【NLP练习】使用Word2Vec实现文本分类

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 一、数据预处理 1. 任务说明 本次加入Word2Vec使用PyTorch实现中文文本分类&#xff0c;Word2Vec则是其中的一种词嵌入方法&#xff0c;是一种用于生成词向量…

2001-2022上市公司数字化转型数据(含原始数据+计算代码+计算结果)

2001-2022上市公司数字化转型数据&#xff08;含原始数据计算代码计算结果&#xff09; 1、时间&#xff1a;2001-2022年 2、来源&#xff1a;原始数据整理自wind 3、指标&#xff1a;证券代码、证券简称、统计截止日期、是否发生ST或*ST或PT、是否发生暂停上市、行业代码、…

戴尔电脑怎么关闭开机密码?

1.同时按键盘上是“window键”&#xff08;一般是键盘最下面一排第二个&#xff09;和“R键“&#xff0c;并在弹出的窗口输入“netplwiz”然后确定。 2.然后会弹出的“用户账户”窗口&#xff0c;接下来取消勾选“要使用本计算机&#xff0c;用户必须输入用户名和密码” 3.上面…