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流程示例:
- 先写一个失败的测试
[Test]
public void IsPrime_InputIs1_ReturnFalse()
{
bool result = PrimeService.IsPrime(1);
Assert.That(result, Is.False);
}
- 实现最简单的通过代码
public static class PrimeService
{
public static bool IsPrime(int candidate)
{
return false;
}
}
- 添加更多测试并重构
[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上运行的测试
}
最佳实践
- 测试命名:使用”MethodName_Scenario_ExpectedResult”命名约定
- 单一职责:每个测试只验证一个行为
- 独立测试:测试之间不应有依赖关系
- 避免逻辑:测试中不应包含条件或循环逻辑
- 快速反馈:保持测试快速执行
- 测试覆盖率:关注关键逻辑的覆盖率,而非盲目追求100%
- 持续集成:将测试纳入CI/CD流程
结论
NUnit是C#开发中强大的测试工具,掌握它可以显著提高代码质量和开发效率。本指南涵盖了从基础到高级的NUnit使用技巧,但实际应用中还需要根据项目特点灵活调整。随着.NET生态的发展,NUnit也在不断进化,建议定期查阅官方文档以了解最新特性。