时间:2021-05-18
单元测试一直都是"好处大家都知道很多,但是因为种种原因没有实施起来"的一个老大难问题。具体是否应该落地单元测试,以及落地的程度, 每个项目都有自己的情况。
本篇为个人认为"如何更好地写单元测试", 即更加 偏向实践向中夹杂一些理论的分享。
下列示例的单元测试框架为 xUnit , Mock库为 Moq
优点有很多, 这里提两点我个人认为的很明显的好处
通常在进行新功能/模块的开发或者是重构的时候,测试会进行回归测试原有的已存在的功能,以验证以前实现的功能是否仍能按预期运行。
使用单元测试,可在每次生成后,甚至在更改一行代码后重新运行整套测试, 从而可以很大程度减少回归缺陷。
当代码紧密耦合或者一个方法过长的时候,编写单元测试会变得很困难。当不去做单元测试的时候,可能代码的耦合不会给人感觉那么明显。为代码编写测试会自然地解耦代码,变相提高代码质量和可维护性。
3A分别是"arrange、act、assert", 分别代表一个合格的单元测试方法的三个阶段
一个单元测试方法可读性是编写测试时最重要的方面之一。 在测试中分离这些操作会明确地突出显示调用代码所需的依赖项、调用代码的方式以及尝试断言的内容.
所以在进行单元测试的编写的时候, 请使用注释标记出3A的各个阶段的, 如下示例
[Fact]public async Task VisitDataCompressExport_ShouldReturnEmptyResult_WhenFileTokenDoesNotExist(){ // arrange var mockFiletokenStore = new Mock<IFileTokenStore>(); mockFiletokenStore .Setup(it => it.Get(It.IsAny<string>())) .Returns(string.Empty); var controller = new StatController( mockFiletokenStore.Object, null); // act var actual = await controller.VisitDataCompressExport("faketoken"); // assert Assert.IsType<EmptyResult>(actual);}尽管私有方法可以通过反射进行直接测试,但是在大多数情况下,不需要直接测试私有的private方法, 而是通过测试公共public方法来验证私有的private方法。
可以这样认为:private方法永远不会孤立存在。更应该关心的是调用private方法的public方法的最终结果。
如果一个类/方法,有很多的外部依赖,造成单元测试的编写困难。那么应该考虑当前的设计和依赖项是否合理。是否有部分可以存在解耦的可能性。选择性重构原有的方法,而不是硬着头皮写下去.
3.4 避免多个断言
如果一个测试方法存在多个断言,可能会出现某一个或几个断言失败导致整个方法失败。这样不能从根本上知道是了解测试失败的原因。
所以一般有两种解决方案
当然如果是对对象进行断言, 可能会对对象的多个属性都有断言。此为例外。
一般有两种。比如针对 UserController 下方法的单元测试应该统一放在 UserControllerTest 或者 UserController_Test 下
单元测试方法名
单元测试的方法名应该具有可读性,让整个测试方法在不需要注释说明的情况下可以被读懂。格式应该类似遵守如下
<被测试方法全名>_<期望的结果>_<给予的条件>// 例子[Fact]public void Add_InputNullOrAlphabetic_ThrowsArgumentException(){ ...}编写.Net Core的单元测试绕不过要选择一个单元测试的框架, 三大单元测试框架中
三大测试框架发展至今已是大差不差, 很多时候选择只是靠个人的喜好。
个人偏好 xUnit 简洁的断言
// xUnitAssert.True()Assert.Equal()// MsTestAssert.IsTrue()Assert.AreEqual()客观地功能性地分析三大框架地差异可以参考如下
https://anarsolutions.com/automated-unit-testing-tools-comparison
官方仓库
https://github.com/moq/moq4
Moq是一个非常流行的模拟库, 只要有一个接口它就可以动态生成一个对象, 底层使用的是Castle的动态代理功能.
基本用法
在实际使用中可能会有如下场景
public 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 进行模拟返回值
[Fact]public void GetUser_ShouldReturnNotFound_WhenCannotFoundUser(){ // arrange // 新建一个IUserService的mock对象 var mockUserService = new Mock<IUserService>(); // 使用moq对IUserService的GetUs方法进行mock: 当入参为233时返回null mockUserService .Setup(it => it.GetUser(233)) .Return((User)null); var controller = new UserController(mockUserService.Object); // act var actual = controller.GetUser(233) as NotFoundResult; // assert // 验证调用过userService的GetUser方法一次,且入参为233 mockUserService.Verify(it => it.GetUser(233), Times.AtMostOnce());}官方仓库
https://github.com/AutoFixture/AutoFixture
AutoFixture是一个假数据填充库,旨在最小化3A中的 arrange 阶段,使开发人员更容易创建包含测试数据的对象,从而可以更专注与测试用例的设计本身。
基本用法
直接使用如下的方式创建强类型的假数据
[Fact]public void IntroductoryTest(){ // arrange Fixture fixture = new Fixture(); int expectedNumber = fixture.Create<int>(); MyClass sut = fixture.Create<MyClass>(); // act int result = sut.Echo(expectedNumber); // assert Assert.Equal(expectedNumber, result);}上述示例也可以和测试框架本身结合,比如xUnit
[Theory, AutoData]public void IntroductoryTest( int expectedNumber, MyClass sut){ // act int result = sut.Echo(expectedNumber); // assert Assert.Equal(expectedNumber, result);}Visual Studio提供了完备的单元测试的支持,包括运行. 编写. 调试单元测试。以及查看单元测试覆盖率等。
如下功能需要Visual Studio 2019 Enterprise版本,社区版不带这个功能。
如何查看覆盖率
主要
使用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
使用范例
// 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);使用RestEase/Refit的场景
如果使用的是 RestEase 或者 Refit 等第三方库,具体接口的定义本质上就是一个interface,所以直接使用moq进行方法mock即可。
并且建议使用这种方式。
IHttpClientFactory
如果使用的是.Net Core自带的 IHttpClientFactory 方式来请求外部接口的话,可以参考如下的方式对 IHttpClientFactory 进行mock
https:///AutoFixture/AutoFixture
到此这篇关于浅谈.Net Core后端单元测试的实现的文章就介绍到这了,更多相关.Net Core 单元测试内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
为什么要进行单元测试?单元测试保证局部代码的质量单元测试改良项目代码的整体结构单元测试降低测试、维护升级的成本单元测试使开发过程适应频繁变化的需求单元测试有助于
1、前言“不会写单元测试的程序员不是合格的程序员,不写单元测试的程序员不是优秀的工程师。”那么问题来了,什么是单元测试,如何做单元测试。2、单元测试2.1单元测
在进行使用的eclipse的进行开发的代码中,必然就会需要进行单元测试,在单元测试的情况提供较多的框架单元测试,例如使用junit单元测试,而在国外进行开发较好
测试分类按开发阶段划分单元测试单元测试又称模块测试,针对软件设计中的最小单位——程序模块,进行正确性检查的测试工作。集成测试集成测试又叫组装测试,通常在单元测试
什么是单元测试?单元测试又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。单元测试主要是用来检验程式的内部逻辑,也称为个体测试、结