浅谈.Net Core后端单元测试

1. 前言

单元测试一直都是"好处大家都知道很多,但是因为种种原因没有实施起来"的一个老大难问题。具体是否应该落地单元测试,以及落地的程度, 每个项目都有自己的情况。

本篇为个人认为"如何更好地写单元测试", 即更加偏向实践向中夹杂一些理论的分享。

下列示例的单元测试框架为xUnit, Mock库为Moq

2. 为什么需要单元测试

优点有很多, 这里提两点我个人认为的很明显的好处

2.1 防止回归

通常在进行新功能/模块的开发或者是重构的时候,测试会进行回归测试原有的已存在的功能,以验证以前实现的功能是否仍能按预期运行。
使用单元测试,可在每次生成后,甚至在更改一行代码后重新运行整套测试, 从而可以很大程度减少回归缺陷。

2.2 减少代码耦合

当代码紧密耦合或者一个方法过长的时候,编写单元测试会变得很困难。当不去做单元测试的时候,可能代码的耦合不会给人感觉那么明显。为代码编写测试会自然地解耦代码,变相提高代码质量和可维护性。

3. 基本原则和规范

3.1 3A原则

3A分别是"arrange、act、assert", 分别代表一个合格的单元测试方法的三个阶段

  • 事先的准备

  • 测试方法的实际调用

  • 针对返回值的断言

一个单元测试方法可读性是编写测试时最重要的方面之一。在测试中分离这些操作会明确地突出显示调用代码所需的依赖项、调用代码的方式以及尝试断言的内容.

所以在进行单元测试的编写的时候, 请使用注释标记出3A的各个阶段的, 如下示例

Copy[Fact]
public async Task VisitDataCompressExport_ShouldReturnEmptyResult_WhenFileTokenDoesNotExist()
{// arrangevar mockFiletokenStore = new Mock<IFileTokenStore>();mockFiletokenStore.Setup(it => it.Get(It.IsAny<string>())).Returns(string.Empty);var controller = new StatController(mockFiletokenStore.Object,null);// actvar actual = await controller.VisitDataCompressExport("faketoken");// assertAssert.IsType<EmptyResult>(actual);
}

3.2 尽量避免直接测试私有方法

尽管私有方法可以通过反射进行直接测试,但是在大多数情况下,不需要直接测试私有的private方法, 而是通过测试公共public方法来验证私有的private方法。

可以这样认为:private方法永远不会孤立存在。更应该关心的是调用private方法的public方法的最终结果。

3.3 重构原则

如果一个类/方法,有很多的外部依赖,造成单元测试的编写困难。那么应该考虑当前的设计和依赖项是否合理。是否有部分可以存在解耦的可能性。选择性重构原有的方法,而不是硬着头皮写下去.

3.4 避免多个断言

如果一个测试方法存在多个断言,可能会出现某一个或几个断言失败导致整个方法失败。这样不能从根本上知道是了解测试失败的原因。

所以一般有两种解决方案

  • 拆分成多个测试方法

  • 使用参数化测试, 如下示例

Copy[Theory]
[InlineData(null)]
[InlineData("a")]
public void Add_InputNullOrAlphabetic_ThrowsArgumentException(string input)
{// arrangevar stringCalculator = new StringCalculator();// actAction actual = () => stringCalculator.Add(input);// assertAssert.Throws<ArgumentException>(actual);
}

当然如果是对对象进行断言, 可能会对对象的多个属性都有断言。此为例外。

3.5 文件和方法命名规范

文件名规范

一般有两种。比如针对UserController下方法的单元测试应该统一放在UserControllerTest或者UserController_Test

单元测试方法名

单元测试的方法名应该具有可读性,让整个测试方法在不需要注释说明的情况下可以被读懂。格式应该类似遵守如下

Copy<被测试方法全名>_<期望的结果>_<给予的条件>// 例子
[Fact]
public void Add_InputNullOrAlphabetic_ThrowsArgumentException()
{...
}

4. 常用类库介绍

4.1 xUnit/MsTest/NUnit

编写.Net Core的单元测试绕不过要选择一个单元测试的框架, 三大单元测试框架中

  • MsTest是微软官方出品的一个测试框架

  • NUnit没用过

  • xUnit是.Net Foundation下的一个开源项目,并且被dotnet github上很多仓库(包括runtime)使用的单元测试框架

三大测试框架发展至今已是大差不差, 很多时候选择只是靠个人的喜好。

个人偏好xUnit简洁的断言

Copy// xUnit
Assert.True()
Assert.Equal()// MsTest
Assert.IsTrue()
Assert.AreEqual()

客观地功能性地分析三大框架地差异可以参考如下

https://anarsolutions.com/automated-unit-testing-tools-comparison

4.2 Moq

官方仓库

  • https://github.com/moq/moq4

Moq是一个非常流行的模拟库, 只要有一个接口它就可以动态生成一个对象, 底层使用的是Castle的动态代理功能.

基本用法

在实际使用中可能会有如下场景

Copypublic class UserController
{private readonly IUserService _userService;public UserController(IUserService userService){_userService = userService;}[HttpGet("{id}")]public IActionResult GetUser(int id){var user = _userService.GetUser(id);if (user == null){return NotFound();}else{...}}
}

在进行单元测试的时候, 可以使用Moq_userService.GetUser进行模拟返回值

Copy[Fact]
public void GetUser_ShouldReturnNotFound_WhenCannotFoundUser()
{// arrange// 新建一个IUserService的mock对象var mockUserService = new Mock<IUserService>();// 使用moq对IUserService的GetUs方法进行mock: 当入参为233时返回nullmockUserService.Setup(it => it.GetUser(233)).Return((User)null);var controller = new UserController(mockUserService.Object);// actvar actual = controller.GetUser(233) as NotFoundResult;// assert// 验证调用过userService的GetUser方法一次,且入参为233mockUserService.Verify(it => it.GetUser(233), Times.AtMostOnce());
}

4.3 AutoFixture

官方仓库

  • https://github.com/AutoFixture/AutoFixture

AutoFixture是一个假数据填充库,旨在最小化3A中的arrange阶段,使开发人员更容易创建包含测试数据的对象,从而可以更专注与测试用例的设计本身。

基本用法

直接使用如下的方式创建强类型的假数据

Copy[Fact]
public void IntroductoryTest()
{// arrangeFixture fixture = new Fixture();int expectedNumber = fixture.Create<int>();MyClass sut = fixture.Create<MyClass>();// actint result = sut.Echo(expectedNumber);// assertAssert.Equal(expectedNumber, result);
}

上述示例也可以和测试框架本身结合,比如xUnit

Copy[Theory, AutoData]
public void IntroductoryTest(int expectedNumber, MyClass sut)
{// actint result = sut.Echo(expectedNumber);// assertAssert.Equal(expectedNumber, result);
}

5. 实践中结合Visual Studio的使用

Visual Studio提供了完备的单元测试的支持,包括运行. 编写. 调试单元测试。以及查看单元测试覆盖率等。

5.1 如何在Visual Studio中运行单元测试

5.2 如何在Visual Studio中查看单元测试覆盖率

如下功能需要Visual Studio 2019 Enterprise版本,社区版不带这个功能。

如何查看覆盖率

  • 在测试窗口下,右键相应的测试组

  • 点击如下的"分析代码覆盖率"

6. 实践中常见场景的Mock

主要

6.1 DbSet

使用EF Core过程中,如何mock DbSet是一个绕不过的坎。

方法一

参考如下链接的回答进行自行封装

https://stackoverflow.com/questions/31349351/how-to-add-an-item-to-a-mock-dbset-using-moq

方法二(推荐)

使用现成的库(也是基于上面的方式封装好的)

仓库地址:

  • https://github.com/romantitov/MockQueryable

使用范例

Copy// 1. 测试时创建一个模拟的List<T>
var users = new List<UserEntity>()
{new UserEntity{LastName = "ExistLastName", DateOfBirth = DateTime.Parse("01/20/2012")},...
};// 2. 通过扩展方法转换成DbSet<UserEntity>
var mockUsers = users.AsQueryable().BuildMock();// 3. 赋值给给mock的DbContext中的Users属性
var mockDbContext = new Mock<DbContext>();
mockDbContext.Setup(it => it.Users).Return(mockUsers);

6.2 HttpClient

使用RestEase/Refit的场景

如果使用的是RestEase或者Refit等第三方库,具体接口的定义本质上就是一个interface,所以直接使用moq进行方法mock即可。

并且建议使用这种方式。

IHttpClientFactory

如果使用的是.Net Core自带的IHttpClientFactory方式来请求外部接口的话,可以参考如下的方式对IHttpClientFactory进行mock

https://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/

6.3 ILogger

由于ILogger的LogError等方法都是属于扩展方法,所以不需要特别的进行方法级别的mock。
针对平时的一些使用场景封装了一个帮助类, 可以使用如下的帮助类进行Mock和Verify

Copypublic static class LoggerHelper
{public static Mock<ILogger<T>> LoggerMock<T>() where T : class{return new Mock<ILogger<T>>();}public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel level, string containMessage, Times times){loggerMock.Verify(x => x.Log(level,It.IsAny<EventId>(),It.Is<It.IsAnyType>((o, t) => o.ToString().Contains(containMessage)),It.IsAny<Exception>(),(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),times);}public static void VerifyLog<T>(this Mock<ILogger<T>> loggerMock, LogLevel level, Times times){loggerMock.Verify(x => x.Log(level,It.IsAny<EventId>(),It.IsAny<It.IsAnyType>(),It.IsAny<Exception>(),(Func<It.IsAnyType, Exception, string>)It.IsAny<object>()),times);}
}

使用方法

Copy[Fact]
public void Echo_ShouldLogInformation()
{// arrangevar mockLogger = LoggerHelpe.LoggerMock<UserController>();var controller = new UserController(mockLogger.Object);// actcontroller.Echo();// assertmockLogger.VerifyLog(LogLevel.Information, "hello", Times.Once());
}

7. 拓展

7.1 TDD介绍

TDD是测试驱动开发(Test-Driven Development)的英文简称. 一般是先提前设计好单元测试的各种场景再进行真实业务代码的编写,编织安全网以便将Bug扼杀在在摇篮状态。

此种开发模式以测试先行,对开发团队的要求较高, 落地可能会存在很多实际困难。详细说明可以参考如下

https://www.guru99.com/test-driven-development.html

参考链接

  • https://docs.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices

  • https://www.kiltandcode.com/2019/06/16/best-practices-for-writing-unit-tests-in-csharp-for-bulletproof-code/

  • https://github.com/AutoFixture/AutoFixture

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

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

相关文章

图论的各种基本算法

本篇主要涉及到图论的基本算法&#xff0c;不包含有关最大流的内容。图论的大部分算法都是由性质或推论得出来的&#xff0c;想朴素想出来确实不容易。二分图(Is-Bipartite)一个图的所有顶点可以划分成两个子集&#xff0c;使所有的边的入度和出度顶点分别在这两个子集中。这个…

社区 正式发布了跨平台的 CoreWCF 0.1.0 GA

CoreWCF 项目在2021.2.19 正式发布了0.1.0 GA版本:https://github.com/CoreWCF/CoreWCF/releases/tag/v0.1.0 &#xff0c;这个版本号虽然是0.1&#xff0c;但是它是可以投入生产的版本&#xff0c;而且是跨平台的&#xff0c;支持LInux部署WCF&#xff0c;当前仅支持http 和 n…

Prim 算法及其高效实现

转自&#xff1a;ivy-endhttp://www.ivy-end.com/archives/943背景最小生成树&#xff08;Minimum Spanning Trees&#xff09;&#xff0c;简称MST。是图论中一个非常重要的概念。解决这个问题有两种算法&#xff0c;今天暂且先来讨论一下Prim Algorithm。不做特别说明&#x…

Silverlight实例教程 - Validation数据验证开篇

说起来Validation验证功能&#xff0c;相信大家都不陌生&#xff0c;在应用中&#xff0c;当需要用户交互输入时&#xff0c;开发人员都会加入一些验证代码&#xff0c;这样可以有效的避免应用异常出现&#xff0c;也可以使应用的错误提示信息清晰明了的显示在客户端&#xff0…

一日一技:微信扫码用户帐号绑定

概述最近在整一个微信扫码用户帐号绑定功能。为了满足用户帐号绑定场景的需要&#xff0c;通过生成用户自己的二维码&#xff0c;用户扫描后&#xff0c;公众号可以接收到事件推送。如下1、用户登录扫码2、绑定成功实现思路扫码绑定账户&#xff0c;其实就是扫描带有用户信息的…

计算机起源的数学思想

人类的历史可以看做一部关于解放的历史。也有这样的说法&#xff0c;懒惰是人类进步的动力。为了偷懒&#xff0c;人类不断的做着各种努力&#xff0c;发明了各种机器工具&#xff0c;将自己从繁重的劳动解放出来&#xff0c;另一方面&#xff0c;每一次大的进步&#xff0c;都…

Redis 通过 RDB 方式进行数据备份与还原

Redis 通过 RDB 方式进行数据备份与还原Intro有的时候我们需要对 Redis 的数据进行迁移&#xff0c;今天介绍一下通过 RDB&#xff08;快照&#xff09;文件进行 Redis 数据的备份和还原Redis 持久化Redis 的数据持久化有两种机制&#xff0c;一种是 RDB(Redis Database)&#…

java proguard 使用_一步步教你使用Proguard混淆Java源代码

ava代码很容易被反编译&#xff0c;以下使用proguard来保护我们的代码proguard选项很多&#xff0c;容易迷糊&#xff0c;现在就把我的配置写下来(实际使用中)&#xff0c;以供参考2.准备好你的jar包&#xff0c;我在这里举例叫做test.jar。3.解压proguard&#xff0c;执行 bin…

稳定匹配问题——稳定婚姻算法设计

图片源自&#xff1a;美剧《How I met your mother》****本代码带有详细的注释&#xff0c;并在控制台输出时详细地说明了算法的过程&#xff0c;非常有助于新手理解稳定匹配问题和稳定婚姻算法的设计思路。****#include <iostream>using namespace std;bool finish_or_n…

如果诸葛亮用C#写出师表...

❝看到一篇18年的文章 "C版《出师表》"&#xff0c;站长觉得挺有意思的&#xff0c;就用C# 控制台也实现了一遍&#xff0c;技术上没啥难度&#xff0c;但复制代码费了1、2个小时&#xff0c;纯粹无聊写着玩&#xff0c;看者别在意枚举、类名、变量中文命名&#xff…

这16个数据可视化案例,惊艳了全球数据行业

数据可视化可以帮你更容易的解释趋势和统计数据。数据是非常强大的。当然&#xff0c;如果你能真正理解它想告诉你的内容&#xff0c;那它的强大之处就更能体现出来了。通过观察数字和统计数据的转换以获得清晰的结论并不是一件容易的事。必须用一个合乎逻辑的、易于理解的方式…

asp.net core 自定义 Content-Type

asp.net core 实现支持自定义 Content-TypeIntro我们最近有一个原本是内网的服务要上公网&#xff0c;在公网上有一层 Cloudflare 作为网站的公网流量提供者&#xff0c;CloudFlare 会有一层防火墙拦截掉一些非法的请求&#xff0c;我们有一些 API 会提交一些 html 内容&#x…

如何优雅的移植JavaScript组件到Blazor

Blazor作为一个新兴的交互式 Web UI 的框架&#xff0c;有其自身的优缺点&#xff0c;如果现有的 JavaScript 组件能移植到 Blazor&#xff0c;无疑让 Blazor 如虎添翼&#xff0c;本文就介绍一下自己在开发 BulmaRazor 组件库的时&#xff0c;封装现有的 JavaScript 组件的方法…

把握人工智能命脉的有效方法

最近广州的天气老是变幻无常&#xff0c;往往今天还热得要命第二天就寒风瑟瑟&#xff08;如下图&#xff09;&#xff0c;让小天甚是怀念每天艳阳高照的夏天&#xff0c;虽然热了点但好歹不用担心猝不及防地收到寒风暴雨黄色预警。说到夏天&#xff0c;不得不提一下1956年的那…

微软的焦虑?想多了!从.NET6 Preview2到大厂招聘,起飞

看了篇文章叫《从.NET看微软的焦虑》&#xff0c;这里忍不住先吐槽一下&#xff0c;看完不仅毫无收获&#xff0c;而且有一种先起个夺眼球的标题&#xff0c;然后再东拼西凑找证据。讲真的&#xff0c;微软市值基本上等于“阿里腾讯百度”三者之和&#xff0c;居然还焦虑的无法…

TED演讲:区块链将如何改变世界?看完太震撼了!

区块链是什么&#xff1f;如果你不知道&#xff0c;你应该了解&#xff1b;如果你知道&#xff0c;有可能你仍需要了解一些它工作原理。唐泰普斯科特在此使这改变世界、建立信任的科技变得简明易懂。他表示&#xff0c;这就是第二代互联网&#xff0c;将有可能改变我们的金钱、…

re管理器Java_自定义布局管理器-FormLayout

第二部分&#xff1a;自定义布局管理器在java.awt包与javax.swing包下有许多现成的布局类&#xff0c;比如BorderLayout、FlowLayout&#xff0c;还有较为复杂的、用于精确定位的布局类GridBagLayout、SpringLayout等。起初我刚刚从事gooey时(06年中)&#xff0c;企图依靠JDK自…

如何看待 70% 的程序员,缺乏数据结构和算法知识?

金三银四来了&#xff0c;各大厂动静不小&#xff0c;都在储备人才&#xff0c;绝对是程序员面试的黄金时间了&#xff0c;不少同学也在后台反馈面试中遇到的一些问题&#xff0c;所以今天想跟大家说说算法。说起算法&#xff0c;那大厂面试是绝对必考的&#xff0c;可以说是一…

Sorry,关注这些 IT 技术类公众号,真的可以为所欲为

工作和生活节奏超快的今天&#xff0c;想要不断提升自我&#xff0c;碎片化阅读学习是你最佳的选择&#xff0c;如果你已经有了一颗学习的心&#xff0c;却苦于不知道从哪里学习&#xff0c;那么&#xff0c;这些学习的工具和途径就很重要了。今天为你推荐一些 IT技术领域的微信…

数据告诉你,抖音是如何在半年之内逆袭的

从春节至今&#xff0c;音乐短视频社区“抖音”在苹果应用商店免费排行榜上连续多天霸榜。凭借多元的音乐风格、酷炫的视觉编辑功能、个性化的分发机制以及良好的社区氛围&#xff0c;抖音在上线不久后便受到了年轻用户的追捧。在这一年半的时间里&#xff0c;抖音到底成长到了…