在软件开发的自动化测试领域,单元测试框架的选择直接影响着项目的测试效率和代码质量。nUnit作为.NET生态中历史最悠久的单元测试框架之一,从最初的Java移植版本到如今功能完善的3.0+版本,已经成为.NET开发者必备的测试工具链核心组件。
这个实战项目不同于简单的"Hello World"式演示,而是基于我过去三年在金融、物联网领域实施nUnit的真实经验,系统梳理框架的进阶用法。你将看到:
推荐使用Visual Studio 2022 Community版(免费)作为IDE,配合.NET 6 LTS版本。特别注意:nUnit 3.x需要额外安装两个NuGet包:
bash复制Install-Package NUnit -Version 3.13.3
Install-Package NUnit3TestAdapter -Version 4.3.1
踩坑提示:如果使用VS Code开发,必须手动添加.runsettings文件配置测试发现策略,否则会出现"未发现测试"的错误。
采用分层架构组织测试代码:
code复制/Solution
/src
/MyApp (生产代码)
/test
/MyApp.UnitTests (nUnit测试项目)
/Properties
AssemblyInfo.cs
/Fixtures
OrderServiceTests.cs
/Helpers
CustomAssertions.cs
关键设计原则:
电商订单系统的测试案例:
csharp复制[TestFixture(typeof(SQLOrderRepository))]
[TestFixture(typeof(MongoOrderRepository))]
public class OrderServiceTests<TRepo> where TRepo : IOrderRepository, new()
{
private IOrderService _service;
[SetUp]
public void Setup()
{
var repo = new TRepo();
_service = new OrderService(repo);
}
[Test]
public void SubmitOrder_Should_CalculateTax()
{
var order = BuildTestOrder();
var result = _service.Submit(order);
Assert.That(result.Tax, Is.GreaterThan(0));
}
}
技术要点:
物流运费计算案例:
csharp复制[TestCase(5, "domestic", ExpectedResult = 10.0)]
[TestCase(15, "international", ExpectedResult = 45.0)]
public double CalculateShipping_Returns_CorrectRate(
double weight,
string region)
{
return ShippingCalculator.Calculate(weight, region);
}
private static IEnumerable<TestCaseData> GetEdgeCases()
{
yield return new TestCaseData(0.1, "domestic").Returns(5.0)
.SetName("MinWeight_Domestic");
yield return new TestCaseData(999, "international").Returns(250.0)
.SetName("MaxWeight_International");
}
[Test, TestCaseSource(nameof(GetEdgeCases))]
public void CalculateShipping_EdgeCases(double weight, string region)
{
// 测试实现...
}
参数化策略对比:
| 方式 | 适用场景 | 可读性 | 维护成本 |
|---|---|---|---|
| TestCase属性 | 简单枚举 | ★★★★ | 低 |
| TestCaseSource | 复杂数据 | ★★★ | 中 |
| ValueSource | 类型安全 | ★★ | 高 |
| 动态生成 | 大数据量 | ★ | 极高 |
物联网设备控制测试案例:
csharp复制[Test]
public async Task TurnOnDevice_When_Offline_ThrowsException()
{
var device = new MockDevice(isOnline: false);
async Task TestCode() => await device.TurnOn();
Assert.That(TestCode,
Throws.TypeOf<DeviceOfflineException>()
.With.Property("ErrorCode")
.EqualTo(500));
}
异步测试要点:
领域模型验证示例:
csharp复制public static class DomainAssertions
{
public static void ValidEntityId(this IResolveConstraint constraint, Guid id)
{
Assert.That(id, Is.Not.EqualTo(Guid.Empty), "ID不能为空");
Assert.That(id.ToString().Length, Is.EqualTo(36), "ID格式错误");
}
}
// 使用示例
[Test]
public void CreateUser_Generates_ValidId()
{
var user = new User("test");
Assert.That(user.Id, Is.ValidEntityId());
}
扩展建议:
在AssemblyInfo.cs中添加:
csharp复制[assembly: Parallelizable(ParallelScope.Fixtures)]
[assembly: LevelOfParallelism(4)]
并发策略对比:
性能提示:数据库测试建议使用ParallelScope.None,避免事务冲突
金融交易测试案例:
csharp复制[TestFixture]
public class TransactionTests
{
private IDbTransaction _tx;
[OneTimeSetUp]
public void InitDatabase()
{
_tx = TestDatabase.BeginTransaction();
}
[TearDown]
public void RollbackAfterEach()
{
_tx.Rollback();
}
[OneTimeTearDown]
public void Cleanup()
{
_tx.Dispose();
}
}
钩子执行顺序:
症状:VS测试窗口显示"No tests found"
检查清单:
xml复制<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
典型错误:
csharp复制// 错误示例:可能导致死锁
[Test]
public void BadAsyncTest()
{
var result = SomeAsyncMethod().Result;
Assert.That(result, Is.True);
}
修正方案:
csharp复制[Test]
public async Task GoodAsyncTest()
{
var result = await SomeAsyncMethod();
Assert.That(result, Is.True);
}
反模式案例:
csharp复制[Test]
public void TestA()
{
SharedResource.Value = 10;
// ...
}
[Test]
public void TestB()
{
var actual = SharedResource.Value; // 不可靠!
Assert.That(actual, Is.EqualTo(10));
}
解决方案: