DesenvolvedorWordPress WhatsApp

Testes automatizados no WordPress com PHPUnit

Testar plugins e temas não é luxo de projeto grande: é o que permite refatorar e atualizar sem medo. Veja como montar a suíte de testes do WordPress com WP-CLI e PHPUnit, escrever testes que carregam o WordPress de verdade e rodar tudo automaticamente no CI.

Precisa de um plugin com testes de verdade?

Atualizado em 19/06/2026

Quem desenvolve para WordPress conhece a sensação: você ajusta um filtro, atualiza um plugin ou sobe uma nova versão do PHP e algo silenciosamente quebra três telas adiante. Testes automatizados existem para transformar esse medo em uma rede de segurança. Em vez de clicar manualmente por toda a interface depois de cada mudança, você escreve uma vez o comportamento esperado e deixa o PHPUnit verificar — em segundos, a cada commit. Este guia cobre a suíte de testes oficial do WordPress, do scaffold à integração contínua, com as armadilhas que costumam custar uma tarde inteira a quem está começando.

Por que testar (e o que se ganha)

O retorno dos testes aparece em três momentos concretos do dia a dia:

Isso é especialmente valioso ao manter um plugin próprio que precisa sobreviver a anos de atualizações de WordPress, PHP e WooCommerce.

A suíte de testes do WordPress (wp-phpunit)

O WordPress não testa o seu código com um PHPUnit "pelado": ele fornece uma biblioteca de testes (a WordPress PHPUnit test library, distribuída também como o pacote Composer wp-phpunit/wp-phpunit) que sobe uma instalação real do WordPress em memória antes de cada execução. É essa biblioteca que define a classe-base WP_UnitTestCase, as fixtures, a factory de dados e o rollback do banco entre testes.

Na prática, isso significa que dentro dos seus testes você tem acesso a get_option(), WP_Query, do_action() e qualquer função do core — exatamente como em uma requisição normal. Por isso a suíte é, em essência, um arcabouço de testes de integração: o objeto sob teste e o WordPress estão juntos, não isolados.

Pré-requisitos. Você vai precisar de PHP na linha de comando, do WP-CLI, de um servidor MySQL/MariaDB acessível e (recomendado) do Composer e do utilitário subversion ou git, que o script de instalação usa para baixar a biblioteca de testes do core.

Gerando o scaffold com WP-CLI

O caminho mais rápido para começar é deixar o WP-CLI montar o esqueleto. Dentro de uma instalação WordPress, com o seu plugin já presente em wp-content/plugins/meu-plugin, rode:

<?php /* (terminal) */ ?>
# Gera os arquivos de teste para o plugin "meu-plugin"
wp scaffold plugin-tests meu-plugin

# O comando cria, dentro da pasta do plugin:
#   bin/install-wp-tests.sh   -> instala WordPress + a lib de testes
#   phpunit.xml.dist          -> configuração do PHPUnit
#   tests/bootstrap.php        -> carrega a lib e o seu plugin
#   tests/test-sample.php      -> um teste de exemplo
#   .phpcs.xml.dist            -> padrão de code style (opcional)

O wp scaffold plugin-tests não inventa nada: ele apenas escreve os arquivos no formato que o core e a comunidade adotam, poupando você de copiá-los de um projeto antigo. Cada um deles tem um papel específico, resumido na tabela mais adiante.

Instalando o ambiente de testes

Com o scaffold no lugar, o próximo passo é instalar o WordPress de teste e a biblioteca. O install-wp-tests.sh recebe, nesta ordem: nome do banco de testes, usuário, senha, host e versão do WordPress.

<?php /* (terminal) */ ?>
# Sintaxe:
#   bin/install-wp-tests.sh <db_name> <db_user> <db_pass> [db_host] [wp_version]

# Exemplo: banco DEDICADO chamado "wordpress_test"
bash bin/install-wp-tests.sh wordpress_test root "senha" 127.0.0.1 latest

# Instala as dependências do Composer (PHPUnit, etc.) e roda a suíte
composer install
vendor/bin/phpunit

Armadilha que destrói dados. O banco informado em <db_name> é derrubado e recriado (DROP + CREATE) toda vez que o script roda. Nunca, em hipótese alguma, aponte para o banco de produção ou para um banco de desenvolvimento que contenha algo que você não pode perder. Use um nome inconfundível como wordpress_test e, de preferência, um MySQL local ou em container, isolado do resto.

O script grava os arquivos da biblioteca de testes em um diretório temporário (por padrão sob /tmp/wordpress-tests-lib) e o WordPress de teste em /tmp/wordpress. O tests/bootstrap.php aponta para esse local através da variável de ambiente WP_TESTS_DIR; se você instalar a lib em outro lugar, exporte essa variável antes de rodar o PHPUnit.

Escrevendo o primeiro teste com WP_UnitTestCase

A classe WP_UnitTestCase estende a TestCase do PHPUnit e adiciona tudo que é específico do WordPress: as fixtures, o acesso à factory e o rollback automático do banco entre os testes — feito via transações de banco, o que torna a suíte rápida mesmo criando muitos registros.

<?php
class Test_Minha_Funcao extends WP_UnitTestCase {

    public function set_up() {
        parent::set_up();
        // Preparação comum a vários testes pode vir aqui.
    }

    public function test_post_recem_criado_fica_publicado() {
        // A factory cria um post REAL no banco de testes e devolve o ID.
        $post_id = $this->factory()->post->create( array(
            'post_title'  => 'Olá, testes',
            'post_status' => 'publish',
        ) );

        $post = get_post( $post_id );

        $this->assertInstanceOf( 'WP_Post', $post );
        $this->assertEquals( 'publish', $post->post_status );
        $this->assertEquals( 'Olá, testes', $post->post_title );
    }

    public function test_usuario_editor_existe() {
        $user_id = $this->factory()->user->create( array( 'role' => 'editor' ) );
        $user    = get_userdata( $user_id );

        $this->assertTrue( in_array( 'editor', $user->roles, true ) );
    }

    public function tear_down() {
        parent::tear_down();
    }
}

Note set_up()/tear_down() (com underscore): as versões recentes da suíte do WordPress usam esses nomes para conviver com diferentes versões do PHPUnit, em vez do setUp()/tearDown() clássico — sempre chame o parent:: correspondente para não quebrar o ciclo de vida das fixtures.

Testando hooks e filtros

Como quase toda customização de WordPress passa por actions e filters, saber testá-los é essencial. O padrão é: registrar o filtro (ou garantir que o seu plugin o registrou), disparar o ponto que o aplica e verificar o resultado.

<?php
class Test_Filtro_Titulo extends WP_UnitTestCase {

    public function test_filtro_adiciona_sufixo_ao_titulo() {
        // Registramos um filtro no próprio teste.
        $callback = function ( $title ) {
            return $title . ' — Loja';
        };
        add_filter( 'the_title', $callback );

        $resultado = apply_filters( 'the_title', 'Produto X' );

        $this->assertEquals( 'Produto X — Loja', $resultado );

        // Boa prática: remover o que você adicionou.
        remove_filter( 'the_title', $callback );
    }

    public function test_meu_plugin_filtra_excerpt_length() {
        // Aqui assumimos que o plugin já fez add_filter( 'excerpt_length', ... )
        $tamanho = apply_filters( 'excerpt_length', 55 );

        // Esperamos que o plugin tenha reduzido para 20.
        $this->assertSame( 20, $tamanho );
    }
}

Para erros, a suíte do WordPress oferece a asserção assertWPError(), que verifica se um valor é uma instância de WP_Error — útil ao testar funções do core que retornam erro em vez de lançar exceção, como wp_insert_post() em condições inválidas.

Conceito / ferramentaPara que serve
WP_UnitTestCaseClasse-base que estende o PHPUnit com fixtures do WordPress e rollback do banco entre testes.
$this->factory()Cria dados de teste reais (posts, usuários, termos, comentários) e devolve seus IDs.
tests/bootstrap.phpCarrega a biblioteca de testes e o seu plugin antes de qualquer teste rodar.
bin/install-wp-tests.shBaixa o WordPress de teste e a lib, e cria o banco de testes (que é recriado a cada execução).
phpunit.xml.distConfigura o PHPUnit: onde estão os testes, o bootstrap e quais diretórios cobrir.
assertWPError()Asserção específica para validar retornos do tipo WP_Error.

Configuração: phpunit.xml.dist

O arquivo gerado pelo scaffold já vem pronto, mas vale entender o essencial. Ele indica o bootstrap, registra a suíte e (opcionalmente) define cobertura:

<?php /* phpunit.xml.dist */ ?>
<phpunit
    bootstrap="tests/bootstrap.php"
    backupGlobals="false"
    colors="true"
    convertErrorsToExceptions="true"
    convertWarningsToExceptions="true">
    <testsuites>
        <testsuite name="meu-plugin">
            <directory suffix="test.php">./tests/</directory>
        </testsuite>
    </testsuites>
</phpunit>

O atributo backupGlobals="false" é deliberado: fazer backup de todas as globais a cada teste seria lento e, no contexto do WordPress, contraproducente. Em compensação, a responsabilidade de não vazar estado global passa a ser sua (veja as armadilhas).

Integração contínua com GitHub Actions

O ganho real vem quando a suíte roda sozinha a cada push e pull request. O workflow abaixo sobe um MySQL, instala o ambiente de testes e dispara o PHPUnit — e usa uma matrix para cobrir mais de uma versão de PHP:

<?php /* .github/workflows/tests.yml */ ?>
name: Testes PHPUnit
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        php: ['7.4', '8.1', '8.3']
    services:
      mysql:
        image: mariadb:10.6
        env:
          MYSQL_ROOT_PASSWORD: root
        ports:
          - 3306:3306
        options: >-
          --health-cmd="healthcheck.sh --connect"
          --health-interval=10s --health-timeout=5s --health-retries=5
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          tools: composer
      - name: Instalar ambiente de testes
        run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 latest
      - name: Composer install
        run: composer install --prefer-dist --no-progress
      - name: Rodar PHPUnit
        run: vendor/bin/phpunit

Teste de unidade × teste de integração

Vale fixar a distinção, porque ela orienta o que você consegue (e o que não consegue) testar com a suíte padrão:

A recomendação prática: extraia regras de negócio puras para classes que possam ser testadas em unidade, e cubra com testes de integração tudo que toca o core. Você ganha velocidade onde dá e fidelidade onde importa.

Armadilhas comuns

SintomaCausa provávelCorreção
Dados de dev sumiramBanco de testes apontava para banco realUse sempre um banco dedicado (wordpress_test)
Could not find wordpress-tests-libLib de testes não instalada / WP_TESTS_DIR erradaRode o install-wp-tests.sh e/ou exporte WP_TESTS_DIR
Erro de versão do PHPUnitIncompatibilidade PHP × PHPUnit × lib do WPFixe versões no composer.json conforme a matriz oficial
Testes passam isolados, falham juntosEstado global vazando entre testesLimpe hooks/opções no set_up()/tear_down()
Teste depende da ordem de execuçãoAcoplamento a estado de outro testeCada teste deve montar seus próprios dados com a factory

Boas práticas

Quer levar a casa? Combine os testes com comandos WP-CLI próprios para automatizar tarefas e mantenha tudo organizado dentro do seu fluxo de desenvolvimento. Testes não eliminam bugs, mas mudam o momento em que você os descobre — do cliente irritado para o pull request ainda aberto.

Quer um plugin com cobertura de testes e CI configurado?

Desenvolvo plugins e customizações WordPress com testes automatizados em PHPUnit e pipeline de CI, para que cada atualização seja segura. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

Perguntas frequentes

Preciso de um banco de dados separado para os testes?

Sim, e isso é inegociável. O script bin/install-wp-tests.sh instala uma cópia do WordPress e cria um banco de testes que é apagado e recriado a cada execução. Se você apontar para o banco de produção (ou de desenvolvimento com dados que importam), os dados serão destruídos. Use sempre um banco dedicado, com um nome óbvio como wordpress_test, idealmente em um MySQL local ou em container, nunca no servidor de produção.

Os testes do WordPress são de unidade ou de integração?

Na prática, a maioria é de integração. A WP_UnitTestCase carrega o WordPress inteiro, com banco, hooks e funções reais. Você testa o comportamento do seu código dentro do WordPress, não em isolamento. Testes de unidade puros (sem WordPress) são possíveis com mocks, mas exigem desacoplar a lógica das funções globais — algo que vale a pena para regras de negócio, mas raramente para código que conversa direto com o core.

Qual versão de PHPUnit devo usar?

Depende da versão de PHP e do WordPress. O ecossistema histórico do core trava o PHPUnit em versões antigas (5.x–7.x via o pacote phpunit/phpunit) por causa de compatibilidade. Em projetos modernos, prefira o pacote oficial wp-phpunit/wp-phpunit via Composer e siga a matriz de compatibilidade documentada. Conflitos entre versão de PHP, PHPUnit e a biblioteca de testes são a fonte nº 1 de erros de ambiente — fixe as versões no composer.json.

O que a factory (<code>$this-&gt;factory()</code>) faz?

É um gerador de dados de teste. $this->factory()->post->create() insere um post real no banco de testes e devolve o ID; há fábricas para usuários, termos, comentários, attachments e mais. Isso evita escrever wp_insert_post() manualmente em cada teste e mantém os dados consistentes. Como o banco sofre rollback entre os testes, os registros criados não vazam de um teste para o outro.

Por que meus testes "vazam" estado entre si?

A WP_UnitTestCase faz rollback do banco via transações, mas não reseta automaticamente todo o estado global do PHP: $GLOBALS, opções em cache, hooks que você adicionou, singletons. Se um teste adiciona um add_filter e não o remove, o próximo teste pode herdá-lo. Adicione e remova no setUp()/tearDown(), ou registre o filtro dentro do próprio teste sabendo que o core restaura os hooks ao final de cada teste.

Dá para rodar os testes no GitHub Actions?

Sim, e é a prática recomendada. Você sobe um serviço MySQL no workflow, roda o install-wp-tests.sh dentro do runner e dispara o phpunit. Use uma matrix para testar várias versões de PHP e de WordPress de uma vez. Assim, toda abertura de pull request executa a suíte e você descobre regressões antes do merge, não em produção.

Referências oficiais

  1. WordPress.org — Automated Testing (Core Handbook)
  2. WordPress.org — Writing PHPUnit Tests
  3. WP-CLI — wp scaffold plugin-tests
  4. PHPUnit — site oficial
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui