浅谈.Net Core后端单元测试的实现

论坛 期权论坛 脚本     
niminba   2021-5-23 05:23   1943   0

1. 前言

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

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

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

2. 为什么需要单元测试

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

2.1 防止回归

通常在进行新功能/模块的开发或者是重构的时候,测试会进行回归测试原有的已存在的功能,以验证以前实现的功能是否仍能按预期运行。

使用单元测试,可在每次生成后,甚至在更改一行代码后重新运行整套测试, 从而可以很大程度减少回归缺陷。

2.2 减少代码耦合

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

3. 基本原则和规范

 3.1 3A原则

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);
}

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

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

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

3.3 重构原则

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

3.4 避免多个断言

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

所以一般有两种解决方案

  • 拆分成多个测试方法
  • 使用参数化测试, 如下示例
[Theory]
[InlineData(null)]
[InlineData("a")]
public void Add_InputNullOrAlphabetic_ThrowsArgumentException(string input)
{
  // arrange
  var stringCalculator = new StringCalculator();

  // act
  Action actual = () => stringCalculator.Add(input);

  // assert
  Assert.Throws<ArgumentException>(actual);
}

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

3.5 文件和方法命名规范 文件名规范

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

单元测试方法名

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

<被测试方法全名>_<期望的结果>_<给予的条件>

// 例子
[Fact]
public void Add_InputNullOrAlphabetic_ThrowsArgumentException()
{
 ...
}

4. 常用类库介绍

4.1 xUnit/MsTest/NUnit

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

  • MsTest是微软官方出品的一个测试框架
  • NUnit没用过
  • xUnit是.Net Foundation下的一个开源项目,并且被dotnet github上很多仓库(包括runtime)使用的单元测试框架

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

个人偏好 xUnit 简洁的断言

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

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

6.3 ILogger

由于ILogger的LogError等方法都是属于扩展方法,所以不需要特别的进行方法级别的mock。

针对平时的一些使用场景封装了一个帮助类, 可以使用如下的帮助类进行Mock和Verify

public 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);
  }
}

使用方法

[Fact]
public void Echo_ShouldLogInformation()
{
  // arrange
  var mockLogger = LoggerHelpe.LoggerMock<UserController>();
  var controller = new UserController(mockLogger.Object);
  
  // act
  controller.Echo();
  
  // assert
  mockLogger.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

到此这篇关于浅谈.Net Core后端单元测试的实现的文章就介绍到这了,更多相关.Net Core 单元测试内容请搜索社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持社区!

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1060120
帖子:212021
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP