单元测试、集成测试和端到端测试

发布: (2026年2月5日 GMT+8 00:25)
7 分钟阅读
原文: Dev.to

Source: Dev.to

Source:

那么,何时、如何、在哪里以及最重要的 为什么 进行软件自动化测试

自动化测试(automated testing)并不是奢侈品,也不是「nice‑to‑have」功能。它是

  • 反馈机制,
  • 设计工具,
  • 用于保障系统可持续性的安全网。

在实际工作中,测试通常分为 三个基本层次

层次描述
单元测试 (Unit Tests)测试最小的逻辑单元(方法、类、纯函数)。
集成测试 (Integration Tests)检查多个组件的协作、正确的依赖注入、映射、持久化以及与真实基础设施的通信。
端到端 (E2E) / 系统测试 (End‑to‑End (E2E) / System Tests)从头到尾验证系统,就像最终用户或客户端看到的一样。

注意 – 这些层次并不是相互竞争的,而是 互补 的。最常见的错误有:

  • 只投资于某一个层次。
  • 混淆它们的角色。

下面我们逐一说明,并提供 C# (.NET) 示例。

1. 单元测试

检查内容

  • 单个方法、类或纯函数。
  • 不依赖于:
    • 数据库,
    • 文件系统,
    • 网络,
    • 时钟,
    • 随机数生成器。

如果某些东西不是确定性的,需要 mock

为什么重要

  • 快速(毫秒级)。
  • 持续运行(IDE、CI)。
  • 记录行为。
  • 强制良好设计(单一职责原则、低耦合)。

一个难以进行 单元测试 的系统几乎总是设计不佳的。

领域示例

public class Order
{
    public decimal TotalAmount { get; }

    public Order(decimal totalAmount)
    {
        TotalAmount = totalAmount;
    }

    public decimal CalculateDiscount()
    {
        if (TotalAmount >= 1000)
            return TotalAmount * 0.10m;

        if (TotalAmount >= 500)
            return TotalAmount * 0.05m;

        return 0;
    }
}

单元测试 (xUnit)

public class OrderTests
{
    [Fact]
    public void Orders_over_1000_get_10_percent_discount()
    {
        var order = new Order(1200m);
        var discount = order.CalculateDiscount();
        Assert.Equal(120m, discount);
    }

    [Fact]
    public void Orders_between_500_and_999_get_5_percent_discount()
    {
        var order = new Order(600m);
        var discount = order.CalculateDiscount();
        Assert.Equal(30m, discount);
    }

    [Fact]
    public void Orders_below_500_get_no_discount()
    {
        var order = new Order(300m);
        var discount = order.CalculateDiscount();
        Assert.Equal(0m, discount);
    }
}

特征

  • 没有任何依赖。
  • 采用清晰的 Arrange‑Act‑Assert 结构。
  • 结果确定。

2. 集成测试

检查内容

  • 多个组件的协作。
  • 正确的依赖注入、映射、持久化。
  • 真实 基础设施的交互(例如 SQLite、Testcontainers、EF Core、仓储)。

这里 不对所有东西进行 mock

为什么重要

  • 捕获单元测试无法发现的错误。
  • 发现:
    • 错误的映射,
    • 迁移问题,
    • 序列化问题,
    • 服务配置错误。

Works on my machine” 的 bug 在这里被根除。

示例:仓储 + EF Core

public class OrderEntity
{
    public int Id { get; set; }
    public decimal TotalAmount { get; set; }
}

public class AppDbContext : DbContext
{
    public DbSet<OrderEntity> Orders => Set<OrderEntity>();

    public AppDbContext(DbContextOptions options)
        : base(options) { }
}

public class OrderRepository
{
    private readonly AppDbContext _context;

    public OrderRepository(AppDbContext context)
    {
        _context = context;
    }

    public async Task AddAsync(OrderEntity order)
    {
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
    }

    public async Task<OrderEntity?> GetByIdAsync(int id)
    {
        return await _context.Orders.Fi```

> **Source:** ...

```csharp
ndAsync(id);
    }
}

使用内存 SQLite 的集成测试

public class OrderRepositoryTests
{
    [Fact]
    public async Task Can_save_and_retrieve_order()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite("Filename=:memory:")
            .Options;

        using var context = new AppDbContext(options);
        context.Database.OpenConnection();
        context.Database.EnsureCreated();

        var repository = new OrderRepository(context);
        var order = new OrderEntity { TotalAmount = 500m };

        await repository.AddAsync(order);
        var savedOrder = await repository.GetByIdAsync(order.Id);

        Assert.NotNull(savedOrder);
        Assert.Equal(500m, savedOrder!.TotalAmount);
    }
}

3. 端到端 (E2E) / 系统测试

检查内容

  • 系统 从端到端,如最终用户或 client 所见。
  • 示例流程:
HTTP request → controller → business logic → database → response
  • 无 mock – 全部真实。

为什么重要(以及危险)

✅ 优点❌ 缺点
确认系统实际工作。运行缓慢。
集成正确。易碎。
提供高水平的信任。调试困难。

一个失败的 E2E 测试会告诉你 有什么东西坏了,而不是 具体是什么

示例:ASP.NET Core API

控制器

[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly AppDbContext _context;

    public OrdersController(AppDbContext context)
    {
        _context = context;
    }

    [HttpPost]
    public async Task<IActionResult> Create(CreateOrderRequest request)
    {
        var order = new OrderEntity
        {
            TotalAmount = request.TotalAmount
        };

        _context.Orders.Add(order);
        await _context.SaveChangesAsync();

        return Ok(order.Id);
    }
}

使用 WebApplicationFactory 的 E2E 测试

public class OrdersApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public OrdersApiTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Can_create_order_via_api()
    {
        var payload = new { TotalAmount = 750 };
        var response = await _client.PostAsJsonAsync("/api/orders", payload);

        response.EnsureSuccessStatusCode();

        var orderId = await response.Content.ReadFromJsonAsync<int>();
        Assert.True(orderId > 0);
    }
}

测试金字塔 (Testing Pyramid)

理想分配的测试遵循经典的金字塔模型:


        │   E2E / System Tests   (5‑10% 的总体数量)

        │   Integration Tests    (10‑20%)

        │   Unit Tests           (70‑85%)
        └───────────────────────────────────────
  • 单元测试:基础——数量多,快速,可靠。
  • 集成测试:中间层——数量较少,但检查协作。
  • 端到端(E2E)测试:顶层——极少,耗时高,但提供最高水平的信任。

遵循此结构,我们可以确保:

  1. 快速反馈循环(单元测试)。
  2. 在集成层面发现错误(集成测试)。
  3. 最终功能验证(端到端测试)。

测试类型与层级

场景测试类型
业务规则Unit
计算Unit
仓储Integration
EF 映射Integration
API 接线Integration
用户 Happy PathE2E
冒烟测试E2E

经验法则

≈ 70 % Unit tests

何时使用何种测试?

  • Unit tests – 用于业务规则和计算的逻辑。
  • Integration tests – 用于与外部依赖的交互(例如仓储、EF 映射、API)。
  • E2E tests – 用于完整的用户场景(happy path)和快速检查(冒烟)。

结论(高级视角)

  • Unit tests 保护 设计
  • Integration tests 保护 基础设施
  • E2E tests 保护系统的 可信度

没有任何层级可以替代其他层级。

✉️ 联系方式: nikosstit@gmail.com

Back to Blog

相关文章

阅读更多 »