PHPUnit, testanto métodos privados com Reflection e DataProvider
Autor: Pedro Ribeiro | Publicado: 2012-12-12T18:22:00-08:00



Olá pessoal, dando a sequência a série de posts relacionados a PHPUnit e testes unitários....








Hoje irei demonstrar como realizar testes em métodos privados.

Primeiramente irei dar minha opinião sobre este tema.

Métodos privados, são utilizados apenas dentro de uma classe, e normalmente, só existem para diminiur a complexidade de outros métodos, que na maioria das vezes são públicos. Este métodos privados devem ser cobertos por testes pela sua chamada pública, que no caso são os métodos públicos.


Neste exemplo não irei focar na complexidade, e sim, em como testar um método privado.....vamos lá!!!


Dada a seguinte classe:







&lt;?php





class&nbsp;Seiya


{


&nbsp; &nbsp;&nbsp;const&nbsp;ARMADURA_DE_PEGASUS&nbsp;=&nbsp;'Pegasus';


&nbsp; &nbsp;&nbsp;const&nbsp;ARMADURA_DE_SAGITARIO&nbsp;=&nbsp;'Sagitario';








&nbsp; &nbsp;&nbsp;private&nbsp;$armaduraAtual;





&nbsp; &nbsp;&nbsp;private&nbsp;$cosmo;





&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;__construct()


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;cosmo&nbsp;=&nbsp;100;


&nbsp; &nbsp;&nbsp;}





&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;invocaArmadura()


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;armaduraAtual&nbsp;=&nbsp;$this-&gt;escolheArmadura();


&nbsp; &nbsp;&nbsp;}








&nbsp; &nbsp;&nbsp;/**


&nbsp; &nbsp; &nbsp;* @param int $dano


&nbsp; &nbsp; &nbsp;*/


&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;defender($dano)


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;cosmo&nbsp;-=&nbsp;$dano;


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;($this-&gt;cosmo&nbsp;&lt;=&nbsp;0)&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;despertarSetimoSentido();


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;}


&nbsp; &nbsp;&nbsp;}








&nbsp; &nbsp;&nbsp;private&nbsp;function&nbsp;despertarSetimoSentido()


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;cosmo&nbsp;=&nbsp;10000;


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;invocaArmadura();


&nbsp; &nbsp;&nbsp;}





&nbsp; &nbsp;&nbsp;/**


&nbsp; &nbsp; &nbsp;* @return string


&nbsp; &nbsp; &nbsp;*/


&nbsp; &nbsp;&nbsp;private&nbsp;function&nbsp;escolheArmadura()


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;($this-&gt;cosmo&nbsp;&lt;=&nbsp;100)&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;self::ARMADURA_DE_PEGASUS;


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;}&nbsp;else&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;self::ARMADURA_DE_SAGITARIO;


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;}


&nbsp; &nbsp;&nbsp;}





&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;getCosmo()


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;$this-&gt;cosmo;


&nbsp; &nbsp;&nbsp;}





&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;setCosmo($cosmo)


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;cosmo&nbsp;=&nbsp;$cosmo;


&nbsp; &nbsp;&nbsp;}


}






Como podem ver, a classe Seiya, tem diversos métodos, mas iremos focar no método escolheArmadura(),&nbsp;que é privado.


O método escolheArmadura() depende do valor de um atributo da classe, que é o cosmo, dependendo do valor, ele irá retornar uma armadura.

Vamos ao teste (PRIMEIRA VERSÃO)!!!






&lt;?php

require&nbsp;ZF2_PATH.&nbsp;'/module/Tutorial/src/Tutorial/Seiya.php';





class&nbsp;SeiyaTest&nbsp;extends&nbsp;PHPUnit_Framework_TestCase


{





&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;testEscolheArmadura()


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$expected&nbsp;=&nbsp;'Pegasus';


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$seiya&nbsp;=&nbsp;new&nbsp;Seiya();


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$seiya-&gt;setCosmo(100);





&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$objReflection&nbsp;=&nbsp;new&nbsp;ReflectionClass('Seiya');


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$method&nbsp;=&nbsp;$objReflection-&gt;getMethod('escolheArmadura');


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$method-&gt;setAccessible(true);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$result&nbsp;=&nbsp;$method-&gt;invoke($seiya);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;assertEquals($expected,&nbsp;$result);


&nbsp; &nbsp;&nbsp;}


}







Para podermos invocar um método privado temos que usar o resurso Reflection, que pode ler e alterar a visibilidade de um método.


Desta forma acabamos de testar a seguinte situação: quando o valor do cosmo é igual a 100, o método deve retornar: "Pegasus", mas não podemos nos limitar a esta situação, vamos aumentar a cobertura deste método....






public&nbsp;function&nbsp;testEscolheArmadura()


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$expected&nbsp;=&nbsp;'Pegasus';


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$seiya&nbsp;=&nbsp;new&nbsp;Seiya();


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$seiya-&gt;setCosmo(100);





&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$objReflection&nbsp;=&nbsp;new&nbsp;ReflectionClass('Seiya');


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$method&nbsp;=&nbsp;$objReflection-&gt;getMethod('escolheArmadura');


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$method-&gt;setAccessible(true);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$result&nbsp;=&nbsp;$method-&gt;invoke($seiya);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;assertEquals($expected,&nbsp;$result);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;

&nbsp; &nbsp; &nbsp; &nbsp; // nova implementação


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$expected&nbsp;=&nbsp;'Sagitario';


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$seiya&nbsp;=&nbsp;new&nbsp;Seiya();


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$seiya-&gt;setCosmo(1000);





&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$objReflection&nbsp;=&nbsp;new&nbsp;ReflectionClass('Seiya');


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$method&nbsp;=&nbsp;$objReflection-&gt;getMethod('escolheArmadura');


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$method-&gt;setAccessible(true);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$result&nbsp;=&nbsp;$method-&gt;invoke($seiya);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;assertEquals($expected,&nbsp;$result);


&nbsp; &nbsp;&nbsp;}





Agora também estamos validando se &nbsp;com o valor 1000 para o atributo cosmo, ele irá retornar "Sagitário".


Se continuarmos aumentando a cobertura desta forma, nosso teste irá ficar muito extenso e com muito código duplicado. Para isso, existem os DataProviders.
DataProviders são conjuntos de dados que serão utilizados como entrada para um teste e o próprio PHPUnit cuida de realizar as iterações de laço para cada cenário.


Agora o teste final.






&lt;?php





require&nbsp;ZF2_PATH.&nbsp;'/module/Tutorial/src/Tutorial/Seiya.php';





class&nbsp;SeiyaTest&nbsp;extends&nbsp;PHPUnit_Framework_TestCase


{





&nbsp; &nbsp;&nbsp;/**


&nbsp; &nbsp; &nbsp;* @dataProvider providerTestEscolheArmadura


&nbsp; &nbsp; &nbsp;*/


&nbsp; &nbsp;&nbsp;public&nbsp;function&nbsp;testEscolheArmadura($cosmo,&nbsp;$expected)


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$seiya&nbsp;=&nbsp;new&nbsp;Seiya();


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$seiya-&gt;setCosmo($cosmo);





&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$objReflection&nbsp;=&nbsp;new&nbsp;ReflectionClass('Seiya');


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$method&nbsp;=&nbsp;$objReflection-&gt;getMethod('escolheArmadura');


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$method-&gt;setAccessible(true);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$result&nbsp;=&nbsp;$method-&gt;invoke($seiya);


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$this-&gt;assertEquals($expected,&nbsp;$result);


&nbsp; &nbsp;&nbsp;}





&nbsp; &nbsp;&nbsp;public&nbsp;static&nbsp;function&nbsp;providerTestEscolheArmadura()


&nbsp; &nbsp;&nbsp;{


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$pegasus&nbsp;=&nbsp;'Pegasus';


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;$sagitario&nbsp;=&nbsp;'Sagitario';





&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;array(


&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;array(1,&nbsp;$pegasus),


&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;array(100,&nbsp;$pegasus),


&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;array(0,&nbsp;$pegasus),


&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;array(101,&nbsp;$sagitario),


&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;array(1000,&nbsp;$sagitario),


&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;);


&nbsp; &nbsp;&nbsp;}


}







Pronto com DataProvider podemos utilizar o mesmo código com vários cenários.

Entendendo...

O cada cenário do DataProvider é injetado no método de teste como argumento. Se observarmos o primeiro cenário, onde temos: 1, $pegasus('Pegasus'), estes dois parâmetros irão ser setados no método de teste.



Bom pessoal, por enquanto é isso...um abraço e fiquem ligados