csharp-tunit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTUnit Best Practices
TUnit单元测试最佳实践
Your goal is to help me write effective unit tests with TUnit, covering both standard and data-driven testing approaches.
本文旨在帮助你使用TUnit编写高效的单元测试,涵盖标准测试和数据驱动测试两种方法。
Project Setup
项目设置
- Use a separate test project with naming convention
[ProjectName].Tests - Reference TUnit package and TUnit.Assertions for fluent assertions
- Create test classes that match the classes being tested (e.g., for
CalculatorTests)Calculator - Use .NET SDK test commands: for running tests
dotnet test - TUnit requires .NET 8.0 or higher
- 使用独立的测试项目,命名遵循规范
[项目名称].Tests - 引用TUnit包和TUnit.Assertions以使用流畅断言
- 创建与被测类对应的测试类(例如,针对类创建
Calculator)CalculatorTests - 使用.NET SDK测试命令:用于运行测试
dotnet test - TUnit需要.NET 8.0或更高版本
Test Structure
测试结构
- No test class attributes required (like xUnit/NUnit)
- Use attribute for test methods (not
[Test]like xUnit)[Fact] - Follow the Arrange-Act-Assert (AAA) pattern
- Name tests using the pattern
MethodName_Scenario_ExpectedBehavior - Use lifecycle hooks: for setup and
[Before(Test)]for teardown[After(Test)] - Use and
[Before(Class)]for shared context between tests in a class[After(Class)] - Use and
[Before(Assembly)]for shared context across test classes[After(Assembly)] - TUnit supports advanced lifecycle hooks like and
[Before(TestSession)][After(TestSession)]
- 不需要测试类属性(如xUnit/NUnit中的属性)
- 测试方法使用属性(而非xUnit中的
[Test])[Fact] - 遵循Arrange-Act-Assert(AAA)模式
- 测试命名使用的格式
方法名_场景_预期行为 - 使用生命周期钩子:用于初始化,
[Before(Test)]用于清理[After(Test)] - 使用和
[Before(Class)]为类中的测试提供共享上下文[After(Class)] - 使用和
[Before(Assembly)]为所有测试类提供共享上下文[After(Assembly)] - TUnit支持高级生命周期钩子,如和
[Before(TestSession)][After(TestSession)]
Standard Tests
标准测试
- Keep tests focused on a single behavior
- Avoid testing multiple behaviors in one test method
- Use TUnit's fluent assertion syntax with
await Assert.That() - Include only the assertions needed to verify the test case
- Make tests independent and idempotent (can run in any order)
- Avoid test interdependencies (use attribute if needed)
[DependsOn]
- 保持测试聚焦于单一行为
- 避免在一个测试方法中测试多个行为
- 使用TUnit的流畅断言语法
await Assert.That() - 仅保留验证测试用例所需的断言
- 确保测试独立且幂等(可按任意顺序运行)
- 避免测试间的依赖(如有需要可使用属性)
[DependsOn]
Data-Driven Tests
数据驱动测试
- Use attribute for inline test data (equivalent to xUnit's
[Arguments])[InlineData] - Use for method-based test data (equivalent to xUnit's
[MethodData])[MemberData] - Use for class-based test data
[ClassData] - Create custom data sources by implementing
ITestDataSource - Use meaningful parameter names in data-driven tests
- Multiple attributes can be applied to the same test method
[Arguments]
- 使用属性定义内联测试数据(等效于xUnit的
[Arguments])[InlineData] - 使用获取基于方法的测试数据(等效于xUnit的
[MethodData])[MemberData] - 使用获取基于类的测试数据
[ClassData] - 通过实现创建自定义数据源
ITestDataSource - 在数据驱动测试中使用有意义的参数名称
- 同一个测试方法可应用多个属性
[Arguments]
Assertions
断言
- Use for value equality
await Assert.That(value).IsEqualTo(expected) - Use for reference equality
await Assert.That(value).IsSameReferenceAs(expected) - Use or
await Assert.That(value).IsTrue()for boolean conditionsawait Assert.That(value).IsFalse() - Use or
await Assert.That(collection).Contains(item)for collectionsawait Assert.That(collection).DoesNotContain(item) - Use for regex pattern matching
await Assert.That(value).Matches(pattern) - Use or
await Assert.That(action).Throws<TException>()to test exceptionsawait Assert.That(asyncAction).ThrowsAsync<TException>() - Chain assertions with operator:
.Andawait Assert.That(value).IsNotNull().And.IsEqualTo(expected) - Use operator for alternative conditions:
.Orawait Assert.That(value).IsEqualTo(1).Or.IsEqualTo(2) - Use for DateTime and numeric comparisons with tolerance
.Within(tolerance) - All assertions are asynchronous and must be awaited
- 使用验证值相等
await Assert.That(value).IsEqualTo(expected) - 使用验证引用相等
await Assert.That(value).IsSameReferenceAs(expected) - 使用或
await Assert.That(value).IsTrue()验证布尔条件await Assert.That(value).IsFalse() - 使用或
await Assert.That(collection).Contains(item)验证集合await Assert.That(collection).DoesNotContain(item) - 使用验证正则表达式匹配
await Assert.That(value).Matches(pattern) - 使用或
await Assert.That(action).Throws<TException>()测试异常await Assert.That(asyncAction).ThrowsAsync<TException>() - 使用运算符链式断言:
.Andawait Assert.That(value).IsNotNull().And.IsEqualTo(expected) - 使用运算符设置备选条件:
.Orawait Assert.That(value).IsEqualTo(1).Or.IsEqualTo(2) - 使用进行DateTime和数值的容差比较
.Within(tolerance) - 所有断言都是异步的,必须使用await
Advanced Features
高级功能
- Use to repeat tests multiple times
[Repeat(n)] - Use for automatic retry on failure
[Retry(n)] - Use to control parallel execution limits
[ParallelLimit<T>] - Use to skip tests conditionally
[Skip("reason")] - Use to create test dependencies
[DependsOn(nameof(OtherTest))] - Use to set test timeouts
[Timeout(milliseconds)] - Create custom attributes by extending TUnit's base attributes
- 使用重复执行测试多次
[Repeat(n)] - 使用在测试失败时自动重试
[Retry(n)] - 使用控制并行执行限制
[ParallelLimit<T>] - 使用有条件地跳过测试
[Skip("reason")] - 使用创建测试依赖
[DependsOn(nameof(OtherTest))] - 使用设置测试超时时间
[Timeout(milliseconds)] - 通过继承TUnit的基类属性创建自定义属性
Test Organization
测试组织
- Group tests by feature or component
- Use for test categorization
[Category("CategoryName")] - Use for custom test names
[DisplayName("Custom Test Name")] - Consider using for test diagnostics and information
TestContext - Use conditional attributes like custom for platform-specific tests
[WindowsOnly]
- 按功能或组件对测试进行分组
- 使用对测试进行分类
[Category("CategoryName")] - 使用设置自定义测试名称
[DisplayName("Custom Test Name")] - 考虑使用进行测试诊断和信息获取
TestContext - 使用条件属性(如自定义的)处理平台特定测试
[WindowsOnly]
Performance and Parallel Execution
性能与并行执行
- TUnit runs tests in parallel by default (unlike xUnit which requires explicit configuration)
- Use to disable parallel execution for specific tests
[NotInParallel] - Use with custom limit classes to control concurrency
[ParallelLimit<T>] - Tests within the same class run sequentially by default
- Use with
[Repeat(n)]for load testing scenarios[ParallelLimit<T>]
- TUnit默认并行运行测试(与xUnit不同,xUnit需要显式配置)
- 使用为特定测试禁用并行执行
[NotInParallel] - 使用结合自定义限制类控制并发量
[ParallelLimit<T>] - 默认情况下,同一类中的测试按顺序执行
- 结合使用和
[Repeat(n)]进行负载测试场景[ParallelLimit<T>]
Migration from xUnit
从xUnit迁移
- Replace with
[Fact][Test] - Replace with
[Theory]and use[Test]for data[Arguments] - Replace with
[InlineData][Arguments] - Replace with
[MemberData][MethodData] - Replace with
Assert.Equalawait Assert.That(actual).IsEqualTo(expected) - Replace with
Assert.Trueawait Assert.That(condition).IsTrue() - Replace with
Assert.Throws<T>await Assert.That(action).Throws<T>() - Replace constructor/IDisposable with /
[Before(Test)][After(Test)] - Replace with
IClassFixture<T>/[Before(Class)][After(Class)]
Why TUnit over xUnit?
TUnit offers a modern, fast, and flexible testing experience with advanced features not present in xUnit, such as asynchronous assertions, more refined lifecycle hooks, and improved data-driven testing capabilities. TUnit's fluent assertions provide clearer and more expressive test validation, making it especially suitable for complex .NET projects.
- 将替换为
[Fact][Test] - 将替换为
[Theory],并使用[Test]提供数据[Arguments] - 将替换为
[InlineData][Arguments] - 将替换为
[MemberData][MethodData] - 将替换为
Assert.Equalawait Assert.That(actual).IsEqualTo(expected) - 将替换为
Assert.Trueawait Assert.That(condition).IsTrue() - 将替换为
Assert.Throws<T>await Assert.That(action).Throws<T>() - 将构造函数/IDisposable替换为/
[Before(Test)][After(Test)] - 将替换为
IClassFixture<T>/[Before(Class)][After(Class)]
为什么选择TUnit而非xUnit?
TUnit提供了现代化、快速且灵活的测试体验,具备xUnit所没有的高级功能,例如异步断言、更精细的生命周期钩子以及改进的数据驱动测试能力。TUnit的流畅断言提供了更清晰、更具表达性的测试验证,特别适用于复杂的.NET项目。