C#中级:单元测试与Mock框架(NUnit与Moq)

一、单元测试核心概念
  1. 目的

    • 验证代码单元(如方法、类)的独立功能
    • 确保修改不破坏现有逻辑(回归测试)
    • 提升代码可维护性与设计质量
  2. FIRST原则

    • Fast(快速):测试应在毫秒级完成
    • Isolated(隔离):不依赖外部资源(数据库/网络)
    • Repeatable(可重复):任何环境结果一致
    • Self-validating(自验证):自动判断通过/失败
    • Timely(及时):测试与开发同步进行
二、NUnit框架基础

.NET主流测试框架,通过NuGet安装:

Install-Package NUnit3TestAdapter
Install-Package NUnit

核心特性

[TestFixture]  // 测试类标记
public class CalculatorTests
{
    private Calculator _calc;

    [SetUp]      // 每个测试前执行
    public void Setup() => _calc = new Calculator();

    [TearDown]   // 每个测试后执行
    public void Cleanup() => _calc = null;

    [Test]       // 测试方法
    public void Add_TwoNumbers_ReturnsSum()
    {
        // Arrange
        int a = 5, b = 3;
        
        // Act
        int result = _calc.Add(a, b);
        
        // Assert
        Assert.That(result, Is.EqualTo(8));  // 结果断言
    }

    [TestCase(1, 2, 3)]  // 参数化测试
    [TestCase(-1, 1, 0)]
    public void Add_MultipleCases_Valid(int x, int y, int expected)
    {
        Assert.That(_calc.Add(x, y), Is.EqualTo(expected));
    }
}

三、Moq框架详解

模拟框架(通过NuGet安装):

Install-Package Moq

核心功能

public interface IOrderService
{
    bool PlaceOrder(string item, int quantity);
}

// 测试中使用Moq
[Test]
public void ProcessOrder_ValidItem_CallsService()
{
    // 1. 创建Mock对象
    var mockService = new Mock<IOrderService>();
    
    // 2. 设置模拟行为
    mockService.Setup(m => 
        m.PlaceOrder(It.IsAny<string>(), It.IsInRange(1, 10, Range.Inclusive))
    ).Returns(true);  // 当参数满足条件时返回true
    
    var processor = new OrderProcessor(mockService.Object);
    
    // 3. 执行测试
    bool result = processor.ProcessOrder("Laptop", 3);
    
    // 4. 验证交互
    Assert.IsTrue(result);
    mockService.Verify(m => 
        m.PlaceOrder("Laptop", 3), Times.Once);  // 验证方法调用次数
}

四、综合应用示例

场景:测试支付服务,模拟支付网关依赖

// 依赖接口
public interface IPaymentGateway
{
    bool ProcessPayment(decimal amount);
}

// 被测类
public class PaymentService
{
    private readonly IPaymentGateway _gateway;
    public PaymentService(IPaymentGateway gateway) => _gateway = gateway;
    
    public string MakePayment(decimal amount) => 
        _gateway.ProcessPayment(amount) ? "Success" : "Failed";
}

// 单元测试
[TestFixture]
public class PaymentServiceTests
{
    [Test]
    public void MakePayment_ValidAmount_ReturnsSuccess()
    {
        // 创建Mock网关
        var mockGateway = new Mock<IPaymentGateway>();
        mockGateway.Setup(g => g.ProcessPayment(100)).Returns(true);
        
        // 注入Mock对象
        var service = new PaymentService(mockGateway.Object);
        
        // 执行并验证
        var result = service.MakePayment(100);
        Assert.That(result, Is.EqualTo("Success"));
        mockGateway.Verify(g => g.ProcessPayment(100), Times.Once);
    }
}

五、最佳实践
  1. 测试命名规范
    [被测方法]_[测试条件]_[预期结果]
    (例:MakePayment_InvalidAmount_ThrowsException

  2. Mock使用原则

    • 仅模拟外部依赖(数据库/API/文件系统)
    • 避免过度模拟,保持测试真实性
    • 优先测试正常路径,再覆盖边界情况
  3. 测试覆盖率
    使用工具(如Coverlet)监控覆盖率,建议核心模块覆盖率达$80%$以上:

    dotnet test --collect:"XPlat Code Coverage"
    

关键提示:结合NUnit[TestCase]MoqIt.Is<T>()可高效构建参数化模拟测试,大幅减少重复代码。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐