Recentemente, eu fui TDDing um método de fábrica. O método era criar um objeto simples ou um objeto embrulhado em um decorador. O objeto decorado pode ser de um dos vários tipos, todos estendendo a StrategyClass.
No meu teste, eu queria verificar se a classe do objeto retornado é como o esperado. Isso é fácil quando o objeto comum é retornado, mas o que fazer quando ele é embrulhado em um decorador?
Eu codifico em PHP para que eu pudesse usar ext/Reflection
para descobrir uma classe de objeto empacotado, mas parecia-me complicar demais as coisas e, de certa forma, repetir as regras do TDD.
Em vez I decidiu introduzir getClassName()
que voltaria nome da classe do objeto quando chamado de StrategyClass. Quando chamado do decorador, no entanto, retornaria o valor retornado pelo mesmo método no objeto decorado.
Algum código para deixar mais claro:
interface StrategyInterface {
public function getClassName();
}
abstract class StrategyClass implements StrategyInterface {
public function getClassName() {
return \get_class($this);
}
}
abstract class StrategyDecorator implements StrategyInterface {
private $decorated;
public function __construct(StrategyClass $decorated) {
$this->decorated = $decorated;
}
public function getClassName() {
return $this->decorated->getClassName();
}
}
E um teste PHPUnit
/**
* @dataProvider providerForTestGetStrategy
* @param array $arguments
* @param string $expected
*/
public function testGetStrategy($arguments, $expected) {
$this->assertEquals(
__NAMESPACE__.'\\'.$expected,
$this->object->getStrategy($arguments)->getClassName()
)
}
//below there's another test to check if proper decorator is being used
Meu argumento aqui é: está correto introduzir métodos que não têm outro uso senão facilitar os testes de unidade? De alguma forma, não parece certo para mim.
Respostas:
Meu pensamento é não, você não deve fazer nada apenas porque é necessário para a testabilidade. Muitas decisões tomadas pelas pessoas beneficiam a testabilidade e a testabilidade pode até ser o principal benefício, mas deve ser uma boa decisão de design por outros méritos. Isso significa que algumas propriedades desejadas não são testáveis. Outro exemplo é quando você precisa saber o quão eficiente é a rotina, por exemplo, o Hashmap usa um intervalo de valores de hash distribuído igualmente - nada na interface externa pode lhe dizer isso.
Em vez de pensar: "estou recebendo a classe de estratégia correta" acho "a classe que eu obtenho desempenha o que essa especificação está tentando testar?" É bom quando você pode testar o encanamento interno, mas não precisa, basta testar girar o botão e ver se há água quente ou fria.
fonte
Minha opinião é que, às vezes, você precisa refazer um pouco o código-fonte para torná-lo mais testável. Não é o ideal e não deve ser uma desculpa para desordenar a interface com funções que devem ser usadas apenas para testes, portanto a moderação geralmente é a chave aqui. Você também não quer estar na situação em que os usuários do seu código repentinamente usam as funções da interface de teste para interações normais com seu objeto.
Minha maneira preferida de lidar com isso (e eu tenho que me desculpar por não poder mostrar como fazer isso no PHP, já que codifico principalmente em linguagens de estilo C) é fornecer as funções 'test' de uma maneira que elas não são. exposto ao mundo externo pelo próprio objeto, mas pode ser acessado por objetos derivados. Para fins de teste, derivaria então uma classe que manipularia a interação com o objeto que realmente quero testar e o teste de unidade usaria esse objeto em particular. Um exemplo em C ++ seria algo parecido com isto:
Classe de tipo de produção:
Classe auxiliar de teste:
Dessa forma, você pelo menos está em uma posição em que não precisa expor as funções do tipo 'teste' em seu objeto principal.
fonte
Poucos meses atrás, quando coloquei minha máquina de lavar louça recém-comprada, saiu muita água da mangueira, percebi que isso provavelmente se deve ao fato de ter sido testado corretamente na fábrica de onde veio. Não é incomum ver furos de montagem e outras coisas nas máquinas que existem apenas para fins de teste em uma linha de montagem.
O teste é importante, se necessário, basta adicionar algo a ele.
Mas tente algumas das alternativas. Sua opção baseada em reflexão não é tão ruim assim. Você pode ter um acessador virtual protegido para o que precisa e criar uma classe derivada para testar e afirmar. Talvez você possa dividir sua classe e testar diretamente uma classe de folha resultante. Ocultar o método de teste com uma variável do compilador no seu código-fonte também é uma opção (eu mal conheço PHP, não tenho certeza se isso é possível no PHP).
No seu contexto, você pode decidir não testar a composição adequada dentro do decorador, mas testar o comportamento esperado que a decoração deve ter. Talvez isso enfoque um pouco mais o comportamento esperado do sistema e não tanto as especificações técnicas (o que o padrão decorador o comprará do ponto de vista funcional?).
fonte
Eu sou um novato absoluto no TDD, mas parece depender do método que está sendo adicionado. Pelo que entendi do TDD, seus testes devem "impulsionar" a criação da sua API até certo ponto.
Quando estiver tudo bem:
Quando não estiver tudo bem:
fonte