Os métodos de teste grandes indicam um cheiro de código?

5

Eu tenho um método específico chamado TranslateValues () (Cyclomatic-Complexity of 5) que eu gostaria de testar.

O teste requer um número substancial de objetos simulados que ocupam a maior parte do método ; O método que está sendo testado é bastante simples com esse requisito excepcional. Eu suspeito disso. Para mim, posso fazer algo assim para aliviar o problema:

namespace TestArea
{
    [TestClass]
    public class PrimaryTests
    {
        //commonly used classes should go up here
        private CoreServiceClient client;
        public CoreServiceClient Client
        {
            get { return client; }
            set { client = value; }
        }

        [TestMethod]
        public void TestMethodForXYZ()
        {
            try
            {
              //use the Client, do some stuff, and assert
            }
            catch
            {
             //catch any problems, report them, and assert.fail
            }
        }
   }
}

Mas isso só vai levar você até certo ponto a reduzir o tamanho e a confusão dos métodos de teste, e isso gera uma pergunta: Os grandes métodos de teste geralmente indicam o cheiro do código? Como você avalia quando seus métodos estão se tornando muito grandes?

    
por Ray 26.07.2012 / 00:03
fonte

4 respostas

2

Eu digo sim. Você está testando demais.

Eu sou culpado disso, mas idealmente você testa uma coisa de cada vez (para ser lida como "uma afirmação por teste"). Essa é a visão utópica e, desde que você esteja mais perto disso do que o "um teste para tudo com uma tonelada de afirmações", você está bem.

Eu acho que realmente se resume a usar a ferramenta corretamente. NUnit, por exemplo, tem TestCaseAttributes para que você possa ter um teste que passe por vários casos, que você basicamente descreveu. Executá-los através de um teste é menos útil, porque deve parar na primeira falha, então você só pode dizer se você quebrou vários casos corrigindo as falhas individuais como você os vê.

Quanto a medir quando eles são muito grandes, é praticamente assim que eu quero ter uma execução de Assert específica (e nenhuma outra), mas não posso, porque o teste deve executar várias outras afirmações para permanecer válido.

No final, isso realmente significa seguir a Responsabilidade única ao escrever testes, bem como código.

    
por 26.07.2012 / 00:17
fonte
2

Por que seu teste pode ser longo:

  • você está testando mais de uma coisa
  • dependências sut exigem muita zombaria (provavelmente o seu caso)
  • seu sut requer muitas dependências

Se você está testando mais de uma coisa

Escreva testes separados, para testar cada coisa. Cada teste exigirá menos organização e menos afirmações. Quando algum teste for quebrado, você saberá exatamente o que não está funcionando como esperado.

Se seus mocks exigem muita configuração

Primeiro de tudo, você deve verificar por que seu sut pedindo sua dependência muito. Talvez você tome decisões com base no estado de outro objeto, violando assim o princípio Diga, não pergunte . Considere mover essa lógica de decisões para dependência.

// for this code you should setup mocks of person and address
if (person.Address != null) {
  Address address = person.Address;
  address.City = city;
}

vs

person.RelocateTo(city);

Se você tiver certeza absoluta de que toda a lógica está em seu lugar, mas sua simulação ainda requer alguma configuração grande, considere mover a criação de simulação para um método separado. Isso também permitirá reutilizar esse método em vários testes.

Mock<Foo> fooMock = new Mock<Foo>();
fooMock.Setup(foo => foo.X).Returns("x");
fooMock.Setup(foo => foo.Y).Returns("y");
fooMock.Setup(foo => foo.Z).Returns("z");
Bar bar = new Bar(fooMock.Object);

vs

Foo foo = CreateFoo("x", "y", "z");
Bar bar = new Bar(foo);

Se o seu sut exigir muitas dependências

Se algumas das suas dependências sempre forem usadas juntas, considere a introdução de um objeto, que as manterá. Também verifique se você realmente precisa de um objeto inteiro para ser passado. Talvez você só precise de um campo desse objeto.

public void Foo(TimeProvider timeProvider)
{
   DateTime time = timeProvider.GetCurrentTime();
   ...
}

vs

public void Foo(DateTime currentTime)
{
   ...
}
    
por 26.07.2012 / 09:28
fonte
1

Eu não acredito que um método de teste grande seja o cheiro de código por si só.

No entanto, existem sinais, por exemplo, quando você

  • precisa zombar de muitas dependências (muito é relativo também)
  • precisa configurar muitas coisas
  • precisa saber muitas coisas sobre os detalhes reais da implementação (quando você faz TDD, isso não será um problema)
  • não pode separar o teste de um método para casos de teste bem definidos (diferentes métodos de teste)

Da minha experiência, um dos maiores sinais de que um método está fazendo muito é quando você não pode realmente escrever um caso de teste específico, apenas algo como TestOfMyMethod() . Isso provavelmente não é um caso de teste, apenas um grande mash de código cobrindo a implementação exata do método real, que será quebrado na próxima vez que você alterar alguma coisa.

    
por 26.07.2012 / 00:19
fonte
1

Não , nem sempre. Os métodos de teste Big geralmente são resultados de estruturas de teste / simulação e seu funcionamento (ou, precisamente - como você deve usá-los).

Por exemplo, o método pode ser perfeitamente bem e bem projetado, mas vamos imaginar que ele use 2 ou 3 dependências - para testá-lo, você provavelmente terá que apagar / zombar dessas dependências. Isso adiciona linhas extras de código ao método de teste, que não parecem atribuir nenhum método testado.

Dito isto, demasiadas dependências podem ser bons indicadores (o que também resultará em código de método de teste mais longo devido ao trabalho extra de configuração) e é aí que eu ficaria cauteloso, não o comprimento em si.

Exemplo muito trivial de teste bastante comum, em que o método de teste será "longo" , comparado ao que está realmente testando (que provavelmente é uma linha de código) :

[Test]
public void GenerateReport_ThrowsInvalidOperationException_WhenTaskIsNotReady()
{
    var providerMock = new Mock<ITaskProvider>();
    providerMock.Setup(m => m.IsTaskReady).Returns(false);
    var sut = new ReportGenerator(providerMock);

    Assert.That(sut, Throws.InstanceOf<InvalidOperationException>());
}

Agora, adicione mais bits .Setup call, asserção mais complexa - isso resulta em um tamanho ainda maior do método de teste.

    
por 26.07.2012 / 00:13
fonte