Quão profundos são seus testes de unidade?


88

O que descobri sobre o TDD é que leva tempo para configurar seus testes e, sendo naturalmente preguiçoso, sempre quero escrever o mínimo de código possível. A primeira coisa que pareço fazer é testar se meu construtor definiu todas as propriedades, mas isso é um exagero?

Minha pergunta é em que nível de granularidade você escreve seus testes de unidade?

..e há um caso de teste demais?

Respostas:


221

Eu sou pago por código que funciona, não por testes, então minha filosofia é testar o mínimo possível para alcançar um determinado nível de confiança (suspeito que esse nível de confiança seja alto em comparação com os padrões da indústria, mas isso pode ser apenas arrogância) . Se eu normalmente não cometer um tipo de erro (como definir as variáveis ​​erradas em um construtor), não testo. Eu tendo a entender os erros de teste, então sou extremamente cuidadoso quando tenho lógica com condicionais complicadas. Ao codificar em equipe, modifico minha estratégia para testar cuidadosamente o código que, coletivamente, tendemos a errar.

Pessoas diferentes terão estratégias de teste diferentes com base nessa filosofia, mas isso parece razoável para mim, dado o estado imaturo de compreensão de como os testes podem se encaixar melhor no loop interno da codificação. Daqui a dez ou vinte anos, provavelmente teremos uma teoria mais universal sobre quais testes escrever, quais testes não escrever e como saber a diferença. Nesse ínterim, a experimentação parece adequada.


40
O mundo não pensa que Kent Beck diria isso! Existem legiões de desenvolvedores zelosamente perseguindo 100% de cobertura porque acham que é o que Kent Beck faria! Eu disse a muitos que você disse, em seu livro XP, que nem sempre adere ao Test First religiosamente. Mas também estou surpreso.
Charlie Flowers

6
Na verdade discordo, porque o código que um desenvolvedor produz não é o seu, e no próximo sprint alguém irá alterá-lo e cometer erros que você "sabe que não". Também TDD você pensa sobre os testes primeiro. Então, se você faz TDD presumindo que vai testar parte do código, está fazendo errado
Ricardo Rodrigues

2
Não estou interessado em cobertura. Estou muito interessado em saber quantas vezes o Sr. Beck confirma código que não foi escrito em resposta a um teste com falha.
sheldonh

1
@RicardoRodrigues, você não pode escrever testes para cobrir código que outras pessoas irão escrever depois. Isso é responsabilidade deles.
Kief

2
Não foi isso que escrevi, leia com atenção; Eu escrevi que se você escrever testes para cobrir apenas uma parte do SEU próprio código, deixando partes descobertas onde "você sabe que não comete erros" e essas partes são alteradas e não têm testes adequados, você tem um problema bem aí, e isso não é TDD.
Ricardo Rodrigues

20

Escreva testes de unidade para coisas que você espera quebrar e para casos extremos. Depois disso, os casos de teste devem ser adicionados à medida que os relatórios de bug chegam - antes de escrever a correção para o bug. O desenvolvedor pode ter certeza de que:

  1. O bug foi corrigido;
  2. O bug não reaparecerá.

De acordo com o comentário anexado - acho que essa abordagem para escrever testes de unidade pode causar problemas, se muitos bugs forem, com o tempo, descobertos em uma determinada classe. Provavelmente, é aqui que a discrição é útil - adicionar testes de unidade apenas para bugs que provavelmente ocorrerão novamente ou onde sua recorrência causaria problemas sérios. Descobri que uma medida de teste de integração em testes de unidade pode ser útil nesses cenários - codepaths superiores de teste de código podem cobrir os codepaths inferiores.


Com a quantidade de bugs que escrevo, isso pode se tornar um antipadrão. Com centenas de testes em código em que as coisas não funcionam, isso pode significar que seus testes se tornam ilegíveis e, quando chegar a hora de reescrever esses testes, isso pode se tornar uma sobrecarga.
Johnno Nolan

@JohnNolan: A legibilidade do teste é tão importante? IMHO não é, pelo menos para esses testes de regressão específicos de bug. Se você está reescrevendo testes com frequência, pode estar testando em um nível muito baixo - idealmente, suas interfaces devem permanecer relativamente estáveis, mesmo se suas implementações mudarem, e você deve testar no nível da interface (embora eu saiba que o mundo real muitas vezes não está assim ...: - /) Se suas interfaces mudarem muito, eu seria a favor de descartar a maioria ou todos esses testes específicos de bug em vez de reescrevê-los.
j_random_hacker

@j_random_hacker Sim, claro que a legibilidade é importante. Os testes são uma forma de documentação e são tão importantes quanto o código de produção. Eu concordo que eliminar os testes de grandes mudanças é uma coisa boa (tm) e que os testes devem ser feitos no nível da interface.
Johnno Nolan

19

Tudo deve ser o mais simples possível, mas não mais simples. - A. Einstein

Uma das coisas mais incompreendidas sobre o TDD é a primeira palavra nele. Teste. É por isso que o BDD apareceu. Porque as pessoas realmente não entendiam que o primeiro D era o importante, ou seja, Driven. Todos nós tendemos a pensar um pouco demais sobre o Teste, e um pouco ou pouco sobre a condução do design. E eu acho que esta é uma resposta vaga para sua pergunta, mas você provavelmente deve considerar como conduzir seu código, em vez do que você realmente está testando; isso é algo em que uma ferramenta de cobertura pode ajudá-lo. O design é uma questão muito maior e mais problemática.


Sim, é vago ... Isso significa que como um construtor não é parte do comportamento, não deveríamos testá-lo. Mas eu deveria testar o MyClass.DoSomething ()?
Johnno Nolan

Bem, depende de: P ... um teste de construção é geralmente um bom começo ao tentar testar o código legado. Mas eu provavelmente (na maioria dos casos) deixaria um teste de construção de fora, ao começar a projetar algo do zero.
kitofr 01 de

É um desenvolvimento dirigido, não um design dirigido. Ou seja, obter uma linha de base de trabalho, escrever testes para verificar a funcionalidade, avançar com o desenvolvimento. Quase sempre escrevo meus testes antes de fatorar algum código pela primeira vez.
Evan Plaice

Eu diria que o último D, Design, é a palavra que as pessoas esquecem, perdendo o foco. No design orientado a testes, você escreve código em resposta a testes com falha. Se você está fazendo um design orientado a teste, com quanto código não testado você termina?
sheldonh

15

Para aqueles que propõem testar "tudo": percebam que "testar totalmente" um método como int square(int x)requer cerca de 4 bilhões de casos de teste em linguagens comuns e ambientes típicos.

Na verdade, é ainda pior do que isso: um método void setX(int newX)também é obrigado não alterar os valores de quaisquer outros membros além x- você está testando que obj.y, obj.zetc. todos permanecem inalteradas depois de chamar obj.setX(42);?

É apenas prático testar um subconjunto de "tudo". Depois de aceitar isso, torna-se mais palatável considerar não testar um comportamento incrivelmente básico. Cada programador tem uma distribuição de probabilidade da localização do bug; a abordagem inteligente é concentrar sua energia em regiões de teste onde você estima que a probabilidade de bug seja alta.


9

A resposta clássica é "teste qualquer coisa que possa quebrar". Eu interpreto isso como significando que testar setters e getters que não fazem nada exceto set ou get é provavelmente muito teste, não há necessidade de perder tempo. A menos que seu IDE escreva para você, você também pode.

Se o seu construtor não definir as propriedades pode levar a erros mais tarde, então testar se elas estão definidas não é um exagero.


sim e este é um vínculo para uma classe com muitas propriedades e muitos construtores.
Johnno Nolan

Quanto mais trivial for um problema (como esquecer de inicializar um membro até zero), mais tempo levará para depurá-lo.
Lev

5

Eu escrevo testes para cobrir as suposições das aulas que irei escrever. Os testes reforçam os requisitos. Essencialmente, se x nunca pode ser 3, por exemplo, vou garantir que haja um teste que cubra esse requisito.

Invariavelmente, se eu não escrever um teste para cobrir uma condição, ele aparecerá mais tarde durante o teste "humano". Certamente escreverei um então, mas prefiro pegá-los logo. Acho que o ponto é que o teste é tedioso (talvez), mas necessário. Eu escrevo testes suficientes para serem concluídos, mas não mais do que isso.


5

Parte do problema de pular testes simples agora está no futuro, a refatoração poderia tornar essa propriedade simples muito complicada com muita lógica. Acho que a melhor ideia é que você pode usar testes para verificar os requisitos para o módulo. Se, ao passar em X, você receber Y de volta, é isso que você deseja testar. Então, quando você alterar o código posteriormente, poderá verificar se X fornece Y e pode adicionar um teste para A fornece B, quando esse requisito for adicionado posteriormente.

Descobri que o tempo que gasto durante o desenvolvimento inicial escrevendo testes compensa na primeira ou na segunda correção de bug. A capacidade de pegar um código que você não olha em 3 meses e ter certeza razoável de que sua correção cobre todos os casos e "provavelmente" não quebra nada é extremamente valiosa. Você também descobrirá que os testes de unidade ajudarão a fazer a triagem de bugs muito além do rastreamento da pilha, etc. Ver como partes individuais do aplicativo funcionam e falham fornece uma grande percepção de por que funcionam ou falham como um todo.


4

Na maioria dos casos, eu diria, se houver lógica, teste-a. Isso inclui construtores e propriedades, especialmente quando mais de uma coisa é definida na propriedade.

Com relação a muitos testes, é discutível. Alguns diriam que tudo deve ser testado quanto à robustez, outros dizem que, para testes eficientes, apenas coisas que podem falhar (ou seja, a lógica) devem ser testadas.

Eu me inclinaria mais para o segundo campo, apenas por experiência pessoal, mas se alguém decidisse testar tudo, eu não diria que foi demais ... um pouco exagerado talvez para mim, mas não muito para eles.

Portanto, não - eu diria que não há testes "demais" no sentido geral, apenas para indivíduos.


3

Desenvolvimento Orientado a Testes significa que você interrompe a codificação quando todos os seus testes forem aprovados.

Se você não tem um teste para uma propriedade, por que deveria implementá-lo? Se você não testar / definir o comportamento esperado no caso de uma atribuição "ilegal", o que a propriedade deve fazer?

Portanto, estou totalmente a favor de testar todos os comportamentos que uma classe deve apresentar. Incluindo propriedades "primitivas".

Para tornar esse teste mais fácil, criei um NUnit simples TestFixtureque fornece pontos de extensão para definir / obter o valor e leva listas de valores válidos e inválidos e tem um único teste para verificar se a propriedade funciona bem. O teste de uma única propriedade pode ser assim:

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

Usando lambdas e atributos, isso pode até ser escrito de forma mais compacta. Percebi que o MBUnit tem até suporte nativo para coisas assim. O ponto, porém, é que o código acima captura a intenção da propriedade.

PS: Provavelmente o PropertyTest também deve ter uma maneira de verificar se outras propriedades do objeto não foram alteradas. Hmm .. de volta à prancheta de desenho.


Fui a uma apresentação no mbUnit. Parece ótimo.
Johnno Nolan

Mas David, deixe-me perguntar: você ficou surpreso com a resposta de Kent Beck acima? A resposta dele faz você se perguntar se você deve repensar sua abordagem? Não porque alguém tenha "respostas do alto", claro. Mas Kent é considerado um dos principais proponentes do teste primeiro. Um centavo pelos seus pensamentos!
Charlie Flowers,

@Charly: A resposta de Kent é muito pragmática. Estou "apenas" trabalhando em um projeto em que integrarei código de várias fontes e gostaria de fornecer um nível muito alto de confiança.
David Schmitt

Dito isso, eu me esforço para ter testes que sejam mais simples do que o código testado e esse nível de detalhe só pode valer a pena em testes de integração onde todos os geradores, módulos, regras de negócios e validadores vêm juntos.
David Schmitt

1

Eu faço teste de unidade para alcançar a cobertura máxima viável. Se eu não conseguir alcançar algum código, eu refatoro até que a cobertura esteja o mais completa possível

Depois de terminar o teste de escrita ofuscante, geralmente escrevo um caso de teste reproduzindo cada bug

Estou acostumado a separar entre teste de código e teste de integração. Durante o teste de integração (que também são testes de unidade, mas em grupos de componentes, portanto, não exatamente para o que os testes de unidade servem), testarei os requisitos a serem implementados corretamente.


1

Portanto, quanto mais eu conduzo minha programação escrevendo testes, menos me preocupo com o nível de granualidade dos testes. Olhando para trás, parece que estou fazendo a coisa mais simples possível para atingir meu objetivo de validar o comportamento . Isso significa que estou gerando uma camada de confiança de que meu código está fazendo o que eu peço; no entanto, isso não é considerado uma garantia absoluta de que meu código está livre de bugs. Eu sinto que o equilíbrio correto é testar o comportamento padrão e talvez um caso extremo ou dois e então passar para a próxima parte do meu projeto.

Aceito que isso não cobrirá todos os bugs e uso outros métodos de teste tradicionais para capturá-los.


0

Geralmente, começo pequeno, com entradas e saídas que sei que devem funcionar. Então, conforme eu corrijo os bugs, adiciono mais testes para garantir que as coisas que consertei sejam testadas. É orgânico e funciona bem para mim.

Você pode testar muito? Provavelmente, mas provavelmente é melhor errar no lado da cautela em geral, embora isso dependa de quão crítico é o seu aplicativo.


0

Acho que você deve testar tudo em seu "núcleo" de sua lógica de negócios. Getter e Setter também porque eles podem aceitar valores negativos ou nulos que você pode não querer aceitar. Se você tiver tempo (sempre dependa do seu chefe) é bom testar outras lógicas de negócios e todos os controladores que chamam esses objetos (você vai do teste de unidade ao teste de integração aos poucos).


0

Eu não faço testes de unidade com métodos setter / getter simples que não têm efeitos colaterais. Mas eu faço testes de unidade em todos os outros métodos públicos. Tento criar testes para todas as condições de contorno em meus algoritmos e verifico a cobertura de meus testes de unidade.

É muito trabalho, mas acho que vale a pena. Eu preferiria escrever código (mesmo testar código) do que percorrer o código em um depurador. Acho que o ciclo de código-construção-implantação-depuração consome muito tempo e quanto mais exaustivos os testes de unidade que integrei em minha construção, menos tempo gasto passando por esse ciclo de código-construção-implantação-depuração.

Você não disse por que a arquitetura também está codificando. Mas para Java eu ​​uso Maven 2 , JUnit , DbUnit , Cobertura e EasyMock .


Eu não disse isso, pois é uma questão bastante independente de linguagem.
Johnno Nolan

O teste de unidade em TDD não cobre apenas enquanto você está escrevendo o código, mas também protege contra a pessoa que herda seu código e então pensa que faz sentido formatar um valor dentro do getter!
Paxic de

0

Quanto mais leio sobre isso, mais acho que alguns testes de unidade são como alguns padrões: Um cheiro de línguas insuficientes.

Quando você precisa testar se seu getter trivial realmente retorna o valor correto, é porque você pode misturar o nome do getter e o nome da variável de membro. Digite 'attr_reader: nome' de ruby, e isso não pode mais acontecer. Simplesmente não é possível em java.

Se seu getter se tornar não trivial, você ainda pode adicionar um teste para ele.


Concordo que testar um getter é trivial. No entanto, posso ser estúpido o suficiente para esquecer de configurá-lo em um construtor. Portanto, é necessário um teste. Meus pensamentos mudaram desde que fiz a pergunta. Veja minha resposta stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/…
Johnno Nolan

1
Na verdade, eu diria que, de alguma forma, os testes de unidade como um todo são o cheiro de um problema de linguagem. Linguagens que suportam contratos (pré / pós-condições sobre métodos) como Eiffel, ainda precisam de alguns testes de unidade, mas precisam menos deles. Na prática, até mesmo contratos simples tornam realmente fácil localizar bugs: quando o contrato de um método quebra, o bug geralmente está naquele método.
Damien Pollet

@Damien: Talvez os testes de unidade e os contratos sejam realmente a mesma coisa disfarçada? O que quero dizer é que uma linguagem que "suporta" contratos basicamente torna mais fácil escrever trechos de código - testes - que são (opcionalmente) executados antes e depois de outros trechos de código, correto? Se sua gramática for simples o suficiente, uma linguagem que não oferece suporte nativo a contratos pode ser facilmente estendida para suportá-los escrevendo um pré-processador, correto? Ou há algumas coisas que uma abordagem (contratos ou testes de unidade) pode fazer e a outra simplesmente não pode?
j_random_hacker

0

Teste o código-fonte que o preocupa.

Não é útil testar partes do código nas quais você tem muita confiança, contanto que não cometa erros nelas.

Teste as correções de bugs, para que seja a primeira e a última vez que você conserta um bug.

Teste para obter confiança em partes obscuras do código, para que você crie conhecimento.

Teste antes da refatoração pesada e média, para não quebrar os recursos existentes.


0

Essa resposta é mais para descobrir quantos testes de unidade usar para um determinado método que você sabe que deseja testar de unidade devido à sua criticidade / importância. Usando a técnica de Teste de Caminho de Base da McCabe, você poderia fazer o seguinte para ter uma melhor confiança de cobertura de código quantitativa do que a simples "cobertura de instrução" ou "cobertura de ramificação":

  1. Determine o valor da Complexidade Ciclomática do método que deseja testar de unidade (o Visual Studio 2010 Ultimate, por exemplo, pode calcular isso para você com ferramentas de análise estática; caso contrário, você pode calculá-lo manualmente por meio do método de fluxograma - http://users.csc. calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html )
  2. Liste o conjunto básico de caminhos independentes que fluem através de seu método - consulte o link acima para um exemplo de fluxograma
  3. Prepare testes de unidade para cada caminho de base independente determinado na etapa 2

Você vai fazer isso para todos os métodos? Seriamente?
Kristopher Johnson,
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.