garotosopa

Vícios de linguagem (de programação)

com 5 comentários

Esses dias o autor do PHPUnit comentou em seu blog sobre um recurso que seria adicionado ao framework que me chamou bastante atenção no aspecto da Orientação a Objetos, especialmente quanto à reação de algumas pessoas.

Até então, os métodos para testar funcionalidades eram derivados da classe que o teste deve estender. Sendo assim, para acessá-los, utilizava-se $this->métodoParaTestarAlgo(). Um caso de teste ficava assim:

<?php
class ExampleTest extends PHPUnit_Framework_TestCase {
    public function testEmptyArray() {
        $array = array();
 
        $this->assertEquals(0, count($array));
    }
}

O novo recurso sugeria incluir um script que definia todas essas funções de teste como globais, de forma que não precisariam mais ser utilizadas no objeto $this, e sim diretamente:

<?php
require_once "PHPUnit/Framework/Assert/Functions.php";
 
class ExampleTest extends PHPUnit_Framework_TestCase {
    public function testEmptyArray() {
        $array = array();
 
        assertEquals(0, count($array));
    }
}

Uma das reações que surgiram nos comentários foi que esse recurso polui o escopo global, e existe uma regra santa que diz que isso não deve ser feito.

Na prática, o problema real é que o projeto que está sendo testado, alguma biblioteca dependente ou o próprio PHP também poderia definir funções globais com o mesmo nome, o que causaria problemas. No caso das bibliotecas não há muito o que falar, mas imagino que alguém que é contra o uso dessas funções globais no PHPUnit não teria funções globais no próprio projeto. Argumento válido, de qualquer forma.

O que mais me incomodou, na verdade, foram argumentos que davam a entender que os testes devem ser orientados a objetos, porque orientação a objetos é uma boa prática. Amém.

O problema é que esses métodos não são “orientados” ao “objeto”, no sentido literal do termo, e pra mim isso faz toda diferença – é basicamente o que distingue o certo do errado, o bem do mal, o Mestre dos Magos do Vingador, O Ryu do M. Bison, o Jacob do Monstro de Fumaça… Isso me levou a pensar que alguns autores imaginam que colocar funções numa classe e chamá-las com $this é uma boa prática por si só.

Analisando a classe PHPUnit_Framework_TestCase, ela estende a classe PHPUnit_Framework_Assert (que é onde os métodos de teste são definidos), apenas para permitir o uso destes através do $this. Aí que mora a raiz do problema.

Na verdade, TestCase não é um Assert, e a regra santa que proibe funções globais também diz que estende-se uma classe quando se tem um subtipo dela, e não quando se quer apenas aproveitar o funcionamento.

Para reafirmar este argumento, todo os métodos da classe Assert são estáticos. Isto é, o objeto $this não é utilizado em nenhum momento.

Olhando pelo aspecto da orientação a objetos, a classe TestCase não deveria estender Assert. Só que isso implicaria em chamar a função estaticamente com PHPUnit_Framework_Assert::assertEquals() ao invés de simplesmente $this->assertEquals(). Ter os métodos no objeto do caso de teste foi apenas uma praticidade, e não uma decisão de design.

Isso é resultado da forma como o PHP funciona, especialmente porque não havia outra forma de agrupar funções se não por classes. Em Perl, por exemplo, seria bastante válido ter o pacote PHPUnit::Framework::Assert exportando todas as funções que poderiam então ser chamadas diretamente pelo nome. Mas mesmo com o recurso recente de namespaces, não seria a mesma coisa em PHP, já que funções não são importadas. Se bem que não parece tão ruim fazer use PHPUnit\Framework\Assert e chamar com Assert\equals(). Tá, é ruim, mas faz mais sentido que estender Assert.

Se alguém achar acoplado demais ter as chamadas estáticas, existem outras saídas quanto a isso. Até porque, escrever seu teste estendendo TestCase que estende Assert é igualmente acoplado. Você poderia, claro, estender outra classe que sobrescrevesse a lógica dos testes, mas seria necessário alterar a herança de todos os casos de teste, e ainda assim continuaria não tendo sentido em relação a TestCase não ser um tipo de Assert.

Pensando em baixo acoplamento, eu criaria uma interface Assert que deveria ter sua implementação injetada no caso de teste, de forma que as chamadas ficassem $this->assert->equals(), sem a necessidade de estender apenas para aproveitar o funcionamento. E já que estamos em uma linguagem dinâmica, seria possível inclusive que o objeto que implementa Assert disponibilizasse outros métodos de teste.

Além disso, existe um contador de testes estático na classe Assert, então fica até mais flexível que passe a ser um objeto instanciado reaproveitado em todos os casos de teste.

Outro argumento que li nos comentários foi sugerindo o uso de fluent interfaces; assim, as chamadas ficariam $this->assertEquals()->assertEquals(), economizando o $this do segundo teste em diante. Pra mim, é outro exemplo de vício da linguagem. As chamadas encadeadas, nesse caso, seriam utilizadas para esconder um problema que não deveria existir: as chamadas serem orientadas ao objeto – quando na verdade não são. Sem contar que hoje a classe é totalmente estática, então envolver $this no retorno para suprir a interface fluente parece fora de cogitação.

De qualquer forma, todos esses meus comentários são apenas uma reflexão superficial de como somos influenciados por características da linguagem. A estrutura do PHPUnit é excelente, com ou sem funções globais, que são apenas detalhes da implementação.

Escrito por garotosopa

agosto 30, 2010 às 8:41 am

Publicado em OOP, PHP

5 Respostas

Assinar os comentários com RSS.

  1. Eu implementaria __call em TestCase “delegando” os metodos “assert(.+)” para uma instancia de Assert.
    Ah mas eles iriam querer manter a compatibilidade com o PHP3 né? É então não dá…

    :)

    Rafael Souza

    agosto 30, 2010 em 8:24 pm

  2. Muito boa a sua observação do purismo do OO.
    O importante é ter um código cliente desceste e de fácil leitura.

    Raphael Almeida

    outubro 3, 2010 em 11:28 am

  3. Cara, que fonte é essa ai no seu editor ?

    emersonsoares

    setembro 6, 2011 em 11:28 am

  4. Monaco.

    garotosopa

    setembro 6, 2011 em 11:34 am

  5. Show de bola, escreve muito bem, acabei de conhecer o blog e curti..

    Anônimo

    outubro 27, 2011 em 5:39 pm


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Sair / Alterar )

Imagem do Twitter

You are commenting using your Twitter account. Sair / Alterar )

Foto do Facebook

You are commenting using your Facebook account. Sair / Alterar )

Connecting to %s

Seguir

Obtenha todo post novo entregue na sua caixa de entrada.