Melhores práticas para testar métodos protegidos com phpunit [fechado
-
05-07-2019 - |
Pergunta
Eu encontrei a discussão sobre Você testa método privado informativo.
Decidi que, em algumas aulas, quero ter métodos protegidos, mas testá -los. Alguns desses métodos são estáticos e curtos. Como a maioria dos métodos públicos faz uso deles, provavelmente poderei remover os testes com segurança posteriormente. Mas, para começar com uma abordagem de TDD e evitar a depuração, eu realmente quero testá -los.
Eu pensei no seguinte:
- Objeto de método como aconselhado em uma resposta parece estar exagerado para isso.
- Comece com métodos públicos e quando a cobertura do código for fornecida por testes de nível superior, vira -os protegidos e remova os testes.
- Herdar uma aula com uma interface testável, tornando públicas métodos protegidos
Qual é a melhor prática? Mais alguma coisa?
Parece que o JUNIT muda automaticamente os métodos protegidos para serem públicos, mas eu não dei uma olhada mais profunda. PHP não permite isso via reflexão.
Solução
Se você estiver usando o PHP5 (> = 5.3.2) com o PhPunit, poderá testar seus métodos privados e protegidos usando a reflexão para defini -los como públicos antes de executar seus testes:
protected static function getMethod($name) {
$class = new ReflectionClass('MyClass');
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method;
}
public function testFoo() {
$foo = self::getMethod('foo');
$obj = new MyClass();
$foo->invokeArgs($obj, array(...));
...
}
Outras dicas
Você parece já estar ciente, mas vou apenas reafirmar de qualquer maneira; É um sinal ruim, se você precisar testar métodos protegidos. O objetivo de um teste de unidade é testar a interface de uma classe e os métodos protegidos são detalhes da implementação. Dito isto, há casos em que faz sentido. Se você usar a herança, poderá ver uma superclasse como fornecendo uma interface para a subclasse. Então, aqui, você teria que testar o método protegido (mas nunca um privado 1). A solução para isso é criar uma subclasse para fins de teste e usá -la para expor os métodos. Por exemplo.:
class Foo {
protected function stuff() {
// secret stuff, you want to test
}
}
class SubFoo extends Foo {
public function exposedStuff() {
return $this->stuff();
}
}
Observe que você sempre pode substituir a herança pela composição. Ao testar o código, geralmente é muito mais fácil lidar com o código que usa esse padrão; portanto, você pode considerar essa opção.
TEASTBURN tem a abordagem correta. Ainda mais simples é chamar o método diretamente e retornar a resposta:
class PHPUnitUtil
{
public static function callMethod($obj, $name, array $args) {
$class = new \ReflectionClass($obj);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method->invokeArgs($obj, $args);
}
}
Você pode chamar isso simplesmente em seus testes:
$returnVal = PHPUnitUtil::callMethod(
$this->object,
'_nameOfProtectedMethod',
array($arg1, $arg2)
);
Eu gostaria de propor uma ligeira variação para getMethod () definido em Resposta de Uckelman.
Esta versão muda getMethod () removendo valores codificados e simplificando um pouco o uso. Eu recomendo adicioná-lo à sua classe phpunitutil, como no exemplo abaixo ou na sua classe phpunit_framework_testCase-eliminamento (ou, suponho, globalmente ao seu arquivo phpunitutil).
Como o MyClass está sendo instanciado de qualquer maneira e o reflexoclass pode pegar uma string ou um objeto ...
class PHPUnitUtil {
/**
* Get a private or protected method for testing/documentation purposes.
* How to use for MyClass->foo():
* $cls = new MyClass();
* $foo = PHPUnitUtil::getPrivateMethod($cls, 'foo');
* $foo->invoke($cls, $...);
* @param object $obj The instantiated instance of your class
* @param string $name The name of your private/protected method
* @return ReflectionMethod The method you asked for
*/
public static function getPrivateMethod($obj, $name) {
$class = new ReflectionClass($obj);
$method = $class->getMethod($name);
$method->setAccessible(true);
return $method;
}
// ... some other functions
}
Eu também criei uma função de alias getProtectedMethod () para ser explícita do que é esperado, mas isso depende de você.
Felicidades!
Eu acho que Troelskn está perto. Eu faria isso em vez disso:
class ClassToTest
{
protected testThisMethod()
{
// Implement stuff here
}
}
Em seguida, implemente algo assim:
class TestClassToTest extends ClassToTest
{
public testThisMethod()
{
return parent::testThisMethod();
}
}
Você então executa seus testes contra o teste TestClasStoTest.
Deve ser possível gerar automaticamente essas classes de extensão analisando o código. Eu não ficaria surpreso se a phpunit já oferece esse mecanismo (embora eu não tenha verificado).
Vou jogar meu chapéu no ringue aqui:
Eu usei o __call hack com graus mistos de sucesso. A alternativa que eu inventei foi usar o padrão do visitante:
1: Gere uma classe STDClass ou personalizada (para aplicar o tipo)
2: prepare isso com o método e argumentos necessários
3: Verifique se o seu SUT possui um método AcceptVisitor que executará o método com os argumentos especificados na classe visitante
4: Injete -o na classe que deseja testar
5: SUT injeta o resultado da operação no visitante
6: Aplique suas condições de teste ao atributo de resultado do visitante
Você pode realmente usar __call () de maneira genérica para acessar métodos protegidos. Para poder testar esta aula
class Example {
protected function getMessage() {
return 'hello';
}
}
Você cria uma subclasse no ExempleTest.php:
class ExampleExposed extends Example {
public function __call($method, array $args = array()) {
if (!method_exists($this, $method))
throw new BadMethodCallException("method '$method' does not exist");
return call_user_func_array(array($this, $method), $args);
}
}
Observe que o método __call () não faz referência à classe de forma alguma, para que você possa copiar o acima para cada classe com métodos protegidos que deseja testar e apenas alterar a declaração de classe. Você pode colocar essa função em uma classe base comum, mas eu não tentei.
Agora, o próprio caso de teste difere apenas em onde você constrói o objeto a ser testado, trocando em exemplo exposto, por exemplo.
class ExampleTest extends PHPUnit_Framework_TestCase {
function testGetMessage() {
$fixture = new ExampleExposed();
self::assertEquals('hello', $fixture->getMessage());
}
}
Acredito que o PHP 5.3 permite que você use a reflexão para alterar diretamente a acessibilidade dos métodos, mas presumo que você precise fazê -lo para cada método individualmente.
Eu sugiro seguir a solução alternativa para a solução alternativa/ideia de "Henrik Paul" :)
Você conhece nomes de métodos privados da sua classe. Por exemplo, eles são como _add (), _edit (), _delete () etc.
Portanto, quando você deseja testá-lo do aspecto do teste de unidade, basta ligar para métodos privados prefixando e/ou sufixando alguns comum palavra (por exemplo _addphpunit) para que, quando o método __call () é chamado (como o método _addphpunit () não existe) da classe proprietária, você apenas coloca o código necessário no método __call () para remover o método prefixado/sufixo (phpunit ) e depois chamar isso de método privado deduzido a partir daí. Este é outro bom uso de métodos mágicos.
Experimente.