单元测试、集成测试和端到端测试
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)测试:顶层——极少,耗时高,但提供最高水平的信任。
遵循此结构,我们可以确保:
- 快速反馈循环(单元测试)。
- 在集成层面发现错误(集成测试)。
- 最终功能验证(端到端测试)。
测试类型与层级
| 场景 | 测试类型 |
|---|---|
| 业务规则 | Unit |
| 计算 | Unit |
| 仓储 | Integration |
| EF 映射 | Integration |
| API 接线 | Integration |
| 用户 Happy Path | E2E |
| 冒烟测试 | E2E |
经验法则
≈ 70 % Unit tests
何时使用何种测试?
- Unit tests – 用于业务规则和计算的逻辑。
- Integration tests – 用于与外部依赖的交互(例如仓储、EF 映射、API)。
- E2E tests – 用于完整的用户场景(happy path)和快速检查(冒烟)。
结论(高级视角)
- Unit tests 保护 设计。
- Integration tests 保护 基础设施。
- E2E tests 保护系统的 可信度。
没有任何层级可以替代其他层级。
✉️ 联系方式: nikosstit@gmail.com