C#进阶 一、NUnit 单元测试详解
1. 引用和安装
通过 NuGet 包管理器安装:
# 安装 NUnit 框架 Install-Package NUnit # 安装 NUnit SDK 框架 Microsoft.NET.Test.Sdk # 安装 NUnit 测试运行器 Install-Package NUnit3TestAdapter # 安装断言扩展(可选) Install-Package NUnit.Analyzers
2. 测试类定义
using NUnit.Framework;
[TestFixture] // 标识测试类,版本2.5以后已省略
public class CalculatorTests
{
private Calculator _calculator;
[SetUp] // 每个测试方法前执行
public void SetUp()
{
_calculator = new Calculator();
}
[TearDown] // 每个测试方法后执行
public void TearDown()
{
_calculator.Dispose();
}
}3. 常用测试属性
测试方法属性:
[Test] // 基本测试方法
public void Add_TwoNumbers_ReturnsSum()
{
// 测试代码
}参数化测试
[TestCase(2, 3, 5)] // 参数化测试
[TestCase(0, 0, 0)]
[TestCase(-1, 1, 0)]
public void Add_TwoNumbers_ReturnsCorrectSum(int a, int b, int expected)
{
var result = _calculator.Add(a, b);
Assert.AreEqual(expected, result);
}测试控制属性:
[Ignore("暂时跳过,等待修复")] // 跳过测试
[Test]
public void DeprecatedTest() { }设置超时时间(毫秒)
[MaxTime(100)] // 设置超时时间(毫秒)
[Test]
public void PerformanceTest() { }失败时重试次数
[Retry(3)] // 失败时重试次数
[Test]
public void FlakyTest() { }4. 常用断言方法
基本断言:
// 相等性断言 Assert.AreEqual(expected, actual); Assert.AreNotEqual(notExpected, actual);
// 布尔断言 Assert.IsTrue(condition); Assert.IsFalse(condition);
// 空值断言 Assert.IsNull(obj); Assert.IsNotNull(obj);
集合断言:
var collection = new List<int> { 1, 2, 3 };
Assert.That(collection, Is.Not.Empty);
Assert.That(collection, Has.Count.EqualTo(3));
Assert.That(collection, Contains.Item(2));
Assert.That(collection, Is.Ordered); // 检查是否有序约束模型(Constraint Model): // 现代 NUnit 推荐的语法 Assert.That(actual, Is.EqualTo(expected)); Assert.That(actual, Is.Not.Null); Assert.That(actual, Is.GreaterThan(0)); Assert.That(actual, Is.InRange(1, 10)); Assert.That(actual, Is.TypeOf<string>());
异常断言:
方法一:使用 Assert.Throws
var ex = Assert.Throws<ArgumentNullException>(() =>
_calculator.Divide(5, 0));
Assert.That(ex.ParamName, Is.EqualTo("divisor"));方法二:使用 Assert.That
Assert.That(() => _calculator.Divide(5, 0), Throws.TypeOf<DivideByZeroException>());
5. 数据驱动测试
[TestFixture]
public class DataDrivenTests
{
// 使用 TestCaseSource
private static readonly object[] AddCases =
{
new object[] { 1, 2, 3 },
new object[] { -1, -1, -2 },
new object[] { 0, 0, 0 }
};
[TestCaseSource(nameof(AddCases))]
public void Add_TestCases(int a, int b, int expected)
{
var result = a + b;
Assert.AreEqual(expected, result);
}
// 使用 ValueSource
private static int[] Values = { 1, 2, 3, 4, 5 };
[Test]
public void IsPositive_ValueSource([ValueSource(nameof(Values))] int value)
{
Assert.That(value, Is.GreaterThan(0));
}
// 使用 Random 数据
[Test]
public void RandomTest([Random(1, 100, 5)] int value)
{
Assert.That(value, Is.InRange(1, 100));
}
}6. 测试生命周期
[TestFixture]
public class LifecycleTests
{
// 一次性设置(整个测试类开始时)
[OneTimeSetUp]
public void OneTimeSetUp()
{
Console.WriteLine("整个测试类只执行一次");
}
// 每个测试方法前的设置
[SetUp]
public void SetUp()
{
Console.WriteLine("每个测试方法前执行");
}
[Test]
public void Test1()
{
Console.WriteLine("Test1 执行");
}
[Test]
public void Test2()
{
Console.WriteLine("Test2 执行");
}
// 每个测试方法后的清理
[TearDown]
public void TearDown()
{
Console.WriteLine("每个测试方法后执行");
}
// 一次性清理(整个测试类结束时)
[OneTimeTearDown]
public void OneTimeTearDown()
{
Console.WriteLine("整个测试类结束时执行一次");
}
}7. 异步测试
[Test]
public async Task GetDataAsync_ReturnsValidData()
{
// 异步方法测试
var result = await _service.GetDataAsync();
Assert.That(result, Is.Not.Null);
Assert.That(result.Count, Is.GreaterThan(0));
}
[Test]
public async Task SaveAsync_ThrowsException_WhenInvalid()
{
var invalidData = new Data();
// 异步异常测试
Assert.That(async () => await _service.SaveAsync(invalidData),
Throws.TypeOf<ValidationException>());
}8. 常用特性测试
[TestFixture]
public class AdvancedTests
{
[Test]
[Category("Integration")] // 分类测试
[Category("Slow")]
public void IntegrationTest()
{
// 集成测试代码
}
[Test]
[Order(1)] // 指定执行顺序
public void FirstTest() { }
[Test]
[Order(2)]
public void SecondTest() { }
[Test]
[Platform("Win")] // 平台特定测试
public void WindowsOnlyTest() { }
[Test]
[Repeat(5)] // 重复执行测试
public void RepeatableTest()
{
// 会执行5次
}
}9. 测试组合和理论
[TestFixture]
public class CombinatorialTests
{
// 组合测试:所有参数组合
[Test]
[Combinatorial]
public void TestAllCombinations(
[Values(true, false)] bool flag1,
[Values(1, 2, 3)] int number)
{
// 会运行 2 * 3 = 6 次测试
}
// 成对测试:减少组合数
[Test]
[Pairwise]
public void TestPairwise(
[Values("a", "b", "c")] string str,
[Values(1, 2)] int num,
[Values(true, false)] bool flag)
{
// 使用成对算法减少测试用例
}
}
