Pular para o conteúdo

PHPUnit: como otimizar a performance dos testes

Testes são uma das partes mais importantes na concepção de um produto digital. Através deles obtemos garantia que determinada funcionalidade cumpre com os requisitos e atende ao cliente de maneira satisfatória.

Para alcançar esse objetivo devemos ter em mente que a entrega dos testes deve ser a mais rápida possível. Com na pirâmide de testes, os unitários são rápidos, baratos e fáceis de implementar.

Subindo o nível na pirâmide o grau de complexidade aumenta e por consequência sua execução também.

Esse post irá cobrir três partes: a apresentação do problema, o uso do xdebug e configurações extras no PHP.

Esse post é a resolução de um problema que estava enfrentando no meu time. Nossos testes no backend estavam demorando cerca de 32 minutos para rodar uma suíte com 600 testes e aproximadamente 2000 asserções , confesso que estava me incomodando profundamente.

Segunda-feira iniciei um processo de investigação nos testes e o primeiro passo foi detectar os testes lentos. Mas como iria fazer isso?

Identificando os testes lentos

Pesquisando na web encontrei o artigo do Elton Minetto, onde ele apresenta um pacote chamado phpunit-speedtrap. No post do Elton ele explica passo a passo como configurar o speedtrap.

O speedtrap executa juntamente com os testes e ao final da execução exibe os 10 primeiros testes mais lentos. Com um ponto de partida, continuei a investigar e juntamente com os desenvolvedores descobrimos que alguns testes estavam com um gargalo muito grande.

Por enquanto esses testes ainda não foram refatorados, mas está no nosso radar em corrigí-los para melhorar a performance dos testes.

Logo após isso, questionei os desenvolvedores sobre outros pontos que poderiam estar afetando a execução dos testes, eles me informaram que poderia ser a extensão de debug do PHP, chamada Xdebug que vem habilitada com o framework de testes que utilizamos, o PHPUnit.

Xdebug

Meu próximo passo foi pesquisar referências na web sobre a possível lentidão dos testes relacionado ao Xdebug. Para minha sorte encontrei diversas informações a respeito que mostravam como desabilitar ou até mesmo criar filtros para melhorar a performance dos testes.

Tentei desabilitar a extensão do Xdebug no arquivo php.ini, localmente porém não tive sucesso. Eu sabia que poderia realizar esse tipo de teste, mas iria precisar de um devops para configurar essa opção no servidor.

Mais uma vez o Elton Minetto salvando a pátria. Dessa vez ele aborda em um artigo publicado em 2016, a relação da lentidão dos testes com o Xdebug, a título de comparação ele conta no artigo que possuía uma base de código que sem o Xdebug habilita terminava em 1.08 , ao habilitar o Xdebug transformou para 22.26 minutos.

Ou seja, deve um aumento significativo. Infelizmente, a opção que era apresentada no artigo não consegui realizar pois precisaria de instalar um novo pacote no servidor. 😭

Conhecendo o xdebug-filter

Seguindo o lema de ser brasileiro e não desistir nunca, persisti em buscar outras alternativas para resolver meu problema. Encontrei um artigo no próprio site do Xdebug, explicando sobre a relação da cobertura de código com o Xdebug.

Ele é frequentemente usado em combinação com PHP_CodeCoverage como parte padrão do PHPUnit. O PHPUnit atribuí uma coleção de cobertura de código para o Xdebug que por sua vez, inicia a cobertura do código por meio do método xdebug_start_code_coverage() e interrompe através do xdebug_stop_code_coverage().

Para cada teste ele utiliza o xdebug_get_code_coverage() para recuperar os resultados.

Sua saída principal é detalha quais linhas nos quais os arquivos foram “atingidos” durante a execução do código.

Usando o Xdebug para tais atividades podemos ter um impacto adicional no desempenho, pois ele irá certificar de algumas informações como:

  • analisar quais linhas de código possuem código executável;
  • quais linhas de código podem ser atingidas;
  • também podem instrumentar para descobrir quais ramificações;
  • caminhos em funções e métodos foram seguidos.

Filtros para o resgate

Desde a versão 2.6 do Xdebug é possível criar filtros para a cobertura de código. Com um filtro, podemos incluir através de uma whitelist caminhos e prefixos que podem ser executados e também é possível negar através de uma blacklist.

Um exemplo, seria informar ao PHPUnit para coletar informações somente da sua pasta src onde fica sua base de código e os outros arquivos ele iria desconsiderar, assim, dependências do Composer, arquivos de configuração seria descartados na cobertura do código.

Existem algumas formas de criar esse filtro, eu criei o filtro baseado nesse artigo. Com um filtro configurado corretamente podemos esperar um ganho de velocidade duas vezes maior.

Esses são alguns relatos de pessoas que usaram os filtros:

This is the effect on the unit test suite of @opencfp with/out xdebug filter. 44.39 vs. 15.34 seconds. I’d call that “much faster”. Great job @derickr!
(Integration tests were omitted) pic.twitter.com/LeNdaxBOOV

— Holger W🌞ltersdorf (@hollodotme) January 17, 2018

Without the filter 6 seconds
With the filter about 4.9 seconds

Anyway specifically you want me to beta test? 🙂

— Cees-Jan Kiewiet (@WyriHaximus) January 17, 2018

Antes de aplicar a técnica de filtros do Xdebug os testes estavam tinham uma execução de aproximadamente 32 minutos. 😱

Habilitando o filtro

Para criarmos o filtro basta utilizar dois comandos que irão reduzir drasticamente o tempo de execução dos testes.

O primeiro comando cria o arquivo xdebug-filter.php dentro do diretório build ele será gerado no diretório raiz da aplicação. Na minha pesquisa não verifiquei se podemos colocar ele em outro diretório.

# dump filter file
# Caso não tenha configurado globalmente o PHPUnit rode assim.
php vendor/bin/phpunit --dump-xdebug-filter build/xdebug-filter.php

# Configurado globalmente
phpunit --dump-xdebug-filter build/xdebug-filter.php

Após executar o comando do xdebug-filter sua saída é exatamente essa:

<?php
  declare(strict_types=1);

  if (!\function_exists('xdebug_set_filter')) {
    return;
  }

  \xdebug_set_filter(
    \XDEBUG_FILTER_CODE_COVERAGE,
    \XDEBUG_PATH_WHITELIST,
    ['seu-path-aqui']
  )

Executando os testes

Após a configuração iremos rodar nossa suíte de testes com o seguinte comando:

# Configuração local do PHPUnit
php vendor/bin/phpunit --prepend build/xdebug-filter.php --coverage-html build/coverage-report

# Configurado globalmente
phpunit --prepend build/xdebug-filter.php --coverage-html build/coverage-report

Com o arquivo do xdebug-filter iremos referenciá-lo nas configurações. Após aplicar as modificações do xdebug-filter, os testes executaram em 8 minutos.

Tive um ganho aproximadamente de 80% de execução! O processo agora está mais rápido e todo mundo feliz.

Dicas para o phpunit.xml

O arquivo phpunit.xml é o setup de configuração para suíte de testes que utilizam PHPUnit.

Vou mostrar alguns paramêtros que podem ser passados que irão melhorar a performance. Ele vem com uma série de paramêtros pré-configurados.

O primeiro paramêtro é o cacheResult="true", que permite o PHPUnit execute somente os testes que falharam anteriormente, com uma suíte grande testes isso é um ganho de tempo de resposta absurdo.

Podemos também usar os paramêtros stopOnFailure="true" que irá executar a suíte de testes até no momento que ela encontra alguma falha, bloqueando os restantes testes. O stopOnError="true" executa a suíte até encontrar algum erro bloqueando assim, a execução dos outros testes restantes.

O meu arquivo do phpunit.xml ficou da seguinte forma:

<?xml version="1.0" encoding="UTF-8">
  <phpunit
        backupGlobals="false"
        backupStaticAttributes="false"
        bootstrap="vendor/autoload.php"
        cacheResult="true"
        colors="true"
        convertErrorsToExceptions="true"
        convertNoticesToExceptions="true"
        convertWarningsToExceptions="true"
        processIsolation="false"
        stopOnFailure="false"
        stopOnError="false">
        <testsuites></testsuites>
        <testsuite name="Unit">
            <directory suffix="Test.php">./tests/Unit</directory>
        </testsuite>

        <testsuite name="Feature">
            <directory suffix="Test.php">./tests/Feature</directory>
        </testsuite>
    </testsuites>
    <filter>
      <whitelist addUncoveredFilesFromWhitelist="false" processUncoveredFilesFromWhitelist="true">
        <directory suffix=".php">./app</directory>
        <exclude>
          <file>./app/Modules/User/routes.php</file>
        </exclude>
      </whitelist>
    </filter>
</phpunit>

Conclusão

Ficou claro para mim que a curiosidade a gana para resolver esse problema foi o fator primordial, com isso tive vários aprendizados. Sempre seja curioso e tenta ao máximo melhorar as condições de trabalho do time.

Garantir a qualidade está também nos pequenos detalhes que podem refletir em grandes conquistas. Todas as referências de artigos que foram pesquisados estão logo abaixo.

Para se aprofundar

Gostou do post?

Eu me esforcei muito para manter este site e gosto muito de interagir com meus leitores.

Meu combustível preferido é o café, então se você gostou deste post, ou achou-o útil, considere me apoiar. ❤️

Você pode me apoiar pelo ou .

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.