Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Estratégias para refatoração de código PHP que não pode ser testado

Teste da unidade e refatoramento do código PHP herdado para facilitar o teste e melhorar a qualidade do código

John Mertic, Software Engineer, SugarCRM
author photo
John Mertic é um engenheiro de software na SugarCRM, e tem vários anos de experiência com aplicativos PHP da Web. Na SugarCRM, ele especializou-se em arquitetura de interface com o usuário, móvel e integração de dados. Um ávido escritor, ele teve artigos publicados na php|architect, IBM developerWorks e na Apple Developer Connector, e é autor do livro "The Definitive Guide to SugarCRM: Better Business Applications". Ele também já fez contribuições a vários projetos de software livre, mais notavelmente ao projeto PHP onde ele é o criador e mantenedor do PHP Windows Installer. Seu e-mail de contato é jmertic@gmail.com.

Resumo:  Com a evolução do PHP de uma simples linguagem de script para uma linguagem de programação completa, houve uma evolução paralela da complexidade das bases de código de um típico aplicativo PHP. Para controlar o suporte e a manutenção desses aplicativos, diversas ferramentas de teste ajudam a automatizar esse processo. Um método, teste de unidade, permite que você teste diretamente o código que escreve, permitindo a correção. No entanto, frequentemente, as bases do código herdado não são adaptáveis a esse tipo de teste. Este artigo analisa estratégias de refatoração de código PHP com problemas comuns a fim de facilitar o teste usando ferramentas de teste de unidade populares, enquanto reduz as dependências que melhoram sua base de código.

Data:  18/Jul/2011
Nível:  Intermediário
Atividade:  1013 visualizações
Comentários:  


Introdução

Analisando os 15 anos de PHP, vemos que ele cresceu de uma simples linguagem de script dinâmica, alternativa aos scripts CGI populares naquele período, para a linguagem de programação completa que é hoje. À medida que a base de código cresce, o teste manual se torna uma tarefa impossível e cada mudança feita no código, grande ou pequena, pode afetar todo o aplicativo. Os efeitos podem ser uma página que não carrega ou um formulário que não salva ou também pode ser algo difícil de detectar ou que apareça apenas sob determinadas circunstâncias. Pode até mesmo fazer com que um problema anterior reapareça no aplicativo. Diversas ferramentas de teste foram desenvolvidas para resolver esses problemas.

Um método popular é conhecido como teste funcional ou de aceitação e testa o aplicativo por meio da interação típica do usuário do aplicativo. Essa é uma boa técnica para testar os diversos processos no aplicativo, mas pode ser um processo bastante lento e geralmente não realiza um trabalho tão bom em testar as classes e funções de nível inferior para se certificar de que estejam funcionando conforme o pretendido. É ai que outro método de teste, o teste de unidade, entra em cena. A meta é testar a funcionalidade do código subjacente do aplicativo, a fim de garantir que os resultados corretos sejam fornecidos na execução. Frequentemente, esses aplicativos da Web "crescidos" recebem muito código herdado que, com o tempo, dificultam a realização de testes, o que reduz a capacidade das equipes de desenvolvimento de fornecer uma boa cobertura de teste para um aplicativo. Isso é chamado comumente de "código intestável". Vamos ver como identificar isso em seu aplicativo e como corrigir.


Identificando o código intestável

As áreas problemáticas de sua base de código que não podem ser testadas normalmente não são aparentes quando o código está sendo escrito. Quando você escreve um código para um aplicativo PHP, há uma tendência de personalizá-lo da forma que a solicitação da Web flui, usando uma abordagem mais voltada aos procedimentos no design do aplicativo. Uma urgência de terminar o projeto ou corrigir o aplicativo de forma apressada pode fazer com que os desenvolvedores "peguem atalhos" a fim de completar o código rapidamente. Antes, um código confuso ou escrito de maneira inadequada podia compor os problemas de impossibilidade de teste, pois os desenvolvedores normalmente tentavam realizar a correção menos arriscada possível, mesmo que isso significasse problemas de suporte no decorrer do tempo. Todos esses problemas são bastante intestáveis pelo teste de unidade sem correr riscos.


Funções que dependem do estado global

Variáveis globais são uma conveniência nos aplicativos PHP. Elas permitem que você tenha variáveis ou objetos que podem ser inicializados logo no início em seu aplicativo e podem ser aproveitados em qualquer local do aplicativo. No entanto, essa flexibilidade tem um custo, pois o uso pesado de variáveis globais é um problema comum visto em códigos intestáveis. Podemos ver isso na Listagem 1.


Listagem 1. Função que depende do estado global
 
<?php
function formatNumber($number)
{
    global $decimal_precision, $decimal_separator, $thousands_separator;

    if ( !isset($decimal_precision) ) $decimal_precision = 2;
    if ( !isset($decimal_separator) ) $decimal_separator = '.';
    if ( !isset($thousands_separator) ) $thousands_separator = ',';

    return number_format($number, $decimal_precision, $decimal_separator,
$thousands_separator);
}

Dois problemas diferentes ocorrem devido a essas variáveis globais. O primeiro problema é que você precisa dar conta de cada uma delas em seu teste, se certificando de que as configura com valores válidos esperados pela função. O segundo e maior problema é que se você não mudar o estado nos testes subsequentes e invalidar seus resultados, precisará redefinir o estado global de volta ao estado que estava antes da execução do teste. O PHPUnit tem recursos que podem fazer o backup de variáveis globais e restaurá-las após a execução do teste, o que pode ajudar a reduzir o problema. No entanto, é melhor fornecer uma maneira de a classe do testador passar diretamente para essas variáveis globais os valores que possam ser usados pelo método. A Listagem 2 mostra um exemplo de como fazer isso.


Listagem 2. Função corrigida para permitir a substituição das variáveis globais
 
<?php
function formatNumber($number, $decimal_precision = null, $decimal_separator = null,
$thousands_separator = null)
{
    if ( is_null($decimal_precision) ) global $decimal_precision;
    if ( is_null($decimal_separator) ) global $decimal_separator;
    if ( is_null($thousands_separator) ) global $thousands_separator;

    if ( !isset($decimal_precision) ) $decimal_precision = 2;
    if ( !isset($decimal_separator) ) $decimal_separator = '.';
    if ( !isset($thousands_separator) ) $thousands_separator = ',';

    return number_format($number, $decimal_precision, $decimal_separator,
$thousands_separator);
}

Essa ação não apenas tornou o código mais testável, mas também fez com que ele não dependa das variáveis globais no método. Isso pode abrir a possibilidade de refatoração desse código a fim de não usar as variáveis globais de jeito nenhum.


Singletons que não podem ser redefinidos

Singletons são classes projetadas para ter apenas uma instância existente por vez em um aplicativo. Eles são um padrão comum usado para objetos globais em um aplicativo, como conexões de banco de dados e definições de configurações. Eles são normalmente considerados um tabu em um aplicativo, o que muitos desenvolvedores consideram ser uma distinção injusta, devido à utilidade de sempre ter um objeto disponível para uso. Muito disso vem do uso exagerado de singletons, uma vez que muitos desses chamados objetos deuses não podem ser estendidos. Porém, de uma perspectiva de teste, um grande problema é que eles são frequentemente imutáveis. Vamos analisar a Listagem 3 como exemplo.


Listagem 3. Objeto singleton que queremos testar
 
<?php
class Singleton
{
    private static $instance;

    protected function __construct() { }
    private final function __clone() {}


    public static function getInstance()
    {
        if ( !isset(self::$instance) ) {
            self::$instance = new Singleton;
        }

        return self::$instance;
    }
}

Você pode ver que após o singleton ser instanciado pela primeira vez, toda chamada feita para o método getInstance() retorna o mesmo objeto e não um novo, o que pode se tornar um grande problema se fizermos alterações nesse objeto. A solução mais fácil é adicionar um método ao objeto que possa redefini-lo. A Listagem 4 mostra esse exemplo.


Listagem 4. Objeto singleton com um método de redefinição adicionado
 
<?php
class Singleton
{
    private static $instance;

    protected function __construct() { }
    private final function __clone() {}


    public static function getInstance()
    {
        if ( !isset(self::$instance) ) {
            self::$instance = new Singleton;
        }

        return self::$instance;
    }

    public static function reset()
    {
        self::$instance = null;
    }
}

Agora, podemos chamar o método de redefinição para iniciar cada teste a fim de garantir que estamos passando pelo código de inicialização para o objeto singleton em cada teste. Ter esse método disponível pode ser útil no aplicativo em geral, pois agora o singleton se torna facilmente mutável.


Trabalhando no construtor de classe

Uma boa prática para o teste de unidade é testar apenas o que você pretende, e evitar ter que configurar mais objetos e variáveis do que você precisa. Cada objeto e variável definidos também precisam ser removidos após o fato. Isso se torna um problema para itens mais incômodos como arquivos e tabelas de banco de dados, nos quais se você precisa modificar o estado, precisa ter extremo cuidado para limpar tudo após a conclusão do teste. A maior barreira para manter essa regra intacta é o construtor do próprio objeto, que faz todo tipo de coisa que não diz respeito ao seu teste. Considere a Listagem 5 como exemplo.


Listagem 5. Classe com um método de singleton grande
 
<?php
class MyClass
{
    protected $results;

    public function __construct()
    {
        $dbconn = new DatabaseConnection('localhost','user','password');
        $this->results = $dbconn->query('select name from mytable');
    }

    public function getFirstResult()
    {
        return $this->results[0];
    }
}

Aqui, para testar o método fdfdfd no objeto, acabamos precisando configurar uma conexão do banco de dados, ter registros na tabela e limpar todos esses recursos após o fato. Isso parece exagerado quando nada disso é necessário para testar o método fdfdfd . Portanto, vamos modificar o construtor, como mostra a Listagem 6.


Listagem 6. Classe modificada para ignorar opcionalmente toda a lógica de inicialização desnecessária
 
<?php
class MyClass
{
    protected $results;

    public function __construct($init = true)
    {
        if ( $init ) $this->init();
    }

    public function init()
    {
        $dbconn = new DatabaseConnection('localhost','user','password');
        $this->results = $dbconn->query('select name from mytable');
    }

    public function getFirstResult()
    {
        return $this->results[0];
    }
}

Refatoramos a grande quantidade de código no construtor para inserir um método init() , que ainda será chamado por padrão no construtor, a fim de evitar a quebra do código existente. No entanto, agora podem apenas passar um operador booleano falso para o construtor durante nossos testes para evitar chamar o método init() e toda a lógica de inicialização desnecessária. Essa refatoramento da classe também melhora o código para separarmos o código de inicialização do código de construção do objeto.


Ter dependências de classe codificadas

Como vimos na seção anterior, um grande problema no design da classe que dificulta o teste é ter que inicializar todos os tipos de objetos que não são necessários para seu teste. Anteriormente, vimos como uma lógica pesada de inicialização pode adicionar todos os tipos de sobrecarga ao escrever um teste (especialmente quando o teste não precisa de nada disso para ter êxito), mas outro problema pode ocorrer quando criamos diretamente novos objetos dentro dos métodos da classe que estamos testando. Vamos analisar a Listagem 7 para obter um exemplo desse código problemático.


Listagem 7. Classe com um método que inicializa diretamente outro objeto
 
<?php
class MyUserClass
{
    public function getUserList()
    {
        $dbconn = new DatabaseConnection('localhost','user','password');
        $results = $dbconn->query('select name from user');

        sort($results);

        return $results;
    }
}

vamos supor que estamos testando o método getUserList acima, mas o foco de nosso teste é assegurar que a lista de usuários retornada seja classificada em ordem alfabética. Nesse caso, o fato de podermos obter os registros do banco de dados realmente não importa, já que o que estamos testando é nossa capacidade de classificar os registros retornados. O problema é que, como nós instanciamos diretamente um objeto de conexão de banco de dados dentro do método, precisamos realizar todo esse trabalho de construção para testar adequadamente o método. Portanto, vamos fazer uma alteração para permitir a interjeição de um objeto, como mostra a Listagem 8.


Listagem 8. Classe com um método que inicializa diretamente outro objeto, mas também fornece uma maneira de substituí-lo

<?php
class MyUserClass
{
    public function getUserList($dbconn = null)
    {
        if ( !isset($dbconn) || !( $dbconn instanceOf DatabaseConnection ) ) {
            $dbconn = new DatabaseConnection('localhost','user','password');
        }
        $results = $dbconn->query('select name from user');

        sort($results);

        return $results;
    }
}

Agora, você pode passar diretamente um objeto compatível com o objeto de conexão de banco de dados esperado e ele usará esse objeto em vez de criar um novo. O objeto passado pode simplesmente ser um objeto mock, ou seja, um objeto no qual codificamos vários dos valores de retorno dos métodos chamados para fornecer diretamente os dados que desejamos usar. Nesse caso, nós substituiríamos o método de consulta do objeto de conexão do banco de dados para podermos retornar apenas os resultados em vez de recorrer ao banco de dados para obtê-los. Fazer esse tipo de refatoração também pode melhorar o método, permitindo que seu aplicativo realize a interjeição de diferentes conexões de banco de dados em vez de ficar vinculado apenas ao padrão especificado.


Benefícios do código testável

É claro que escrever um código mais testável tem o benefício óbvio de facilitar a geração de testes unitários para seu aplicativo PHP (que você viu nos exemplos apresentados neste artigo), mas isso também cria um aplicativo com um design melhor, mais modular e mais estável durante o processo. Todos nós vimos diversos níveis de códigos "espaguete" que entrelaçam de forma sólida os negócios e exibem lógica em uma grande bagunça processual em aplicativos PHP e que, sem dúvida, podem causar pesadelos de suporte para qualquer pessoa que precisar analisá-los. Durante o processo de tornar o código testável, nós refatoramos um código anteriormente problemático, não apenas em seu design, mas também em suas funções. Abrimos opções para uma melhor reutilização do código, fazendo com que as funções e classes não tivessem apenas um propósito e as tornando mais úteis por outras áreas do aplicativo, removendo dependências codificadas. Além disso, facilitamos o trabalho de suporte da base de código removendo o código de qualidade inferior e substituindo por outro com uma qualidade muito melhor.


Conclusão

Neste artigo, analisamos como tornar o código PHP mais testável por meio de vários exemplos de códigos intestáveis clássicos nos aplicativos PHP. Exploramos como as situações surgem no aplicativo e vimos como corrigir da melhor maneira o código problemático para possibilitar o teste. Também vimos como essas mudanças em seu código não apenas tornaram o código mais testável, mas também melhoraram a qualidade do código em geral e promoveram a reutilização do código nas seções de código refatoradas.



Download

DescriçãoNomeTamanhoMétodo de download
Article source codecode_examples.zip3KBHTTP

Informações sobre métodos de download


Recursos

Aprender

Obter produtos e tecnologias

Discutir

Sobre o autor

author photo

John Mertic é um engenheiro de software na SugarCRM, e tem vários anos de experiência com aplicativos PHP da Web. Na SugarCRM, ele especializou-se em arquitetura de interface com o usuário, móvel e integração de dados. Um ávido escritor, ele teve artigos publicados na php|architect, IBM developerWorks e na Apple Developer Connector, e é autor do livro "The Definitive Guide to SugarCRM: Better Business Applications". Ele também já fez contribuições a vários projetos de software livre, mais notavelmente ao projeto PHP onde ele é o criador e mantenedor do PHP Windows Installer. Seu e-mail de contato é jmertic@gmail.com.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=700024
ArticleTitle=Estratégias para refatoração de código PHP que não pode ser testado
publish-date=07182011
author1-email=jmertic@gmail.com
author1-email-cc=

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).