Opa! / 41 posts / 0 categories / 4 comments / feed / Feed dos Comentários
Rails Summit Latin America

Rubygems na eApps

Para hospedar este blog eu contratei a eApps, sabia de alguns blogs que estavam hospedados lá e que eram boas recomendações para mim. Esses dias, tentei instalar o merb no servidor usando a instalação normal, gem install merb, mas o processo acaba assim:


[root@joeh ~]# gem install merb
Bulk updating Gem source index for: http://gems.tron.name/gems.rubyforge.org/
Terminated

Achei que poderia ser algum bloqueio por parte deles para a instalação de gems, por isso, como consta ali em cima, a url é um mirror, coloquei na tentativa de tentar burlar esse bloqueio, mas não funcionou.

Até que fui atrás para descobrir o problema, e acabei chegando na resposta, como não é necessário para mim ainda ter um servidor mega potente para rodar o blog, contratei o plano mais básico da eApps, mas parece que os 160MB de RAM não são suficientes para o Rubygems :(

É, meu primeiro projeto usando Ruby + Merb já começou dando trabalho :D

Update: Consegui resolver o problema instalando o Rubygems 1.2.0, hoje instalei o rails normalmente.

A annotation @covers do PHPUnit

O Code Coverage do PHPUnit tem um annotation chamada @covers que ajuda bastante na hora de analisarmos se os nossos testes estão cobrindo bem o nosso código.


/**
 * @covers BankAccount::getBalance
 */
public function testBalanceIsInitiallyZero() {
    $this->assertEquals(0, $this->balance->getBalance());
}

Com ela podemos especificar quais métodos aquele teste cobre, gerando um resultado mais fiél do que está sendo testado. Por exemplo, dentro do método getBalance pode ter uma cacetada de método sendo chamado, e sem a annotation, o PHPUnit vai considerar que aquele teste está testando todos esses métodos, e vai colocar isso no relatório.

Eu possuia testes informando 100% de cobertura, quando passei a usar essa annotation os números mudaram, e pude perceber que vários trechos de código realmente não estavam sendo testados.

PDO: bindParam ou bindValue?

Programando hoje uma validação para o framework que estou desenvolvendo me deparei com um problema usando variáveis em SQL, mais especificamente usando variáveis com o PDO.

Escrevendo os testes unitários para essas validações notei que precisava mudar a forma como passava os parâmetros para a SQL, ao invés de passar no execute, precisaria passar usando uma função própria para isso.

Lendo o manual, achei três opções de bind, bindColumn, bindParam e bindValue, depois de ler a documentação, optei pela bindParam, pois bindColumn é usado para atribuir o resultado para uma variável diretamente e bindColumn... eu só fui ver depois :P

Meus testes já estavam prontos e passando corretamente, mas quando fiz a mudança começou a não passar mais, então comecei a debugar para ver o que estava acontecendo. Depois de horas perdidas descobri o problema, não posso usar bindParam, tenho que usar bindValue.

O método bindParam recebe o valor do parâmetro por referência, então se o valor da variável mudar entre o chamada de bindParam e execute, o valor do parâmetro muda, alterando o resultado da SQL, coloquei bindValue e tudo voltou ao normal, com os testes passando novamente. Mas o que me deixa intrigado é que o meu código não mudava nada na variável, não tinha nada entre os métodos que pudesse fazer com o o resultado fosse diferente.

Mas fica a lição, ler a documentação melhor da próxima vez ;)

Problemas com Late Static Binding

Já vi algumas pessoas reportando um problema com Late Static Binding que, do meu ponto de vista, é facilmente resolvido da seguinte maneira:

Usando o exemplo que eu dei num post anterior, bastaria definirmos o método findAll como final:


class ActiveRecord {

    public static final function findAll() {
        $className = get_called_class();
        echo "Buscando os dados do modelo {$className}";
    }
}

Assim ele não pode ser sobreescrito, então teriamos apenas que criar um método com outro nome.


class User extends ActiveRecord {

    public static function findAllOrdered() {
        self::findAll();
    }
}

User::findAllOrdered();

output
--
Buscando os dados do modelo User

A única ressalva é que deve ser usado self:: para chamar o método da classe ActiveRecord, senão o nome da classe será resolvido como ActiveRecord ao invés de User

PHP e os métodos mágicos

O PHP tem uma funcionalidade muito útil que são os Magic Methods, eles nos permitem fazer overload de atributos e métodos, tornando nossos objetos ainda mais dinâmicos.

Porém tenho uma pergunta que ainda não tem resposta: Por que ao invés de criar os "métodos mágicos" não foi criado uma interface para isso?

Hoje é possével, por exemplo, fazer com que um objeto se comporte como um array, para isto basta implementar uma interface, chamada ArrayAccess, essa interface provê quatro métodos:

  • offsetExists: retorna um boolean dizendo se a chave acessada existe no objeto
  • offsetGet: retorna o valor referente a chave passada
  • offsetSet: define o valor para uma chave dentro do objeto
  • offsetUnset: serve para apagar o valor de uma chave dentro do objeto

Esses métodos não são chamados diretamente, eles são acessados através de caracteristicas da linguagem. Vamos analisar o trecho de código abaixo:


$objeto = new ArrayObject(); // classe interna do PHP
                             // que implementa ArrayAccess
if(!isset($objeto["chave"])) {
    $objeto["chave"] = "valor da chave";
    echo $objeto["chave"];
}
else {
    unset($objeto["chave"]);
}

O exemplo fez uso de todos os métodos da interface ArrayAccess, no if foi chamado offsetExists, dentro dele, na atribuição, foi chamado offsetSet, logo abaixo, na impressão da variável, offsetGet, no else foi chamado offsetUnset, quando a variável foi apagada.

Na declaração da variável nós poderiamos usar um $objeto = array(); normalmente que tudo funcionaria do mesmo jeito, porém não teriamos a caracteristica OOP, nem poderiamos fazer outras operações ao verificar uma variável, setar uma variável, pegar o valor de uma variável e apagar uma variável.

OK, mas o que isso tem a ver com os métodos mágicos do PHP? Tudo! Os métodos prefixados com __ não precisariam existir, poderiam estar encapsulados em uma interface, assim como ArrayAccess, tornando tudo mais OOP, que umas das promessas do PHP5. Infelizmente acredito que isso não vá mudar, e teremos que viver com isso pra sempre :(

Os métodos mágicos não são a pior coisa do mundo, mas poderiam ser implementados de uma forma mais interessante, dando até mais credibilidade para a linguagem e sua promessa de linguagem orientada a objetos.

PHP 5.3: Late Static Binding

Um recurso muito interessante do PHP 5.3 é o Late Static Binding, que serve para "aplicar herança" a métodos estáticos, usando a keyword static.

Atualmente métodos estáticos são resolvidos em tempo de compilação, a keyword self se restringe a classe que o método pertence, já static referencia a classe chamada inicialmente, em tempo de execução.

Isto é muito útil para implementar o pattern ActiveRecord, desta forma:

class ActiveRecord {
    public static function findAll() {
        $className = get_called_class();
        echo "Buscando os dados do modelo {$className}";
    }
}

class User extends ActiveRecord {

}

User::findAll();

-- output
Buscando os dados do modelo User

Atualmente isso só funciona se o nome da classe for passado por parâmetro, mais ou menos dessa forma.

class ActiveRecord {

    public static function findAll($className) {
        echo "Buscando os dados do modelo {$className}";
    }
}

class User extends ActiveRecord {

}

User::findAll("User");

-- output
Buscando os dados do modelo User

Que seria mais ou menos uma "simulação" dos Generics do Java, porém bem mais feio.

Mock Objects com PHPUnit

Estou usando PHPUnit ultimamente como ferramenta de testes para o projeto que desenvolvo aqui na empresa onde trabalho. A parte que estou testando atualmente é a camada de banco de dados, usando PDO e geração de objetos como resultado, basicamente um ActiveRecord, com relacionamentos entre objetos e tudo mais.

Para testar as funcionalidades desta camada, pensei, sem nem pestanejar, em Mock Objects. Crio os mocks, conforme a necessidade, baseados em uma classe base que provê as funcões básicas para os modelos, e uso eles dentro de cada teste. Porém, como nem tudo são flores no mundo da programação, o framework de Mock Objects do PHPUnit tem um sério problema de compatibilidade com a forma de desenvolvimento que utilizo nos meus projetos, pois ele recria os métodos estáticos da classe base usando $this no código, então quando rodo os testes, booom, erro na hora.

Depois de muito tentar encontrar uma forma de resolver esse problema - pretendia usar mocks de qualquer jeito - acabei desistindo e partindo para uma solução tabajara, definir as classes vazias dentro do mesmo arquivo de testes, - eu sei, eu sei, a idéia é péssima.

Rodando os testes um a um, sem uma suite de testes, tudo corria normalmente, e eu estava feliz da vida. No mesmo dia, resolvi agrupar meus testes, criando uma suite, para que rodassem juntos, e assim, tornando tudo mais confiável. Foi mandar rodar e, boom, as classes definidas nos arquivos de testes possuiam o mesmo nome, como no PHP os requires servem para todo o script, foram gerados vários erros de classe redefinida, fazendo com que eu tivesse outra péssima idéia, checar se a classe existe antes de defini-la nos arquivos de testes, assim:


if(!class_exists("User", false)) {
    class User extends Base {
    }
}

Por enquanto sigo assim, não sei como isso funciona em outras linguagens, meu primeiro contato full com testes foi em PHP, se alguêm puder comentar me explicando, eu ficaria agradecido! :)