测试策略 testing-strategies

这个技能涵盖多种软件测试策略,用于确保代码质量和系统可靠性。它包括合同测试以验证服务间API兼容性、快照测试用于UI输出一致性、基于属性的测试检查算法行为等。关键词:测试策略、合同测试、快照测试、软件测试、质量保证、测试组织、变异测试、基于属性测试。

测试 0 次安装 0 次浏览 更新于 3/8/2026

name: testing-strategies description: 测试策略,包括合同测试、快照测试、变异测试、基于属性的测试和测试组织

测试策略

测试结构(安排-行动-断言)

describe("OrderService", () => {
  describe("createOrder", () => {
    it("用有效项目创建订单并返回订单ID", async () => {
      const repo = new InMemoryOrderRepository();
      const service = new OrderService(repo);
      const input = { customerId: "c1", items: [{ productId: "p1", quantity: 2 }] };

      const result = await service.createOrder(input);

      expect(result.id).toBeDefined();
      expect(result.status).toBe("pending");
      expect(result.items).toHaveLength(1);
      const saved = await repo.findById(result.id);
      expect(saved).toEqual(result);
    });

    it("拒绝空项目的订单", async () => {
      const service = new OrderService(new InMemoryOrderRepository());

      await expect(
        service.createOrder({ customerId: "c1", items: [] })
      ).rejects.toThrow("订单必须至少有一个项目");
    });
  });
});

按行为命名测试,而不是方法名。每个测试应该是独立和自包含的。

合同测试(Pact)

import { PactV4 } from "@pact-foundation/pact";

const provider = new PactV4({
  consumer: "OrderService",
  provider: "UserService",
});

describe("UserService合同", () => {
  it("按ID返回用户", async () => {
    await provider
      .addInteraction()
      .given("ID为user-1的用户存在")
      .uponReceiving("对用户user-1的请求")
      .withRequest("GET", "/api/users/user-1")
      .willRespondWith(200, (builder) => {
        builder.jsonBody({
          id: "user-1",
          name: "Alice",
          email: "alice@example.com",
        });
      })
      .executeTest(async (mockServer) => {
        const client = new UserClient(mockServer.url);
        const user = await client.getUser("user-1");
        expect(user.name).toBe("Alice");
      });
  });
});

合同测试验证消费者期望与提供者能力匹配,无需两个服务同时运行。

快照测试

import { render } from "@testing-library/react";

it("渲染用户资料卡", () => {
  const { container } = render(
    <UserCard user={{ name: "Alice", email: "alice@example.com", role: "admin" }} />
  );

  expect(container).toMatchSnapshot();
});

it("使用内联快照渲染订单摘要", () => {
  const summary = formatOrderSummary(mockOrder);

  expect(summary).toMatchInlineSnapshot(`
    "订单 #123
    项目数: 3
    总计: $45.99
    状态: 待处理"
  `);
});

对于小输出使用内联快照。在代码审查期间仔细审查快照差异。

基于属性的测试

import fc from "fast-check";

describe("sortUsers", () => {
  it("总是返回相同数量的元素", () => {
    fc.assert(
      fc.property(
        fc.array(fc.record({ name: fc.string(), age: fc.nat(120) })),
        (users) => {
          const sorted = sortUsers(users, "name");
          return sorted.length === users.length;
        }
      )
    );
  });

  it("为任何输入产生排序结果", () => {
    fc.assert(
      fc.property(
        fc.array(fc.record({ name: fc.string(), age: fc.nat(120) })),
        (users) => {
          const sorted = sortUsers(users, "age");
          for (let i = 1; i < sorted.length; i++) {
            if (sorted[i].age < sorted[i - 1].age) return false;
          }
          return true;
        }
      )
    );
  });
});

使用测试容器的集成测试

import { PostgreSqlContainer } from "@testcontainers/postgresql";

let container: any;
let db: Database;

beforeAll(async () => {
  container = await new PostgreSqlContainer("postgres:16").start();
  db = await createDatabase(container.getConnectionUri());
  await db.migrate();
}, 60000);

afterAll(async () => {
  await db.close();
  await container.stop();
});

it("创建并检索用户", async () => {
  const user = await db.user.create({ name: "Alice", email: "alice@test.com" });
  const found = await db.user.findById(user.id);
  expect(found).toEqual(user);
});

测试替身

function createMockEmailService(): EmailService {
  const sent: Array<{ to: string; subject: string }> = [];
  return {
    send: async (to, subject, body) => { sent.push({ to, subject }); },
    getSent: () => sent,
  };
}

const emailService = createMockEmailService();
const service = new NotificationService(emailService);
await service.notifyUser("user-1", "欢迎");
expect(emailService.getSent()).toHaveLength(1);
expect(emailService.getSent()[0].subject).toBe("欢迎");

反模式

  • 测试实现细节而不是行为
  • 在测试之间共享可变状态(没有 beforeEach 重置)
  • 编写依赖于执行顺序的测试
  • 模拟所有内容而不是在集成测试中使用真实依赖
  • 忽略不稳定测试而不是修复根本原因
  • 测试琐碎的 getter/setter 而错过边缘情况

检查清单

  • [ ] 测试按行为组织,而不是按方法或文件
  • [ ] 每个测试遵循安排-行动-断言结构
  • [ ] 合同测试验证服务间 API 兼容性
  • [ ] 快照测试在代码审查期间审查(不盲目更新)
  • [ ] 基于属性的测试覆盖算法代码的 invariants
  • [ ] 集成测试使用测试容器用于真实依赖
  • [ ] 测试替身是最小且行为聚焦的
  • [ ] CI 在不稳定测试检测时失败