0Pricing
PHP Academy · Aula

Testes de integração e funcionais

Teste como os componentes trabalham juntos na prática.

Testes de integração e funcionais é uma aula grátis de PHP Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de PHP Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de PHP Academy inclui 4 aulas no total.

Além dos testes unitários

Os testes unitários comprovam que uma classe funciona isoladamente, com tudo substituído por simulações. Mas as simulações podem enganar — o banco de dados real, o roteador do framework ou a camada HTTP comportam-se de maneira diferente. Os testes de integração verificam dois ou mais componentes reais conectados entre si; os testes funcionais exercitam a aplicação de ponta a ponta por meio do seu ponto de entrada público (uma solicitação HTTP ou um comando de console). Nesta lição, você aprenderá o papel de ambos e como escrevê-los.

A pirâmide de testes

Uma suíte saudável tem o formato de uma pirâmide:

  • Muitos testes unitários — rápidos, isolados e executados constantemente.
  • Menos testes de integração — usam banco de dados/fila/cache reais e são mais lentos.
  • Poucos testes funcionais/de ponta a ponta — usam a pilha completa, são os mais lentos e mais frágeis.

Inverter essa proporção (criando um “cone de sorvete” composto principalmente por testes de ponta a ponta) resulta em uma suíte lenta e instável. Priorize a cobertura nos níveis inferiores sempre que possível.

Integração: repositório contra um banco de dados real

Um teste de integração de um repositório usa uma conexão real com o banco de dados — não uma simulação — para detectar erros de SQL, esquema e mapeamento. Use um banco de dados exclusivo para testes (ou um SQLite em memória cujo dialeto seja suficientemente próximo do seu) e um PDO real.

<?php
use PHPUnit\Framework\TestCase;

final class UserRepositoryTest extends TestCase
{
    private \PDO $pdo;

    protected function setUp(): void
    {
        $this->pdo = new \PDO('sqlite::memory:');
        $this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        $this->pdo->exec('CREATE TABLE users (id INTEGER PRIMARY KEY, email TEXT)');
    }

    public function test_persists_and_finds_a_user(): void
    {
        $repo = new UserRepository($this->pdo);
        $id = $repo->create('ada@example.com');
        self::assertSame('ada@example.com', $repo->find($id)->email);
    }
}

Isolamento: reversão de transação

Os testes de integração não devem deixar estado residual uns nos outros. O padrão mais rápido é envolver cada teste em uma transação e revertê-la em tearDown() — o banco de dados retorna a um estado limpo sem precisar recriar os dados. (O truncamento é a alternativa quando o código sob teste confirma a transação ou usa DDL.)

<?php
protected function setUp(): void
{
    $this->pdo = TestDb::connection();
    $this->pdo->beginTransaction();
}

protected function tearDown(): void
{
    $this->pdo->rollBack();   // undo everything this test did
}

Dados de teste e fábricas

Os testes precisam de dados. Evite grandes despejos de SQL difíceis de manter; prefira pequenas fábricas que criem apenas as linhas necessárias para um teste, com valores padrão sensatos que você possa substituir em cada caso. Assim, a intenção de cada teste fica explícita e resistente ao crescimento do esquema.

<?php
final class UserFactory
{
    public static function create(\PDO $pdo, array $overrides = []): int
    {
        $data = array_merge(['email' => 'user'.uniqid().'@test.dev'], $overrides);
        $stmt = $pdo->prepare('INSERT INTO users (email) VALUES (:email)');
        $stmt->execute($data);
        return (int) $pdo->lastInsertId();
    }
}

Funcional: acessando a aplicação como um cliente

Um teste funcional conduz o núcleo/roteador real da aplicação com uma solicitação simulada e verifica a resposta — código de status, cabeçalhos e corpo. O WebTestCase do Symfony e os auxiliares de testes HTTP do Laravel fazem exatamente isso sem iniciar um servidor web real. A seguir está o formato do Symfony.

<?php
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

final class HealthControllerTest extends WebTestCase
{
    public function test_health_endpoint_returns_ok(): void
    {
        $client = static::createClient();
        $client->request('GET', '/health');

        self::assertResponseIsSuccessful();        // 2xx
        self::assertJson($client->getResponse()->getContent());
    }
}

Verificando a resposta

Os testes funcionais verificam o comportamento observável no limite: status, formato JSON e efeitos colaterais no banco de dados. A seguir, um POST cria um recurso; verificamos tanto a resposta HTTP quanto se o registro realmente foi armazenado — comprovando que toda a pilha cooperou.

<?php
public function test_creating_a_user_persists_it(): void
{
    $client = static::createClient();
    $client->request('POST', '/users', server: [
        'CONTENT_TYPE' => 'application/json',
    ], content: json_encode(['email' => 'ada@example.com']));

    self::assertResponseStatusCodeSame(201);

    $repo = static::getContainer()->get(UserRepository::class);
    self::assertNotNull($repo->findByEmail('ada@example.com'));
}

Simulando seletivamente as extremidades

Mesmo os testes funcionais não devem chamar provedores de pagamento reais nem enviar e-mails reais. Substitua apenas os serviços externos mais externos — troque-os no contêiner de testes por implementações simuladas/em memória — mantendo real o seu código, o roteamento e o banco de dados. Assim, você preserva a fidelidade de ponta a ponta sem chamadas de rede instáveis.

<?php
public function test_checkout_charges_via_gateway(): void
{
    $client = static::createClient();
    // Replace the real gateway binding with an in-memory fake:
    static::getContainer()->set(PaymentGateway::class, new FakePaymentGateway());

    $client->request('POST', '/checkout', content: json_encode(['cart' => 1]));
    self::assertResponseIsSuccessful();
}

Suítes separadas, velocidades separadas

Divida o seu phpunit.xml em conjuntos de testes para que a suíte rápida de testes unitários seja executada a cada salvamento, enquanto as suítes lentas de integração/funcionais sejam executadas sob demanda ou na integração contínua. Agrupe por diretório e selecione com --testsuite.

<phpunit>
  <testsuites>
    <testsuite name="unit">
      <directory>tests/Unit</directory>
    </testsuite>
    <testsuite name="integration">
      <directory>tests/Integration</directory>
    </testsuite>
    <testsuite name="functional">
      <directory>tests/Functional</directory>
    </testsuite>
  </testsuites>
</phpunit>

O determinismo é mais importante que a cobertura

A maneira mais rápida de perder a confiança em uma suíte é permitir instabilidade. Torne determinísticos os testes de integração/funcionais:

  • Congele o tempo (injete um relógio) em vez de chamar time().
  • Defina uma semente para a aleatoriedade; evite verificações dependentes da ordem em consultas não ordenadas.
  • Redefina o estado do banco de dados a cada teste (reversão de transação/truncamento).
  • Nunca dependa da ordem de execução dos testes.

Faça o banco de dados de testes corresponder ao de produção

O SQLite em memória é rápido, mas diferenças de dialeto (sensibilidade a maiúsculas e minúsculas, funções JSON, verificação de chaves estrangeiras e tipos) podem esconder erros que só aparecem no seu mecanismo real. Para código que usa SQL específico do mecanismo, execute os testes de integração no mesmo mecanismo usado em produção — normalmente em um contêiner Docker descartável na integração contínua — em vez de usar um substituto conveniente.

<?php
// Read connection from environment so CI points at a real Postgres/MySQL container
$dsn = getenv('TEST_DATABASE_URL') ?: 'sqlite::memory:';
$pdo = new \PDO($dsn);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
// In CI, TEST_DATABASE_URL targets the same engine as production.

Verificação rápida

Como um teste funcional deve tratar um provedor de pagamentos de terceiros?

Recapitulação

Você aprendeu a testar componentes em conjunto:

  • Os testes de integração usam bancos de dados/serviços reais para detectar o que as simulações escondem; isole-os com a reversão de transações.
  • Os testes funcionais conduzem o núcleo real por meio de solicitações simuladas e verificam as respostas e os efeitos colaterais.
  • As fábricas criam dados mínimos; simule apenas os limites externos mais externos.
  • Divida as suítes por velocidade e mantenha os testes mais lentos determinísticos.

A seguir: medir a qualidade dos testes com testes de mutação.

Perguntas Frequentes

A aula “Testes de integração e funcionais” é grátis?

Sim — o texto completo de “Testes de integração e funcionais” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de PHP Academy, atualize para CoddyKit PRO. O curso de PHP Academy inclui 4 aulas no total.

O que vou aprender em “Testes de integração e funcionais”?

Teste como os componentes trabalham juntos na prática. Você pratica PHP Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar PHP Academy?

Nenhuma experiência prévia é necessária. PHP Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.

Quanto tempo leva a aula “Testes de integração e funcionais”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de PHP Academy?

Sim. Cada aula de PHP Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. O fluxo de trabalho do desenvolvimento orientado por testes
  2. Simulação e criação de dublês com Mockery
  3. Testes de integração e funcionais
  4. Testes de mutação com Infection
← Voltar para PHP Academy