功能测试模拟用户操作验证端到端请求响应,集成测试聚焦服务间协作验证逻辑链路;前者基于WebTestCase发起HTTP请求,后者直接调用容器服务并断言数据流与状态变更。

Symfony功能测试和集成测试常被混用,但二者目标不同:功能测试模拟真实用户操作(如点击、提交表单、跳转),验证整个请求生命周期是否符合预期;集成测试则聚焦多个服务或组件间的协作是否正确,不强调HTTP层,更关注逻辑链路是否打通。
功能测试:从请求到响应的端到端验证
功能测试基于 WebTestCase,使用客户端发起真实HTTP请求,检查响应状态、重定向、模板变量、DOM内容等。它默认启动轻量内核,加载完整服务容器,但不启动Web服务器。
- 创建测试类继承
WebTestCase,调用static::createClient()获取测试客户端 - 用
$client->request('GET', '/path')发起请求,再通过$client->getResponse()检查状态码、内容类型、重定向头等 - 用
$crawler = $client->followRedirect()或$crawler->selectLink('Submit')->click()模拟用户交互 - 用
$crawler->filter('h1')->text()或$this->assertSelectorTextContains('body', 'Welcome')验证页面内容 - 若需登录,可手动注入安全令牌到会话(如通过
PostAuthenticationGuardToken),或使用$client->loginUser($user)(Symfony 5.4+)
集成测试:验证服务协同与数据流
集成测试不依赖HTTP协议,直接调用服务方法,重点是多个类/服务组合后行为是否符合设计。它比单元测试粒度粗,比功能测试更轻量,适合验证业务逻辑链路(如“下单→扣库存→发消息”)。
- 同样继承
WebTestCase,但通常不调用request(),而是从容器中获取服务:$orderService = static::$container->get(OrderService::class) - 传入真实或构造的DTO、实体、Repository模拟对象,执行核心方法
- 断言返回值、数据库状态变更(如用
$em->getRepository(Order::class)->findOneBy(['id' => $id]))、事件是否触发、消息是否推送到队列 - 可选择性启用数据库事务回滚(
static::bootKernel(['environment' => 'test']);+$em->beginTransaction()+$em->rollback())保证测试隔离
关键配置与常见陷阱
两类测试都依赖正确的测试环境配置。确保 config/packages/test/*.yaml 中禁用非必要服务(如邮件发送器替换为 test 类型),并启用 framework.test: ~ 和 session.storage_id: session.storage.mock_file。
- 功能测试中,
client默认不保留会话,需显式调用$client->enableProfiler()或设置 Cookie 才能维持登录态 - 集成测试若涉及 Doctrine,注意实体管理器可能未刷新,必要时调用
$em->clear()或$em->refresh($entity) - 避免在测试中硬编码路径或URL;使用路由生成器:
$url = $router->generate('app_order_show', ['id' => $order->getId()]) - 不推荐在功能测试中直接操作数据库——应通过服务调用触发状态变更,保持测试层次清晰
如何选择:功能 or 集成?
判断依据是测试意图。想确认“用户访问 /checkout 页面能否看到订单摘要并成功提交?”——选功能测试。想确认“调用 InventoryService::reserve() 是否准确减少库存并抛出异常当不足?”——选集成测试。两者互补,不替代单元测试,而是填补单个方法验证与真实场景之间的鸿沟。


















