Métodos vazios de teste de unidade?


170

Qual é a melhor maneira de testar a unidade um método que não retorna nada? Especificamente em c #.

O que realmente estou tentando testar é um método que pega um arquivo de log e o analisa para cadeias de caracteres específicas. As seqüências de caracteres são inseridas em um banco de dados. Nada que não tenha sido feito antes, mas sendo MUITO novo no TDD, estou me perguntando se é possível testar isso ou se é algo que realmente não é testado.


55
Por favor, não use o termo "TDD" se não é isso que você está fazendo. Você está fazendo testes de unidade, não TDD. Se você estivesse usando TDD, nunca teria uma pergunta como "como testar um método". O teste existiria primeiro e, em seguida, a pergunta seria "como fazer esse teste passar?" Mas se você estivesse fazendo TDD, seu código seria escrito para o teste (e não o contrário), e você acabaria basicamente respondendo sua própria pergunta. Seu código seria formatado de maneira diferente como resultado do TDD, e esse problema nunca ocorreria. Apenas esclarecendo.
Suamere 30/05

Respostas:


151

Se um método não retornar nada, é um dos seguintes

  • imperativo - Você está pedindo ao objeto que faça algo consigo mesmo.
  • informativo - basta notificar alguém que algo aconteceu (sem esperar ação ou resposta), respectivamente.

Métodos imperativos - você pode verificar se a tarefa foi realmente executada. Verifique se a mudança de estado realmente ocorreu. por exemplo

void DeductFromBalance( dAmount ) 

pode ser testado verificando se o saldo que lança esta mensagem é realmente menor que o valor inicial por dAmount

Métodos informativos - são raros como um membro da interface pública do objeto ... portanto, normalmente não são testados em unidade. No entanto, se necessário, você pode verificar se o tratamento a ser feito em uma notificação ocorre. por exemplo

void OnAccountDebit( dAmount )  // emails account holder with info

pode ser testado verificando se o email está sendo enviado

Poste mais detalhes sobre seu método real e as pessoas poderão responder melhor.
Atualização : seu método está fazendo duas coisas. Na verdade, eu o dividiria em dois métodos que agora podem ser testados independentemente.

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

A seqüência [] pode ser facilmente verificada, fornecendo ao primeiro método um arquivo fictício e as seqüências esperadas. O segundo é um pouco complicado. Você pode usar um Mock (google ou fluxo de pilha de pesquisa em estruturas de simulação) para imitar o banco de dados ou acessar o banco de dados real e verificar se as cadeias foram inseridas no local correto. Confira esta lista de livros bons ... Eu recomendo o Teste de Unidade Pragmática, se você estiver em crise.
No código, seria usado como

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );

1
ei gishu, boa resposta. O exemplo que você deu ... não são mais testes de integração ...? e se assim for, a questão permanece: como alguém realmente testa métodos de vácuo ... talvez seja impossível?
andy

2
@andy - depende da sua definição de 'testes de integração'. Os métodos imperativos geralmente mudam de estado, para que você possa ser verificado por um teste de unidade que interroga o estado do objeto. Os métodos informativos podem ser verificados por um teste de unidade que é conectado a um ouvinte / colaborador simulado, para garantir que o sujeito do teste emita a notificação correta. Penso que ambos podem ser razoavelmente testados através de testes de unidade.
Gishu 12/07/10

@ o banco de dados pode ser simulado / separado por uma interface de acessador, permitindo que a ação seja testada pelos dados passados ​​para o objeto simulado.
Peter Geiger

62

Teste seus efeitos colaterais. Isso inclui:

  • Isso gera alguma exceção? (Se for o caso, verifique se sim. Se não, tente alguns casos extremos que talvez não sejam cuidadosos - argumentos nulos são a coisa mais óbvia.)
  • Ele joga bem com seus parâmetros? (Se eles são mutáveis, eles os modificam quando não deveriam e vice-versa?)
  • Isso tem o efeito correto no estado do objeto / tipo no qual você está chamando?

Claro, há um limite para o quanto você pode testar. Você geralmente não pode testar com todas as entradas possíveis, por exemplo. Teste pragmaticamente - o suficiente para dar a você a confiança de que seu código foi projetado adequadamente e implementado corretamente e o suficiente para atuar como documentação suplementar para o que um chamador pode esperar.


31

Como sempre: teste o que o método deve fazer!

Deve mudar o estado global (uuh, cheiro de código!) Em algum lugar?

Deve chamar em uma interface?

Deveria lançar uma exceção quando chamado com os parâmetros errados?

Não deveria lançar exceção quando chamado com os parâmetros corretos?

Deveria ...?


11

Tipos de retorno nulo / sub-rotinas são notícias antigas. Não faço um tipo de retorno nulo (a menos que eu esteja sendo extremamente preguiçoso) há 8 anos (desde o momento desta resposta, um pouco antes da pergunta).

Em vez de um método como:

public void SendEmailToCustomer()

Crie um método que siga o paradigma int.TryParse () da Microsoft:

public bool TrySendEmailToCustomer()

Talvez não exista nenhuma informação que seu método precise retornar para uso a longo prazo, mas retornar o estado do método depois que ele executa seu trabalho é uma grande utilidade para o chamador.

Além disso, bool não é o único tipo de estado. Há várias vezes em que uma sub-rotina feita anteriormente pode realmente retornar três ou mais estados diferentes (bom, normal, ruim etc.). Nesses casos, você usaria apenas

public StateEnum TrySendEmailToCustomer()

No entanto, embora o Try-Paradigm responda de alguma forma a essa pergunta sobre como testar um retorno nulo, há outras considerações também. Por exemplo, durante / após um ciclo "TDD", você estaria "Refatorando" e notaria que está fazendo duas coisas com seu método ... quebrando, assim, o "Princípio de responsabilidade única". Portanto, isso deve ser tratado primeiro. Segundo, você pode ter identificado uma dependência ... está tocando em Dados "persistentes".

Se você estiver executando o material de acesso a dados no método em questão, precisará refatorar para uma arquitetura de camada n ou camada. Mas podemos assumir que quando você diz "As cadeias são inseridas em um banco de dados", na verdade quer dizer que está chamando uma camada de lógica de negócios ou algo assim. Sim, vamos assumir isso.

Quando seu objeto é instanciado, agora você entende que ele possui dependências. É quando você precisa decidir se fará Injeção de Dependência no Objeto ou no Método. Isso significa que seu construtor ou o método em questão precisa de um novo parâmetro:

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

Agora que você pode aceitar uma interface do seu objeto de camada de negócios / dados, pode simulá-la durante testes de unidade e não tem dependências ou medo de testes de integração "acidentais".

Então, no seu código ativo, você passa um IBusinessDataEtcobjeto REAL . Mas em seu teste de unidade, você passa um IBusinessDataEtcobjeto MOCK . Nesse Mock, você pode incluir as propriedades que não são da interface, int XMethodWasCalledCountou algo cujos estados são atualizados quando os métodos da interface são chamados.

Portanto, seu teste de unidade passará por seu (s) método (s) em questão, execute qualquer lógica que ele tenha e chame um ou dois ou um conjunto selecionado de métodos em seu IBusinessDataEtcobjeto. Quando você faz suas afirmações no final do seu teste de unidade, você tem algumas coisas para testar agora.

  1. O estado da "sub-rotina", que agora é um método Try-Paradigm.
  2. O estado do seu IBusinessDataEtcobjeto simulado .

Para obter mais informações sobre as idéias de injeção de dependência no nível da construção ... no que se refere ao teste de unidade ... consulte os padrões de design do Builder. Ele adiciona mais uma interface e classe para cada interface / classe atual que você possui, mas elas são muito pequenas e fornecem enormes aumentos de funcionalidade para um melhor teste de unidade.


O primeiro pedaço desta ótima resposta serve como um conselho geral incrível para todos os programadores iniciantes / intermediários.
Pimbrouwers 13/0918

não deveria ser algo assim public void sendEmailToCustomer() throws UndeliveredMailException?
A.Emad 13/06

1
@ A.Emad Boa pergunta, mas não. Controlar o fluxo do seu código confiando em lançar exceções é uma prática recomendada há muito conhecida. Embora, em voidmétodos, especialmente em linguagens orientadas a objetos, tenha sido a única opção real. A alternativa mais antiga da Microsoft é o Try-Paradigm que discuto, e paradigmas de estilo funcional, como Monads / Maybes. Assim, os comandos (no CQS) ainda podem retornar informações valiosas sobre o status, em vez de confiar no lançamento, que é semelhante a um GOTO(que sabemos que é ruim). jogar (e ir) é lento, difícil de depurar e não é uma boa prática.
Suamere 16/06

Obrigado por esclarecer isso. É específico do C # ou é uma prática ruim, em geral, lançar exceções mesmo em linguagens como Java e C ++?
A.Emad 18/06

9

Tente o seguinte:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}

1
Isso não deve ser necessário, mas pode ser feito como tal
Nathan Alard

Bem-vindo ao StackOverflow! Por favor, considere adicionar algumas explicações ao seu código. Obrigado.
Aurasphere

2
O ExpectedAttributefoi projetado para tornar esse teste mais claramente.
Martin Liversage

8

Você pode até tentar desta maneira:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}

1
Esta é a maneira mais simples, suponho.
Navin Pandit

5

isso terá algum efeito em um objeto ... consulta o resultado do efeito. Se não tem efeito visível, não vale a pena testar a unidade!


4

Presumivelmente, o método faz alguma coisa e não volta simplesmente?

Supondo que este seja o caso, então:

  1. Se ele modificar o estado de seu objeto proprietário, você deverá testar se o estado foi alterado corretamente.
  2. Se ele receber algum objeto como parâmetro e modificá-lo, você deverá testar se o objeto foi modificado corretamente.
  3. Se lançar exceções em certos casos, teste se essas exceções foram lançadas corretamente.
  4. Se o seu comportamento variar com base no estado de seu próprio objeto, ou em algum outro objeto, pré-ajuste o estado e teste o método possui a forma correta através de um dos três métodos de teste acima).

Se você nos informar o que o método faz, eu poderia ser mais específico.


3

Use o Rhino Mocks para definir quais chamadas, ações e exceções podem ser esperadas. Supondo que você possa zombar ou remover partes do seu método. Difícil de saber sem conhecer algumas especificidades aqui sobre o método, ou mesmo contexto.


1
A resposta para como fazer o teste de unidade nunca deve ser obter uma ferramenta de terceiros que faça isso por você. No entanto, quando uma pessoa sabe como fazer o teste de unidade, é aceitável o uso de uma ferramenta de terceiros para facilitar a tarefa.
Suamere 30/05

2

Depende do que está fazendo. Se houver parâmetros, passe as simulações que você poderá perguntar posteriormente se elas foram chamadas com o conjunto correto de parâmetros.


Concordado - verificar o comportamento das zombarias testando o método seria uma maneira.
Jeff Schumacher

0

Qualquer que seja a instância que você esteja usando para chamar o método void, você pode simplesmente usar,Verfiy

Por exemplo:

No meu caso, _Logé a instância e LogMessageé o método a ser testado:

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

Os Verifylançamentos são uma exceção devido à falha do método que o teste falharia?

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.