Deveríamos escrever testes para nossos getters e setters ou é um exagero?
Deveríamos escrever testes para nossos getters e setters ou é um exagero?
Respostas:
Eu diria que não.
A @Will disse que você deveria ter 100% de cobertura do código, mas na minha opinião isso é uma distração perigosa. Você pode escrever testes de unidade com 100% de cobertura e, no entanto, testar absolutamente nada.
Os testes de unidade existem para testar o comportamento do seu código, de maneira expressiva e significativa, e os getters / setters são apenas um meio para atingir um fim. Se você testar, use os getters / setters para atingir seu objetivo de testar a funcionalidade "real", então isso é bom o suficiente.
Se, por outro lado, seus getters e setters fizerem mais do que apenas get e set (ou seja, são métodos adequadamente complexos), então sim, eles devem ser testados. Mas não escreva um caso de teste de unidade apenas para testar um levantador ou levantador, isso é uma perda de tempo.
Roy Osherove em seu famoso livro 'The Art Of Unit Testing' diz:
As propriedades (getters / setters em Java) são bons exemplos de código que geralmente não contêm lógica e não requerem testes. Mas cuidado: depois de adicionar qualquer verificação dentro da propriedade, verifique se a lógica está sendo testada.
Nota : Esta resposta continua recebendo votos positivos, embora potencialmente seja um mau conselho. Para entender o porquê, dê uma olhada na irmãzinha abaixo.
Tudo bem controverso, mas eu diria que quem responde 'não' a essa pergunta está perdendo um conceito fundamental de TDD.
Para mim, a resposta é um retumbante sim se você seguir o TDD. Se você não é, então não é uma resposta plausível.
O TDD é frequentemente citado como tendo os principais benefícios.
Como programadores, é terrivelmente tentador pensar nos atributos como algo de significância e getters e setter como uma espécie de sobrecarga.
Mas os atributos são um detalhe de implementação, enquanto setters e getters são a interface contratual que realmente faz os programas funcionarem.
É muito mais importante escrever que um objeto deve:
Permitir que seus clientes alterem seu estado
e
Permitir que seus clientes consultem seu estado
então como esse estado é realmente armazenado (para o qual um atributo é o mais comum, mas não o único).
Um teste como
(The Painter class) should store the provided colour
é importante para a parte da documentação do TDD.
O fato de a eventual implementação ser trivial (atributo) e não trazer benefícios de defesa deve ser desconhecido para você quando você escreve o teste.
Um dos principais problemas no mundo do desenvolvimento de sistemas é a falta de engenharia de ida e volta 1 - o processo de desenvolvimento de um sistema é fragmentado em subprocessos desconexos cujos artefatos (documentação, código) geralmente são inconsistentes.
1 Brodie, Michael L. "John Mylopoulos: costurando sementes da modelagem conceitual". Modelagem Conceitual: Fundamentos e Aplicações. Springer Berlin Heidelberg, 2009. 1-9.
É a parte da documentação do TDD que garante que as especificações do sistema e seu código sejam sempre consistentes.
No TDD, escrevemos primeiro o teste de aceitação com falha, e depois escrevemos o código que os deixou passar.
Dentro do BDD de nível superior, escrevemos os cenários primeiro e depois os fazemos passar.
Por que você deve excluir setters e getter?
Em teoria, é perfeitamente possível no TDD uma pessoa escrever o teste e outra implementar o código que o faz passar.
Então pergunte a si mesmo:
A pessoa que está escrevendo os testes para uma classe menciona getters e setter.
Como getters e setters são uma interface pública para uma classe, a resposta é obviamente sim , ou não haverá como definir ou consultar o estado de um objeto. No entanto , a maneira de fazer isso não é necessariamente testando cada método isoladamente; veja minha outra resposta para obter mais.
Obviamente, se você escrever o código primeiro, a resposta pode não ser tão clara.
tl; dr: Sim, você deveria , e com o OpenPojo é trivial.
Você deve fazer alguma validação em seus getters e setters, portanto deve testar isso. Por exemplo, setMom(Person p)
não deve permitir que alguém mais jovem que ela seja sua mãe.
Mesmo que você não esteja fazendo nada disso agora, é provável que faça no futuro, isso será bom para a análise de regressão. Se você deseja permitir que as mães sejam convidadas a null
fazer um teste, caso alguém mude isso mais tarde, isso reforçará suas suposições.
Um erro comum é void setFoo( Object foo ){ foo = foo; }
onde deveria estar void setFoo( Object foo ){ this.foo = foo; }
. (No primeiro caso, o foo
que está sendo gravado é o parâmetro, não o foo
campo no objeto ).
Se você estiver retornando uma matriz ou coleção, deverá testar se o getter irá ou não executar cópias defensivas dos dados passados no setter antes de retornar.
Caso contrário, se você tiver os setters / getters mais básicos, os testes de unidade adicionarão talvez cerca de 10 minutos no máximo por objeto, então qual é a perda? Se você adicionar comportamento, você já tem um teste de esqueleto e recebe esse teste de regressão gratuitamente. Se você estiver usando Java, não terá desculpa, pois existe o OpenPojo . Há um conjunto de regras existente que você pode habilitar e, em seguida, varre todo o projeto com elas para garantir que elas sejam aplicadas de maneira consistente no seu código.
De seus exemplos :
final PojoValidator pojoValidator = new PojoValidator();
//create rules
pojoValidator.addRule( new NoPublicFieldsRule () );
pojoValidator.addRule( new NoPrimitivesRule () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );
//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester () );
pojoValidator.addTester( new GetterTester () );
//test all the classes
for( PojoClass pojoClass : PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() ) )
pojoValidator.runValidation( pojoClass );
Permita-me elaborar:
De Trabalhando efetivamente com código legado 1 :
O termo teste de unidade tem uma longa história no desenvolvimento de software. Comum à maioria das concepções de testes de unidade é a ideia de que são testes isolados de componentes individuais de software. O que são componentes? A definição varia, mas no teste de unidade, geralmente estamos preocupados com as unidades comportamentais mais atômicas de um sistema. No código processual, as unidades são frequentemente funções. No código orientado a objetos, as unidades são classes.
Observe que com o OOP, onde você encontra getters e setters, a unidade é a classe , não necessariamente métodos individuais .
Todos os requisitos e testes seguem a forma da lógica Hoare :
{P} C {Q}
Onde:
{P}
é a pré-condição ( fornecida )C
é a condição de disparo ( quando ){Q}
é a pós-condição ( então )Então vem a máxima:
Teste de comportamento, não implementação
Isso significa que você não deve testar como C
atinge a pós-condição, deve verificar se esse {Q}
é o resultado C
.
Quando se trata de OOP, C
é uma classe. Portanto, você não deve testar efeitos internos, apenas efeitos externos.
Getters e setters podem envolver alguma lógica, mas, desde que essa lógica não tenha efeito externo - tornando-os acessadores de bean 2 ) um teste terá que procurar dentro do objeto e, com isso, não apenas violará o encapsulamento, mas também testará a implementação.
Portanto, você não deve testar isoladores e getters de bean. Isto é mau:
Describe 'LineItem class'
Describe 'setVAT()'
it 'should store the VAT rate'
lineItem = new LineItem()
lineItem.setVAT( 0.5 )
expect( lineItem.vat ).toBe( 0.5 )
Embora se setVAT
fosse lançar uma exceção, um teste correspondente seria apropriado, pois agora existe um efeito externo.
Não há sentido em alterar o estado interno de um objeto se essa mudança não tiver efeito externo, mesmo que esse efeito ocorra posteriormente.
Portanto, um teste para setters e getters deve se preocupar com o efeito externo desses métodos, não os internos.
Por exemplo:
Describe 'LineItem class'
Describe 'getGross()'
it 'should return the net time the VAT'
lineItem = new LineItem()
lineItem.setNet( 100 )
lineItem.setVAT( 0.5 )
expect( lineItem.getGross() ).toBe( 150 )
Você pode pensar:
Espere um segundo, estamos testando
getGross()
aqui nãosetVAT()
.
Mas se setVAT()
o teste não funcionar corretamente, o teste falhará da mesma forma.
1 Feathers, M., 2004. Trabalhando efetivamente com o código legado. Prentice Hall Professional.
2 Martin, RC, 2009. Código limpo: um manual de artesanato em software ágil. Pearson Education.
Embora existam motivos justificados para as Propriedades, existe uma crença comum em Design Orientado a Objetos que expor o estado membro via Properties é um design ruim. O artigo de Robert Martin sobre o Princípio Aberto Fechado expande isso afirmando que Propriedades incentivam o acoplamento e, portanto, limitam a capacidade de fechar uma classe da modificação - se você modificar a propriedade, todos os consumidores da classe precisarão mudar também. Ele qualifica que expor variáveis de membro não é necessariamente um design ruim, pode ser apenas um estilo pobre. No entanto, se as propriedades forem somente leitura, há menos chances de abuso e efeitos colaterais.
A melhor abordagem que posso fornecer para o teste de unidade (e isso pode parecer estranho) é tornar o máximo possível de propriedades protegidas ou internas. Isso impedirá o acoplamento, enquanto desencoraja a gravação de testes tolos para getters e setters.
Há razões óbvias nas quais as Propriedades de leitura / gravação devem ser usadas, como as propriedades do ViewModel vinculadas aos campos de entrada etc.
Mais praticamente, os testes de unidade devem direcionar a funcionalidade por meio de métodos públicos. Se o código que você está testando usar essas propriedades, você obtém cobertura de código gratuitamente. Se essas propriedades nunca forem destacadas pela cobertura do código, há uma possibilidade muito forte de que:
Se você escrever testes para getters e setters, terá uma falsa sensação de cobertura e não poderá determinar se as propriedades são realmente usadas pelo comportamento funcional.
Se a complexidade ciclomática do getter e / ou setter for 1 (o que geralmente são), então a resposta é não, você não deve.
Portanto, a menos que você tenha um SLA que exija 100% de cobertura de código, não se preocupe e se concentre em testar o aspecto importante do seu software.
PS Lembre-se de diferenciar getters e setters, mesmo em idiomas como C #, onde as propriedades podem parecer a mesma coisa. A complexidade do setter pode ser maior que o getter e, assim, validar um teste de unidade.
Um exame bem-humorado, porém sábio: O Caminho de Testivus
"Escreva o teste que você pode hoje"
Testar getters / setters pode ser um exagero se você é um testador experiente e este é um projeto pequeno. No entanto, se você está apenas começando a aprender como testar a unidade ou esses getters / setters podem conter lógica (como o setMom()
exemplo do @ ArtB ), seria uma boa ideia escrever testes.
Na verdade, esse foi um tópico recente entre minha equipe e eu. Atendemos 80% de cobertura de código. Minha equipe argumenta que getters e setters são implementados automaticamente e o compilador está gerando algum código básico nos bastidores. Nesse caso, dado que o código gerado não é invasivo, não faz sentido testar o código que o compilador cria para você. Também tivemos essa discussão sobre métodos assíncronos e, nesse caso, o compilador gera um monte de código nos bastidores. Este é um caso diferente e algo que testamos. Resposta longa, curta, fale com sua equipe e decida por si mesmo se vale a pena testar.
Além disso, se você estiver usando o relatório de cobertura de código como o nosso, é possível adicionar o atributo [ExcludeFromCodeCoverage]. Nossa solução foi usar isso para modelos que possuem apenas propriedades usando getters e setters ou na própria propriedade. Dessa forma, não afetará a% total de cobertura de código quando o relatório de cobertura de código for executado, assumindo que é isso que você está usando para calcular suas porcentagens de cobertura de código. Teste feliz!
Na minha opinião, a cobertura do código é uma boa maneira de verificar se você perdeu alguma funcionalidade que deve cobrir.
Quando você inspeciona a cobertura manualmente por sua coloração bonita, pode-se argumentar que getters e setters simples não precisam ser testados (embora eu sempre o faça).
Quando você verifica apenas a porcentagem de cobertura do código em seu projeto, uma porcentagem de cobertura de teste como 80% não faz sentido. Você pode testar todas as partes não lógicas e esquecer algumas partes cruciais. Nesse caso, apenas 100% significa que você testou todo o seu código vital (e também todo o código não lógico). Assim que são 99,9%, você sabe que se esqueceu de algo.
A propósito: A cobertura do código é a verificação final para verificar se você testou totalmente uma unidade. Mas 100% de cobertura de código não significa necessariamente que você realmente testou todas as funcionalidades da classe. Portanto, os testes de unidade devem sempre ser implementados seguindo a lógica da classe. No final, você executa a cobertura para ver se esqueceu alguma coisa. Quando você fez certo, atingiu 100% na primeira vez.
Mais uma coisa: enquanto trabalhava recentemente em um grande banco na Holanda, notei que o Sonar indicava 100% de cobertura do código. No entanto, eu sabia que algo estava faltando. Ao inspecionar as porcentagens de cobertura do código por arquivo, indicou um arquivo com uma porcentagem menor. A porcentagem inteira do código base era tão grande que o único arquivo não fez com que a porcentagem fosse exibida como 99,9%. Então, você pode querer cuidar disso ...
Fiz uma pequena análise da cobertura obtida no próprio código JUnit .
Uma categoria de código descoberto é "muito simples de testar" . Isso inclui getters e setters simples, que os desenvolvedores do JUnit não testam.
Por outro lado, o JUnit não possui nenhum método (não obsoleto) com mais de três linhas que não seja coberto por nenhum teste.
Eu diria: SIM Os erros nos métodos getter / setter podem entrar silenciosamente e causar alguns erros feios.
Eu escrevi uma lib para facilitar esse e alguns outros testes. A única coisa que você precisa escrever nos seus testes JUnit é:
assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
new EmptyCollectionCheck(), new GetterIsSetterCheck(),
new HashcodeAndEqualsCheck(), new PublicVariableCheck())));
Sim, especialmente se o item a ser obtido é um objeto de uma classe subclassificada de uma classe abstrata. Seu IDE pode ou não alertá-lo de que uma determinada propriedade não foi inicializada.
E então algum teste aparentemente não relacionado trava com um NullPointerException
e leva um tempo para você descobrir que uma propriedade gettable não está realmente lá para começar.
Embora isso ainda não fosse tão ruim quanto descobrir o problema na produção.
Pode ser uma boa idéia garantir que todas as suas classes abstratas tenham construtores. Caso contrário, o teste de um getter pode alertá-lo para um problema lá.
Quanto aos getters e setters de primitivos, a pergunta pode ser: Estou testando meu programa ou testando a JVM ou CLR? De um modo geral, a JVM não precisa ser testada.