Quando comecei a tentar testar meus códigos, iniciei também a busca por métricas que indicassem que eu estava no caminho correto, evitando me desvirtuar durante essa mudança de pensamento ao codificar software. Na época, o manual do PHPUnit indicava que o framework de teste possuia ferramentas para cobertura de código. Eis que fui apresentado ao Code Coverage.
"Uau! Esta é uma métrica indiscutível para mensurar a qualidade de meus testes!" - pensei na época.
Depois que tomei conhecimento da análise da Cobertura de Código pelos testes, não se falava em outra coisa a não ser:
Neste projeto o objetivo é ter 90% de cobertura de código; Mas no próximo, aaaaah, no próximo nada menos do que 98% pode ser aceito.
Sério. Ficávamos numa neura assim mesmo. Não era para menos. Pense comigo: sendo como objetivo do teste, garantir que o código de produção funcione, nada mais sensato do que cobrir a maior quantidade do Código de Produção (todo código que não é de teste ou do framework no projeto, assim digamos) possível. Ou seja: a ideia é que nossos testes passe pelo maior número de linhas possíveis para garantirmos que toda linha está funcionando como deveria em seu devido fluxo. Por exemplo:
class User {
public void fazLalala(String comFoo) throws Exception {
if (comFoo == null) {
throw new Exception("Lascou!");
}
if (comFoo.startWith("Lalala")) {
this.doSomething(comFoo);
} else {
this.doAnother(comFoo);
}
}
}
No caso deveriamos exercitar os 3 possíveis casos: lançamento de exception
; if == true
e if != true
, afinal o teste é para verificar se a exception é lançada somente quando necessário e o if/else trabalhar conforme o esperado vindo do input do método.
Porque este pensamento está incorreto
Testar as possibilidades do comportamento de um método de um objeto é fundamental, porém, no caso acima, eu estava olhando pela ótica errada.
Eu estava testando para obter Code Coverage ao invés de obter Code Coverage por estar testando.
Inversamente a Matemática, no português a inversão dos valores podem não serem equivalentes, como neste caso. Testar com o objetivo de obter Code Coverage não é um bom motivo; agora, obter um bom Code Coverage por causa do teste é algo muito bom.
Para ilustar o problema de Testar Code Coverage Driven, vamos ver o código abaixo:
class User {
public void addCredentials(String username, String password) {
// do something...
}
public String getName() {
return "...";
}
public String getCredentials() {
return "...";
}
public String getLastname() {
return "...";
}
}
Ao Testar focado em obter Code Coverage, algo assim poderá ser encontrado na classe de teste do User
:
class UserTest {
@test
public void testGetName() {
user = new User();
user.setName("name");
assertEquals("name", user.getName());
}
@test
public void testGetLastname() {
user = new User();
user.setLastname("last name");
assertEquals("last name", user.getLastname());
}
@test
public void testAddCredentials() {
user = new User();
user.addCredentials("username", "senha_marota");
assertEquals([["username", Digest::MD5.hexdigest("senha_marota")]], user.getCredentials());
}
É possível observar dois problemas aqui:
- Getter/Setter foi testado isoladamente, como se fosse uma regra de domínio.
- O teste do addCredentials teve o mesmo tratamento do que os Getters/Setters.
Não me entenda errado: não existe nada que diga para não testarmos getters/setters. O problema é: testar coisas que serão testadas em métodos futuros por consequência. Por consequência que digo seria:
@test
public void testCredentialUsernameShouldNotEqualsToName() {
String name = "Mesmo nome";
user = new User();
user.setName(name)
user.addCredentials(name, "senha_marota");
assertEmpty(user.getCredentials());
}
Assumindo que uma das regras da Credencial é que o username não seja igual ao nome do usuário, um teste deste tipo já nos garante que o setName()
e o getName()
funcionam como esperamos que funcionará, pois o addCredentials
irá chamar o getName
para achar o nome do usuário:
public void addCredentials(String username, String password) {
if (getName() == username) {
// do nothing / throws exception
}
}
Com isto, o teste testGetName()
pode ser dispensado tranquilamente, pois ele foi testado em consequência ao teste de negócio testCredentialUsernameShouldNotEqualsToName
.
O segundo problema é ainda mais grave: por testar getter/setter, você poderá cair em copy/paste nos testes pela sua similaridade e talvez isso o fará cair na armadilha de esquecer de testar o que realmente precisa de atenção por conter Regras de Negócio (Domínio) envolvida, como o addCredentials
tem. Testes válidos para a classe proposta seriam: testAddNewCredential
, testCredentialUsernameShouldNotEqualsToName
e talvez até testDuplicatedCredentialShouldRaiseError
.
Porque este pensamento está correto
A corretude de Testar Orientado a Cobertura de Código está em se e somente se o Teste fosse para garantir que o código funciona o que não é o objetivo do teste de unidade. Por não ser objetivo principal, testar para satisfazer a cobertura de código não trará garantia alguma, além de que o software não têm erros de sintaxe ou fluxo. É alguma coisa? Sim, claro! Mas estará longe da grande vantagem que Test-First traz: montar sua aplicação da forma mais orquestrada possível.
Concluíndo
Foque nos testes que agregam valor ao negócio(domínio) do software que os métodos auxiliares serão avaliados em consequência, trazendo uma cobertura altíssima e mais: confiável. Exercite seu addCredentials
criando um teste para cada regra de negócio que lhe foi solicitada, dando segurança real no seu Código de Produção.
0sem comentários ainda