csharp-tunit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

TUnit 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.,
    CalculatorTests
    for
    Calculator
    )
  • Use .NET SDK test commands:
    dotnet test
    for running tests
  • 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
    [Test]
    attribute for test methods (not
    [Fact]
    like xUnit)
  • Follow the Arrange-Act-Assert (AAA) pattern
  • Name tests using the pattern
    MethodName_Scenario_ExpectedBehavior
  • Use lifecycle hooks:
    [Before(Test)]
    for setup and
    [After(Test)]
    for teardown
  • Use
    [Before(Class)]
    and
    [After(Class)]
    for shared context between tests in a class
  • Use
    [Before(Assembly)]
    and
    [After(Assembly)]
    for shared context across test classes
  • TUnit supports advanced lifecycle hooks like
    [Before(TestSession)]
    and
    [After(TestSession)]
  • 不需要测试类属性(如xUnit/NUnit中的属性)
  • 测试方法使用
    [Test]
    属性(而非xUnit中的
    [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
    [DependsOn]
    attribute if needed)
  • 保持测试聚焦于单一行为
  • 避免在一个测试方法中测试多个行为
  • 使用TUnit的流畅断言语法
    await Assert.That()
  • 仅保留验证测试用例所需的断言
  • 确保测试独立且幂等(可按任意顺序运行)
  • 避免测试间的依赖(如有需要可使用
    [DependsOn]
    属性)

Data-Driven Tests

数据驱动测试

  • Use
    [Arguments]
    attribute for inline test data (equivalent to xUnit's
    [InlineData]
    )
  • Use
    [MethodData]
    for method-based test data (equivalent to xUnit's
    [MemberData]
    )
  • Use
    [ClassData]
    for class-based test data
  • Create custom data sources by implementing
    ITestDataSource
  • Use meaningful parameter names in data-driven tests
  • Multiple
    [Arguments]
    attributes can be applied to the same test method
  • 使用
    [Arguments]
    属性定义内联测试数据(等效于xUnit的
    [InlineData]
  • 使用
    [MethodData]
    获取基于方法的测试数据(等效于xUnit的
    [MemberData]
  • 使用
    [ClassData]
    获取基于类的测试数据
  • 通过实现
    ITestDataSource
    创建自定义数据源
  • 在数据驱动测试中使用有意义的参数名称
  • 同一个测试方法可应用多个
    [Arguments]
    属性

Assertions

断言

  • Use
    await Assert.That(value).IsEqualTo(expected)
    for value equality
  • Use
    await Assert.That(value).IsSameReferenceAs(expected)
    for reference equality
  • Use
    await Assert.That(value).IsTrue()
    or
    await Assert.That(value).IsFalse()
    for boolean conditions
  • Use
    await Assert.That(collection).Contains(item)
    or
    await Assert.That(collection).DoesNotContain(item)
    for collections
  • Use
    await Assert.That(value).Matches(pattern)
    for regex pattern matching
  • Use
    await Assert.That(action).Throws<TException>()
    or
    await Assert.That(asyncAction).ThrowsAsync<TException>()
    to test exceptions
  • Chain assertions with
    .And
    operator:
    await Assert.That(value).IsNotNull().And.IsEqualTo(expected)
  • Use
    .Or
    operator for alternative conditions:
    await Assert.That(value).IsEqualTo(1).Or.IsEqualTo(2)
  • Use
    .Within(tolerance)
    for DateTime and numeric comparisons with 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>()
    测试异常
  • 使用
    .And
    运算符链式断言:
    await Assert.That(value).IsNotNull().And.IsEqualTo(expected)
  • 使用
    .Or
    运算符设置备选条件:
    await Assert.That(value).IsEqualTo(1).Or.IsEqualTo(2)
  • 使用
    .Within(tolerance)
    进行DateTime和数值的容差比较
  • 所有断言都是异步的,必须使用await

Advanced Features

高级功能

  • Use
    [Repeat(n)]
    to repeat tests multiple times
  • Use
    [Retry(n)]
    for automatic retry on failure
  • Use
    [ParallelLimit<T>]
    to control parallel execution limits
  • Use
    [Skip("reason")]
    to skip tests conditionally
  • Use
    [DependsOn(nameof(OtherTest))]
    to create test dependencies
  • Use
    [Timeout(milliseconds)]
    to set test timeouts
  • 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
    [Category("CategoryName")]
    for test categorization
  • Use
    [DisplayName("Custom Test Name")]
    for custom test names
  • Consider using
    TestContext
    for test diagnostics and information
  • Use conditional attributes like custom
    [WindowsOnly]
    for platform-specific tests
  • 按功能或组件对测试进行分组
  • 使用
    [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
    [NotInParallel]
    to disable parallel execution for specific tests
  • Use
    [ParallelLimit<T>]
    with custom limit classes to control concurrency
  • Tests within the same class run sequentially by default
  • Use
    [Repeat(n)]
    with
    [ParallelLimit<T>]
    for load testing scenarios
  • TUnit默认并行运行测试(与xUnit不同,xUnit需要显式配置)
  • 使用
    [NotInParallel]
    为特定测试禁用并行执行
  • 使用
    [ParallelLimit<T>]
    结合自定义限制类控制并发量
  • 默认情况下,同一类中的测试按顺序执行
  • 结合使用
    [Repeat(n)]
    [ParallelLimit<T>]
    进行负载测试场景

Migration from xUnit

从xUnit迁移

  • Replace
    [Fact]
    with
    [Test]
  • Replace
    [Theory]
    with
    [Test]
    and use
    [Arguments]
    for data
  • Replace
    [InlineData]
    with
    [Arguments]
  • Replace
    [MemberData]
    with
    [MethodData]
  • Replace
    Assert.Equal
    with
    await Assert.That(actual).IsEqualTo(expected)
  • Replace
    Assert.True
    with
    await Assert.That(condition).IsTrue()
  • Replace
    Assert.Throws<T>
    with
    await Assert.That(action).Throws<T>()
  • Replace constructor/IDisposable with
    [Before(Test)]
    /
    [After(Test)]
  • Replace
    IClassFixture<T>
    with
    [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.Equal
    替换为
    await Assert.That(actual).IsEqualTo(expected)
  • Assert.True
    替换为
    await 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项目。