C# NUnit使用指南:从入门到精通


NUnit是.NET生态中最流行的单元测试框架之一,它提供了丰富的特性来帮助开发者编写可靠、可维护的单元测试。本指南将全面介绍如何在C#项目中使用NUnit进行单元测试。

1. 环境配置

安装NUnit

首先,通过NuGet包管理器安装必要的NUnit组件:

dotnet add package NUnit
dotnet add package NUnit3TestAdapter
dotnet add package Microsoft.NET.Test.Sdk

对于Visual Studio用户,也可以使用NuGet包管理器界面搜索并安装这些包。

创建测试项目

建议为单元测试创建单独的项目:

dotnet new nunit -n MyProject.Tests

2. 基本测试结构

测试类

测试类需要用[TestFixture]特性标记:

[TestFixture]
public class CalculatorTests
{
    private Calculator _calculator;

    [SetUp]
    public void Setup()
    {
        _calculator = new Calculator();
    }

    [Test]
    public void Add_TwoNumbers_ReturnsSum()
    {
        // Arrange
        int a = 5;
        int b = 3;

        // Act
        int result = _calculator.Add(a, b);

        // Assert
        Assert.That(result, Is.EqualTo(8));
    }
}

生命周期方法

  • [SetUp]:每个测试方法执行前运行
  • [TearDown]:每个测试方法执行后运行
  • [OneTimeSetUp]:测试类中第一个测试方法执行前运行
  • [OneTimeTearDown]:测试类中所有测试方法执行后运行

3. 断言方法

NUnit提供了丰富的断言方法:

基本断言

Assert.That(result, Is.EqualTo(expected));  // 推荐方式
Assert.AreEqual(expected, result);          // 传统方式
Assert.IsTrue(condition);
Assert.IsFalse(condition);
Assert.IsNull(obj);
Assert.IsNotNull(obj);

集合断言

var numbers = new List<int> { 1, 2, 3 };

Assert.That(numbers, Is.Not.Empty);
Assert.That(numbers, Has.Exactly(3).Items);
Assert.That(numbers, Does.Contain(2));
Assert.That(numbers, Is.Ordered.Ascending);

异常断言

// 方式1
var ex = Assert.Throws<ArgumentException>(() => SomeMethod());
Assert.That(ex.Message, Does.Contain("invalid"));

// 方式2
Assert.That(() => SomeMethod(), 
    Throws.ArgumentException.With.Message.Contains("invalid"));

4. 参数化测试

[TestCase] 属性

[TestCase(1, 2, 3)]
[TestCase(0, 0, 0)]
[TestCase(-1, -1, -2)]
public void Add_TwoNumbers_ReturnsSum(int a, int b, int expected)
{
    int result = _calculator.Add(a, b);
    Assert.That(result, Is.EqualTo(expected));
}

[TestCaseSource] 属性

private static readonly object[] AddCases =
{
    new object[] { 1, 2, 3 },
    new object[] { 0, 0, 0 },
    new object[] { -1, -1, -2 }
};

[TestCaseSource(nameof(AddCases))]
public void Add_TwoNumbers_ReturnsSum(int a, int b, int expected)
{
    int result = _calculator.Add(a, b);
    Assert.That(result, Is.EqualTo(expected));
}

[ValueSource] 属性

private static int[] Values = { 1, 2, 3 };

[Test]
public void TestWithValues([ValueSource(nameof(Values))] int value)
{
    Assert.That(value, Is.Positive);
}

5. 测试分组与筛选

[Category] 属性

[Test]
[Category("Fast")]
public void FastTest()
{
    // 快速运行的测试
}

[Test]
[Category("Slow")]
public void SlowTest()
{
    // 运行较慢的测试
}

运行特定类别的测试:

dotnet test --filter TestCategory=Fast

6. 模拟与依赖注入

使用Moq进行模拟

首先安装Moq:

dotnet add package Moq

示例:

[Test]
public void ProcessOrder_ShouldSendEmail()
{
    // Arrange
    var mockEmailService = new Mock<IEmailService>();
    var orderProcessor = new OrderProcessor(mockEmailService.Object);
    var order = new Order();

    // Act
    orderProcessor.Process(order);

    // Assert
    mockEmailService.Verify(
        x => x.SendEmail(
            order.CustomerEmail, 
            It.IsAny<string>(), 
            It.IsAny<string>()),
        Times.Once);
}

7. 异步测试

NUnit完全支持异步测试:

[Test]
public async Task GetUserAsync_ReturnsUser()
{
    // Arrange
    var repository = new UserRepository();
    int userId = 1;

    // Act
    var user = await repository.GetUserAsync(userId);

    // Assert
    Assert.That(user, Is.Not.Null);
    Assert.That(user.Id, Is.EqualTo(userId));
}

8. 性能测试

NUnit可以测量测试执行时间:

[Test]
[Timeout(1000)] // 测试必须在1秒内完成
public void PerformanceTest()
{
    // 性能敏感的代码
}

[Test]
[MaxTime(500)] // 测试通常应在500ms内完成,但不会失败
public void NonCriticalPerformanceTest()
{
    // 代码
}

9. 测试驱动开发(TDD)实践

TDD流程示例:

  1. 先写一个失败的测试
[Test]
public void IsPrime_InputIs1_ReturnFalse()
{
    bool result = PrimeService.IsPrime(1);
    Assert.That(result, Is.False);
}
  1. 实现最简单的通过代码
public static class PrimeService
{
    public static bool IsPrime(int candidate)
    {
        return false;
    }
}
  1. 添加更多测试并重构
[TestCase(2)]
[TestCase(3)]
[TestCase(5)]
public void IsPrime_PrimesLessThan10_ReturnTrue(int value)
{
    var result = PrimeService.IsPrime(value);
    Assert.That(result, Is.True);
}

10. 高级特性

自定义约束

public class EvenNumberConstraint : Constraint
{
    public override ConstraintResult ApplyTo<TActual>(TActual actual)
    {
        if (actual is int number)
        {
            return new ConstraintResult(
                this, 
                actual, 
                number % 2 == 0);
        }
        return new ConstraintResult(this, actual, false);
    }
}

public static class Is
{
    public static EvenNumberConstraint Even => new EvenNumberConstraint();
}

// 使用
Assert.That(4, Is.Even);

平台特定测试

[Test]
[Platform(Include = "Win")]
public void WindowsOnlyTest()
{
    // 只在Windows上运行的测试
}

最佳实践

  1. 测试命名:使用”MethodName_Scenario_ExpectedResult”命名约定
  2. 单一职责:每个测试只验证一个行为
  3. 独立测试:测试之间不应有依赖关系
  4. 避免逻辑:测试中不应包含条件或循环逻辑
  5. 快速反馈:保持测试快速执行
  6. 测试覆盖率:关注关键逻辑的覆盖率,而非盲目追求100%
  7. 持续集成:将测试纳入CI/CD流程

结论

NUnit是C#开发中强大的测试工具,掌握它可以显著提高代码质量和开发效率。本指南涵盖了从基础到高级的NUnit使用技巧,但实际应用中还需要根据项目特点灵活调整。随着.NET生态的发展,NUnit也在不断进化,建议定期查阅官方文档以了解最新特性。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注