青海建筑网站建设公司,企业网站分析,怎么改网站内容,南宁seo全网营销1. 前言单元测试一直都是好处大家都知道很多,但是因为种种原因没有实施起来的一个老大难问题。具体是否应该落地单元测试#xff0c;以及落地的程度, 每个项目都有自己的情况。本篇为个人认为如何更好地写单元测试, 即更加偏向实践向中夹杂一些理论的… 1. 前言单元测试一直都是好处大家都知道很多,但是因为种种原因没有实施起来的一个老大难问题。具体是否应该落地单元测试以及落地的程度, 每个项目都有自己的情况。本篇为个人认为如何更好地写单元测试, 即更加偏向实践向中夹杂一些理论的分享。下列示例的单元测试框架为xUnit, Mock库为Moq2. 为什么需要单元测试优点有很多, 这里提两点我个人认为的很明显的好处2.1 防止回归通常在进行新功能/模块的开发或者是重构的时候测试会进行回归测试原有的已存在的功能以验证以前实现的功能是否仍能按预期运行。使用单元测试可在每次生成后甚至在更改一行代码后重新运行整套测试, 从而可以很大程度减少回归缺陷。2.2 减少代码耦合当代码紧密耦合或者一个方法过长的时候编写单元测试会变得很困难。当不去做单元测试的时候可能代码的耦合不会给人感觉那么明显。为代码编写测试会自然地解耦代码变相提高代码质量和可维护性。3. 基本原则和规范3.1 3A原则3A分别是arrange、act、assert, 分别代表一个合格的单元测试方法的三个阶段事先的准备测试方法的实际调用针对返回值的断言一个单元测试方法可读性是编写测试时最重要的方面之一。在测试中分离这些操作会明确地突出显示调用代码所需的依赖项、调用代码的方式以及尝试断言的内容.所以在进行单元测试的编写的时候, 请使用注释标记出3A的各个阶段的, 如下示例Copy[Fact]
public async Task VisitDataCompressExport_ShouldReturnEmptyResult_WhenFileTokenDoesNotExist()
{// arrangevar mockFiletokenStore new MockIFileTokenStore();mockFiletokenStore.Setup(it it.Get(It.IsAnystring())).Returns(string.Empty);var controller new StatController(mockFiletokenStore.Object,null);// actvar actual await controller.VisitDataCompressExport(faketoken);// assertAssert.IsTypeEmptyResult(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.ThrowsArgumentException(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-comparison4.2 Moq官方仓库https://github.com/moq/moq4Moq是一个非常流行的模拟库, 只要有一个接口它就可以动态生成一个对象, 底层使用的是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 MockIUserService();// 使用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/AutoFixtureAutoFixture是一个假数据填充库旨在最小化3A中的arrange阶段使开发人员更容易创建包含测试数据的对象从而可以更专注与测试用例的设计本身。基本用法直接使用如下的方式创建强类型的假数据Copy[Fact]
public void IntroductoryTest()
{// arrangeFixture fixture new Fixture();int expectedNumber fixture.Createint();MyClass sut fixture.CreateMyClass();// actint result sut.Echo(expectedNumber);// assertAssert.Equal(expectedNumber, result);
}
上述示例也可以和测试框架本身结合比如xUnitCopy[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. 测试时创建一个模拟的ListT
var users new ListUserEntity()
{new UserEntity{LastName ExistLastName, DateOfBirth DateTime.Parse(01/20/2012)},...
};// 2. 通过扩展方法转换成DbSetUserEntity
var mockUsers users.AsQueryable().BuildMock();// 3. 赋值给给mock的DbContext中的Users属性
var mockDbContext new MockDbContext();
mockDbContext.Setup(it it.Users).Return(mockUsers);
6.2 HttpClient使用RestEase/Refit的场景如果使用的是RestEase或者Refit等第三方库具体接口的定义本质上就是一个interface所以直接使用moq进行方法mock即可。并且建议使用这种方式。IHttpClientFactory如果使用的是.Net Core自带的IHttpClientFactory方式来请求外部接口的话可以参考如下的方式对IHttpClientFactory进行mockhttps://www.thecodebuzz.com/unit-test-mock-httpclientfactory-moq-net-core/6.3 ILogger由于ILogger的LogError等方法都是属于扩展方法所以不需要特别的进行方法级别的mock。针对平时的一些使用场景封装了一个帮助类, 可以使用如下的帮助类进行Mock和VerifyCopypublic static class LoggerHelper
{public static MockILoggerT LoggerMockT() where T : class{return new MockILoggerT();}public static void VerifyLogT(this MockILoggerT loggerMock, LogLevel level, string containMessage, Times times){loggerMock.Verify(x x.Log(level,It.IsAnyEventId(),It.IsIt.IsAnyType((o, t) o.ToString().Contains(containMessage)),It.IsAnyException(),(FuncIt.IsAnyType, Exception, string)It.IsAnyobject()),times);}public static void VerifyLogT(this MockILoggerT loggerMock, LogLevel level, Times times){loggerMock.Verify(x x.Log(level,It.IsAnyEventId(),It.IsAnyIt.IsAnyType(),It.IsAnyException(),(FuncIt.IsAnyType, Exception, string)It.IsAnyobject()),times);}
}
使用方法Copy[Fact]
public void Echo_ShouldLogInformation()
{// arrangevar mockLogger LoggerHelpe.LoggerMockUserController();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-practiceshttps://www.kiltandcode.com/2019/06/16/best-practices-for-writing-unit-tests-in-csharp-for-bulletproof-code/https://github.com/AutoFixture/AutoFixture