C#单元测试
in 默认分类 with 0 comment

一直想学习一下单元测试 但是感觉比较复杂 而且对于小型的项目 点击的测试 基本上够用了 所以,一直就没有学 现在开始学一下

为什么现在该学单元测试?

以前现在
写完代码 → 手动运行控制台 → 看输出是否对写完代码 → 按下Ctrl+Shift+R → 10个测试自动跑完,红/绿灯一目了然
改了一行代码,担心影响其他功能 → 心里打鼓改了一行代码,测试全过 → 安心提交,甚至敢重构!
没有文档,别人看不懂你写的 HelperClass测试就是活文档:ShouldReturnTrueWhenInputIsPositive()比注释更清晰
项目越做越大,不敢动老代码有了测试覆盖,老代码变成“安全区”,敢改、敢优化

常用测试框架

常用的C#单元测试有 XUnit MSTest NUnit

测试框架优点适合你吗
xUnit.net轻量、现代、性能好、约定优于配置、社区推荐度高强烈推荐新手入门
NUnit功能丰富、支持 [TestCase]、老牌稳定适合需要复杂参数化测试的场景
MSTestVS内置、微软官方、无需安装包不推荐新项目,语法老旧,灵活性差

单元测试的方法与规范

单元测试的黄金法则:AAA 模式(Arrange-Act-Assert)

这是所有现代单元测试的底层结构。不管你用 xUnit、NUnit 还是 MSTest,都必须遵循!

阶段说明示例
Arrange(准备)创建被测对象、设置输入数据、模拟依赖var calc = new Calculator();
Act(执行)调用你要测试的方法var result = calc.Add(2, 3);
Assert(断言)验证结果是否符合预期Assert.Equal(5, result);

规范建议:

测试命名规范

语义化 > 简洁
不要叫 Test1()TestMethod()
推荐命名:MethodName_State_ExpectedBehavior

场景好的命名
正常情况Add_ShouldReturnSumOfTwoPositiveNumbers
边界值Divide_WhenDivisorIsZero_ThrowsArgumentException
异常路径CalculateDiscount_WhenUserIsUnder18_ReturnsZero
多参数组合CalculateTax_WhenIncomeIs50kAndStateIsCA_Returns7500

✅ 使用下划线 _ 分隔,清晰易读
✅ 用动词短语描述行为(Should / Returns / Throws)
✅ 所有单词首字母大写(PascalCase),保持风格统一

📌 xUnit 官方推荐命名风格:https://xunit.net/docs/style-guidelines

测试粒度:一个测试只测一件事

❌ 错误示范:一个测试干了三件事

[Fact]
public void ShouldWorkCorrectly()
{
    var calc = new Calculator();

    Assert.Equal(5, calc.Add(2, 3));     // 测试加法
    Assert.Equal(6, calc.Multiply(2, 3)); // 测试乘法
    Assert.True(calc.IsPositive(5));     // 测试正数判断
}

问题:

避免测试中的“魔法数字”和硬编码**

❌ 不好的写法:

Assert.Equal(12345, service.CalculateTotal());

✅ 更好的写法:

const int expectedTotal = 12345;
var result = service.CalculateTotal();
Assert.Equal(expectedTotal, result);

测试数据:善用 [Theory] + [InlineData] 做参数化测试

当你需要测试多个输入输出组合时,别写 10 个重复的 [Fact]

✅ 示例:测试加法多种情况

[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
[InlineData(999, 1, 1000)]
public void Add_ShouldReturnCorrectSum(int a, int b, int expected)
{
    var calc = new Calculator();
    var result = calc.Add(a, b);
    Assert.Equal(expected, result);
}

✅ 优势:

✅ 六、异常测试:一定要断言异常类型 + 消息(如果重要)

[Fact]
public void Divide_WhenDivisorIsZero_ThrowsArgumentExceptionWithMessage()
{
    var calc = new Calculator();

    var ex = Assert.Throws<ArgumentException>(() => 
        calc.Divide(10, 0));

    // ✅ 不仅要抛异常,还要确保错误信息正确
    Assert.Equal("除数不能为零", ex.Message);
}

测试不应依赖外部环境(数据库、文件、网络)

单元测试 = 单元测试。意思是:隔离、快速、可重复。

❌ 错误示范:

[Fact]
public void GetUserFromDatabase_ReturnsValidUser()
{
    var repo = new UserRepository(); // 实际连了 SQL Server
    var user = repo.GetUserById(1); // 依赖真实数据库
    Assert.NotNull(user);
}

👉 问题:

✅ 正确做法:使用 Mock(模拟)

[Fact]
public void GetUserById_WhenIdExists_ReturnsUser()
{
    // Arrange
    var mockRepo = new Mock<IUserRepository>();
    mockRepo.Setup(r => r.GetUserById(1))
            .Returns(new User { Id = 1, Name = "Alice" });

    var service = new UserService(mockRepo.Object);

    // Act
    var user = service.GetUserById(1);

    // Assert
    Assert.NotNull(user);
    Assert.Equal("Alice", user.Name);
}

✅ 使用 Moq 库(NuGet 包:Moq)
✅ 你的测试只关心“接口契约”,不关心实现细节
✅ 测试速度从秒级 → 毫秒级

💡 一句话记住:单元测试不碰磁盘、不联网、不读配置文件、不启动服务

测试代码也要整洁:DRY & KISS

测试代码也是代码!它也需要:

原则说明
DRY(Don’t Repeat Yourself)相同的 Arrange 逻辑抽到 SetUp或构造函数(xUnit 用构造函数)
KISS(Keep It Simple, Stupid)别在测试里写复杂逻辑、循环、条件分支

测试即文档(Documentation by Example)

你的测试,就是最真实的“使用说明书”。

[Fact]
public void CalculateDiscount_WhenUserIsPremiumAndHasMoreThan10Orders_Returns20Percent()
{
    var user = new User { IsPremium = true, OrderCount = 15 };
    var discount = service.CalculateDiscount(user);
    Assert.Equal(0.20m, discount);
}

总结:单元测试十项黄金规范(可打印贴墙上)

这篇文章有通义千问的一份功劳