ABP的单元测试使用

143 阅读12分钟

基本概念

单元测试

ABP框架的设计考虑了可测试性. 有一些不同级别的自动化测试:

  • 单元测试: 通常只测试一个类(或者一起测试几个类). 这些测试会很快. 然而, 你通常需要处理对服务依赖项的模拟.
  • 集成测试: 你通常会测试一个服务, 但这一次你不会模拟基本的基础设施和服务, 以查看它们是否正确地协同工作.
  • 用户界面测试: 测试应用程序的UI, 就像用户与应用程序交互一样.

单元测试 vs 集成测试

与单元测试相比, 集成测试有一些显著的优势:

  • 编写更加简单 因为你不需要模拟和处理依赖关系.
  • 你的测试代码运行于所有真正的服务和基础设施(包括数据库映射和查询), 因此它更接近于真正的应用程序测试.

同时它们有一些缺点:

  • 与单元测试相比, 它们更慢, 因为所有的基础设施都准备好了测试用例.
  • 服务中的一个bug可能会导致多个测试用例失败, 因此在某些情况下, 可能会更难找到真正的问题.

我们建议混合使用: 在必要的地方编写单元测试或集成测试, 并且有效的编写和维护它.

AAA 模式

Arrange-Act-Assert(AAA)是一种编写测试用例的模式,用于清晰地组织测试代码,使之更易于阅读和理解。这种模式的主要目标是确保每个测试都是独立和自描述的,通过将测试结构化成三个明确的部分:设置(Arrange)、执行(Act)和断言(Assert)。

Arrange:设置输入数据,初始化对象

Act:执行测试行为,获得输出和响应

Assert: 验证Act阶段的输出和效果,是否符合期望

示例如下

using Xunit;
using Shouldly;

public class CalculatorTests
{
    public class Calculator
    {
        public int Add(int a, int b) => a + b;
    }

    [Fact]
    public void Add_ShouldSumValues()
    {
        // Arrange
        var calculator = new Calculator();
        int a = 5, b = 3;

        // Act
        var result = calculator.Add(a, b);

        // Assert
        result.ShouldBe(8);
    }
}

ABP 内置的测试基础设施

解决方案中已经安装了以下库:

xUnit:单元测试框架

xUnit 是一个流行的 .NET 单元测试框架,用于创建和执行单元测试。下面是 xUnit 的一些基本用法,包括测试类和测试方法的设置,以及一些常用的断言。

安装 xUnit

首先,需要为你的 .NET 项目添加 xUnit 的 NuGet 包。如果使用 Visual Studio,可以通过 NuGet 包管理器安装;如果使用命令行,可以执行以下命令:

dotnet add package xunit
dotnet add package xunit.runner.visualstudio

没有这个包的话 xunit.runner.visualstudio,vs 无法支持

这些包分别是 xUnit 核心库和 Visual Studio 的测试运行器支持。

创建测试类和方法

xUnit 使用类和方法来组织测试。每个测试方法都装饰有 [Fact] 或 [Theory] 属性。[Fact] 用于不需要任何参数的测试;[Theory] 用于需要参数的数据驱动测试。

示例代码

下面是一个示例测试类,展示了几种基本的测试方法:

using Xunit;

public class CalculatorTests
{
    [Fact]
    public void CanAddTwoNumbers()
    {
        // Arrange
        var calculator = new Calculator();
        int a = 5;
        int b = 3;

        // Act
        var result = calculator.Add(a, b);

        // Assert
        Assert.Equal(8, result);
    }

    [Theory]
    [InlineData(3, 2, 5)]
    [InlineData(-3, -2, -5)]
    [InlineData(3, -2, 1)]
    public void CanAddNumbers(int a, int b, int expected)
    {
        // Arrange
        var calculator = new Calculator();

        // Act
        var result = calculator.Add(a, b);

        // Assert
        Assert.Equal(expected, result);
    }

    [Fact]
    public void DivisionByZeroThrows()
    {
        // Arrange
        var calculator = new Calculator();

        // Act and Assert
        Assert.Throws<DivideByZeroException>(() => calculator.Divide(10, 0));
    }
}

public class Calculator
{
    public int Add(int a, int b) => a + b;
    public int Divide(int a, int b) => a / b;
}

常用的断言方法

xUnit 提供了 Assert 类,它包含用于断言的各种静态方法。这些方法用于验证代码的行为。一些常见的断言方法包括:

  • Assert.Equal(expected, actual):验证两个值是否相等。
  • Assert.True(condition):验证条件是否为真。
  • Assert.False(condition):验证条件是否为假。
  • Assert.NotNull(object):验证对象不为空。
  • Assert.Throws(action):验证执行动作时抛出特定类型的异常。

运行测试

如果使用 Visual Studio,可以在“测试资源管理器”中运行和查看测试结果。如果使用命令行,可以运行以下命令:

dotnet test

这将运行项目中所有的测试,并输出结果。

以上是 xUnit 的基本用法和示例,可以根据这些基础开始构建更复杂的测试案例。

NSubstitute:模拟库

NSubstitute 是一个用于 .NET 的模拟库,常用于单元测试中来模拟(mock)依赖项的行为。它提供了一个简单的 API 来创建和配置模拟对象,使得你可以专注于测试代码的特定部分,而不是依赖整个应用的其它部分。

安装 NSubstitute

首先,需要为你的测试项目添加 NSubstitute 的 NuGet 包。如果使用 Visual Studio,可以通过 NuGet 包管理器安装;如果使用命令行,可以执行以下命令:

dotnet add package NSubstitute

创建和使用模拟对象

使用 NSubstitute 创建模拟对象非常直接。你只需调用 Substitute.For() 方法,其中 T 是你想要模拟的接口或类。

示例代码

以下是一个使用 NSubstitute 的测试类示例:

using Xunit;
using NSubstitute;

public interface ICalculator
{
    int Add(int a, int b);
    int Divide(int a, int b);
}

public class CalculatorService
{
    private readonly ICalculator _calculator;

    public CalculatorService(ICalculator calculator)
    {
        _calculator = calculator;
    }

    public int PerformAddition(int a, int b)
    {
        return _calculator.Add(a, b);
    }

    public int SafeDivision(int a, int b)
    {
        if (b == 0) return 0;
        return _calculator.Divide(a, b);
    }
}

public class CalculatorServiceTests
{
    [Fact]
    public void PerformAddition_CallsAddMethodOfCalculator()
    {
        // Arrange
        var calculator = Substitute.For<ICalculator>();
        var service = new CalculatorService(calculator);
        int a = 5, b = 3;

        // Act
        service.PerformAddition(a, b);

        // Assert 这个只是判断这个方法是否被调用的,并不是真正调用这个方法
        calculator.Received().Add(a, b);
    }

    [Fact]
    public void SafeDivision_DividesIfNotZero()
    {
        // Arrange
        var calculator = Substitute.For<ICalculator>();
        //给这个模拟对象的方法设置返回值
        calculator.Divide(10, 2).Returns(5);
        var service = new CalculatorService(calculator);

        // Act
        var result = service.SafeDivision(10, 2);

        // Assert
        Assert.Equal(5, result);
    }
}

常用的 NSubstitute 功能

  • Substitute.For(): 创建一个模拟对象。
  • Received(): 检查某个方法是否被调用。
  • Returns(): 配置一个方法的返回值。
  • Throws(): 配置一个方法抛出异常。

运行测试

和 xUnit 测试一样,如果使用 Visual Studio,可以在“测试资源管理器”中运行测试。如果使用命令行,可以使用 dotnet test 命令运行测试。

这就是 NSubstitute 的基本用法和一些示例,帮助你开始创建复杂的模拟来测试你的 .NET 应用程序。

Shouldly:断言库

Shouldly 是一个断言库,它扩展了.NET的断言功能,提供了更丰富的语义和更详细的错误消息,使得单元测试的断言更直观和易于理解。Shouldly 让测试结果和测试断言看起来更像是英语句子,提高了代码的可读性。

dotnet add package Shouldly

Shouldly 通过扩展方法提供断言功能,这些方法可以直接在测试的值上调用。这种方式使得断言的意图更加明显,错误消息也更有帮助。

下面是一个使用 Shouldly 进行断言的测试类示例:

using Xunit;
using Shouldly;

public class CalculatorTests
{
    public class Calculator
    {
        public int Add(int a, int b) => a + b;
        public int Divide(int a, int b) => a / b;
    }

    [Fact]
    public void Add_ShouldSumValues()
    {
        // Arrange
        var calculator = new Calculator();
        int a = 5, b = 3;

        // Act
        var result = calculator.Add(a, b);

        // Assert
        result.ShouldBe(8);
    }

    [Fact]
    public void Divide_DivisionByZero_ShouldThrow()
    {
        // Arrange
        var calculator = new Calculator();

        // Act & Assert
        Should.Throw<DivideByZeroException>(() => calculator.Divide(10, 0));
    }

    [Fact]
    public void Add_ShouldNotSumIncorrectly()
    {
        // Arrange
        var calculator = new Calculator();
        int a = 5, b = 3;

        // Act
        var result = calculator.Add(a, b);

        // Assert
        result.ShouldNotBe(9);
    }
}

常用的 Shouldly 方法

  • ShouldBe(): 断言两个值应该相等。如果不相等,将给出详细的错误消息。
  • ShouldNotBe(): 断言两个值不应相等。
  • ShouldThrow(): 断言一个操作应该抛出特定的异常。
  • ShouldNotThrow(): 断言一个操作不应抛出异常。

如果没有这个库,那么需要写成Assert.Equal、Assert.Throw 这样的形式

Shouldly 提供了一种更自然的方式来编写断言,它的错误消息更有用,可以帮助开发者更快地诊断测试失败的原因。通过它的语法,测试代码变得更加清晰和易于维护。

创建单元测试项目步骤

1、创建一个类库项目

2、引入相关依赖

必须安装这个包,否则会报错

3、创建一个 TestBaseModule 类

[DependsOn(typeof(TextTemplatingScribanModule),
        typeof(AbpTestBaseModule),
        typeof(AbpAutofacModule))]
public class TextTemplatingScribanTestBaseModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        //添加测试时始终允许授权
        context.Services.AddAlwaysAllowAuthorization();
    }
}

4、创建 TestBase 基类

public class TextTemplatingScribanTestBase : AbpIntegratedTest<TextTemplatingScribanTestBaseModule>
{
    // 在这里配置你的测试环境
    protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
    {
        options.UseAutofac();
    }
}

其中,AbpIntegratedTest 类用于创建集成测试的基类。这种测试类型主要用于在一个更接近生产环境的设置中测试应用程序的组件,包括数据库访问、文件系统交互、第三方服务集成等。

EvaluatorsJintTestBase 继承自 AbpIntegratedTest,这里的 T 是 EvaluatorsJintTestBaseModule。这表明这个测试基类将使用 ABP 框架中的依赖注入、配置和初始化机制,所有这些都配置在 EvaluatorsJintTestBaseModule 模块中。

SetAbpApplicationCreationOptions 方法用于配置 ABP 应用的创建选项 。 在 ABP 框架中,默认使用的是自带的简单依赖注入容器,可以通过 UseAutofac() 方法切换到更复杂的 Autofac 容器。

5、创建...Tests 类,编写测试方法

public class TextTemplatingScribanTests : TextTemplatingScribanTestBase
{
    private readonly ICustomTemplateRenderer _customTemplateRenderer;

    public TextTemplatingScribanTests()
    {
        _customTemplateRenderer = GetRequiredService<ICustomTemplateRenderer>();
    }

    [Fact]
    public async Task Render_Template_ShoubleReturnResult()
    {
        // Arrange
        var templateContent = "Hello {{ name }}!";
        var engineName = "Scriban";
        var model = new
        {
            name = "World"
        };
        // Act
        var result = await _customTemplateRenderer.RenderTemplateAsync(templateContent, engineName, model);
        // Assert
        result.ShouldBe("Hello World");
    }
}

6、 运行测试项目

方法一:

dotnet test Arim.TextTemplating.Core.Tests
//如果当前目录就是单元测试项目,则可以直接运行
dotnet test 

方法二:

然后在这个窗口可进行运行,调试(可进入断点)链接

其他测试工具介绍

代码覆盖率:coverlet.collector

coverlet.collector 是一个流行的 .NET 库,用于在 .NET 应用程序中收集代码覆盖率数据。它是用于 .NET Core 项目的跨平台代码覆盖工具之一,尤其在与测试框架和持续集成流程集成时非常有用。

主要功能

  1. 跨平台支持:Coverlet 提供了在 Windows、Linux 和 macOS 上运行测试并收集代码覆盖率数据的能力。
  2. 集成测试工具:它可以与常用的测试框架(如 xUnit, NUnit, MSTest)无缝集成。
  3. 多种输出格式:Coverlet 支持多种代码覆盖率结果输出格式,如 opencover、cobertura、json 等,这使得它可以与各种持续集成工具(如 Jenkins、TeamCity、Azure DevOps)和代码覆盖率分析工具(如 SonarQube、ReportGenerator)配合使用。
  4. 命令行工具:虽然 coverlet.collector 是作为数据收集器在测试中使用,Coverlet 也提供了一个命令行工具 coverlet.console,用于在命令行环境中执行覆盖率收集。
  5. 无需安装 SDK:与某些其他代码覆盖工具不同,使用 Coverlet 不需要安装额外的 SDK 或软件,只需要在测试项目中添加 NuGet 包即可。

使用场景

  • 单元测试代码覆盖率:在执行单元测试时自动收集代码覆盖率数据。
  • 集成测试覆盖率:在更复杂的集成测试场景中收集覆盖率数据,帮助开发者理解哪些代码被测试覆盖到,哪些没有。
  • 持续集成/持续部署(CI/CD) :在 CI/CD 管道中自动收集和报告代码覆盖率数据,以提高代码质量。

示例:在 .NET 项目中使用 coverlet.collector

首先,需要在测试项目中通过 NuGet 添加 coverlet.collector 包。然后,可以在 .csproj 文件中配置测试数据收集器,如下所示:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
    </PackageReference>
    <PackageReference Include="coverlet.collector" Version="3.0.2" />
  </ItemGroup>

</Project>

接着,在运行测试时可以通过命令行指定收集器:

dotnet test --collect:"XPlat Code Coverage"

运行后,会在 TestResults 目录中生成覆盖率报告。

单元测试框架: MSTest

MSTest 是一个由 Microsoft 提供的测试框架,用于支持 .NET 应用程序中的单元测试和集成测试。MSTest 是 Visual Studio 的一部分,同时也是一个独立的 NuGet 包,可以用于不同类型的 .NET 项目,如 .NET Framework、.NET Core 和 .NET 5/6等。

主要特点

  1. 集成度高:MSTest 与 Visual Studio 集成非常紧密,提供了一套丰富的测试功能和直观的用户界面来执行测试和查看结果。
  2. 跨平台支持:虽然最初主要用于 .NET Framework,但随着 MSTest V2 的推出,现在也支持在 .NET Core 和其他.NET 版本上运行,这使得它可以在 Windows、Linux 和 macOS 上执行。
  3. 简单的使用方法:MSTest 提供了易于理解和实施的属性和断言库,使开发者能够快速编写和运行测试。
  4. 数据驱动测试:MSTest 支持数据驱动测试,可以从外部数据源(如数据库或 Excel 文件)动态地输入测试数据。
  5. 丰富的断言库:提供了一系列断言方法,帮助验证测试中的条件是否满足预期。
  6. 执行筛选:支持在命令行或通过 Visual Studio 进行测试筛选,允许开发者运行指定的测试集。

使用场景

  • 单元测试:验证代码的各个部分(如类和方法)是否按预期工作。
  • 集成测试:测试应用程序中各个组件的集成点,确保它们在一起时能正常工作。
  • 回归测试:在代码修改后确保现有功能仍然正确无误。
  • 持续集成/持续部署:在自动化构建过程中运行测试,确保代码更改不会破坏现有功能。

示例:在 .NET Core 项目中使用 MSTest

首先,在测试项目中添加 MSTest 相关的 NuGet 包:

<ItemGroup>
  <PackageReference Include="MSTest.TestFramework" Version="2.2.3" />
  <PackageReference Include="MSTest.TestAdapter" Version="2.2.3" />
</ItemGroup>

然后,编写一个简单的测试类:

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ExampleTests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            Assert.AreEqual(1, 1);
        }
    }
}

在 Visual Studio 中,可以直接在“测试资源管理器”中运行此测试,或在命令行中使用 dotnet test 命令。