Os testes de unidade devem ser escritos para getter e setters?

141

Deveríamos escrever testes para nossos getters e setters ou é um exagero?

Hector421
fonte
Acho que não. Você não deve escrever um caso de teste para getter / setter.
Harry Joy
1
Eu suponho que você quer dizer Java? Essa é uma questão particularmente aguda para Java, muito menos para linguagens mais modernas.
skaffman
@skaffman Quais idiomas modernos não têm propriedades? Certamente, linguagens como Java exigem que eles sejam corpos de método completos, mas isso não torna lógico diferente de dizer C #.
Claus Jørgensen
2
@ Claus: Ele não disse propriedades, disse getters e setters. Em java, você os escreve manualmente; em outros idiomas, obtém melhor suporte.
skaffman
2
Pode-se perguntar por que existem getters e setters .
precisa

Respostas:

180

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.

skaffman
fonte
7
Visar 100% de cobertura de código é bobagem. Você acabará cobrindo um código que não é exposto ao público e / ou código sem complexidade. É melhor deixar essas coisas para a geração automática, mas mesmo os testes gerados automaticamente são inúteis. É melhor mudar o foco para testes importantes, como testes de integração.
Claus Jørgensen
7
@ Claus: Eu concordo, exceto um pouco sobre focar nos testes de integração. Os testes de unidade são críticos para o bom design e complementam os testes de integração.
skaffman
5
Eu acho que 100% de cobertura de código quando todo o conjunto de testes é executado é um bom objetivo. Setters de propriedade e outros códigos são executados como parte de testes maiores. As últimas peças a serem cobertas provavelmente são o tratamento de erros. E se o tratamento de erros não for coberto por testes de unidade, nunca será coberto. Deseja realmente enviar um produto que contenha código que nunca foi executado?
Anders Abel
Eu tenderia a discordar deste conselho. Sim, getters e setters são simples, mas também são surpreendentemente fáceis de estragar. Algumas linhas de testes simples e você sabe que eles estão funcionando e continuam a funcionar.
Charles
Você pode estar testando setters desnecessários que você (ou uma ferramenta) criou apenas para ter um POJO 'arredondado'. Por exemplo, você pode usar o Gson.fromJson para "inflar" o POJOS (não são necessários setters). Nesse caso, minha escolha é excluir os setters não utilizados.
Alberto Gaona
36

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.

azhidkov
fonte
1
Considerando o pouco tempo necessário para testá-los e a probabilidade de um comportamento ser adicionado, não vejo por que não. Eu suspeito que, se pressionado, ele pode responder "bem, suponho por que não".
Sled
1
Livros antigos importantes costumam ter maus conselhos. Eu já vi muitos casos em que as pessoas jogam getters e setters rapidamente e cometem erros porque estão cortando e colando ou esquecendo isso. ou isso-> ou algo assim. Eles são fáceis de testar e certamente devem ser testados.
Charles
35

Um retumbante SIM com TDD


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 DDD no TDD

O TDD é frequentemente citado como tendo os principais benefícios.

  • Defesa
    • Garantir que o código possa mudar, mas não seu comportamento .
    • Isso permite a sempre tão importante prática de refatoração .
    • Você ganha esse TDD ou não.
  • Projeto
    • Você especifica o que algo deve fazer, como deve se comportar antes de implementá- lo.
    • Isso geralmente significa decisões de implementação mais informadas .
  • Documentação
    • O conjunto de testes deve servir como documentação de especificação (requisitos).
    • O uso de testes para esse fim significa que a documentação e a implementação estão sempre em estado consistente - uma alteração em um significa uma alteração em outro. Compare com a manutenção de requisitos e design em um documento do Word separado.

Separe a responsabilidade da implementação

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.

A falta de engenharia de ida e volta ...

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.

... e como o TDD resolve

É a parte da documentação do TDD que garante que as especificações do sistema e seu código sejam sempre consistentes.

Projete primeiro, implemente depois

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.

Izhaki
fonte
2
Esse é apenas um lado da moeda. Pense nas coisas que você poderia ter feito em vez de testar apenas para testar. Como Kent Beck disse, você é pago pelo código de trabalho, não pelos testes de trabalho.
Georgii Oleinikov 10/04/19
@GeorgiiOleinikov Você leu minha outra resposta abaixo? Está praticamente alinhado com a sua visão.
Izhaki
21

tl; dr: Sim, você deveria , e com o OpenPojo é trivial.

  1. 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.

  2. 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 nullfazer um teste, caso alguém mude isso mais tarde, isso reforçará suas suposições.

  3. Um erro comum é void setFoo( Object foo ){ foo = foo; }onde deveria estar void setFoo( Object foo ){ this.foo = foo; }. (No primeiro caso, o fooque está sendo gravado é o parâmetro, não o foocampo no objeto ).

  4. 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.

  5. 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 );
Trenó
fonte
11
Eu sei que isso é antigo agora, mas: você deve estar validando seus getters e setters? Fiquei com a impressão de que setMom deveria fazer o que diz. Se estiver validando, não deve ser validateAndSetMom? Ou melhor, o código de validação não deveria existir em OUTRO LUGAR do que um objeto simples? O que estou perdendo aqui?
Ndtreviv
14
Sim, você deve sempre validar suas entradas. Se não estiver, por que não usar apenas uma variável pública? Torna-se a mesma coisa. Todo o benefício do uso de setters versus variáveis ​​é que ele permite garantir que seu objeto nunca seja inválido, como a idade sendo um número negativo. Se você não fizer isso no objeto, estará fazendo isso em outro lugar (como um objeto deus "de serviço" ) e isso não é realmente OOP nesse ponto.
Sled
1
Bem, esse é provavelmente o destaque da minha semana. Obrigado.
Sled
19

Sim, mas nem sempre isoladamente

Permita-me elaborar:

O que é um teste de unidade?

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 .

O que é um bom teste?

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 Catinge 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.

Por que não testar getters e setters de bean isoladamente

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 setVATfosse lançar uma exceção, um teste correspondente seria apropriado, pois agora existe um efeito externo.

Como você deve testar getters e setters?

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ão setVAT() .

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.

Izhaki
fonte
13

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:

  1. Faltam testes que usem indiretamente as propriedades
  2. As propriedades não são utilizadas

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.

bryanbcook
fonte
12

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.

Claus Jørgensen
fonte
4
Embora um deve testar para cópia defensiva em getters
Sled
8

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.

muymoo
fonte
1
seu link deve realmente apontar para: The Way of Testivus
JJS
2

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!

Devin C
fonte
2

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 ...

Radboud
fonte
1

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.

avandeursen
fonte
1

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())));

-> https://github.com/Mixermachine/base-test

Aaron Dietz
fonte
0

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 NullPointerExceptione 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.

Alonso del Arte
fonte