Quando o teste através do método público começa a cheirar mal (exemplo)

No artigo sobre o teste de métodos públicos, eu toquei no teste de unidade da lógica de classe privada. Eu acho que valeria a pena refazer a tese, pois a maioria, na minha opinião, percebeu que estávamos falando sobre o teste de métodos privados, embora fosse sobre lógica privada. Neste artigo, quero ilustrar a tese principal com um exemplo prático. Sob kat um exemplo com um pouco de análise.

Exemplo de estoque


Para ilustrar o problema, vim com um exemplo. Sua idéia é retirada de um projeto real. Idealmente, como alguém pode notar, a classe teria que ser escrita de maneira diferente. Mas agora ninguém vai reescrevê-lo (ou refatorar), porque isso custará muito tempo para editar e testar o que funciona. Portanto, não será aprovado. Nesse caso, você precisa alterar o código e as alterações devem estar corretas. E então, aqui está o código. A lógica importante está concentrada no método ProcessMessage. Objetivo: Introduzir um novo sinalizador da lógica de negócios no processamento de mensagens. Este sinalizador possui uma lógica não trivial.

Como você implementará e testará a bandeira?

using System; using System.Threading.Tasks; using System.Threading; using System.Messaging; namespace PublicMethodNottrivialSample { public class MessageProcessorService { private object _lockObject = new object(); private CancellationTokenSource _cancellationSource; private Action _receiveMessage; private int _listenerThreads = 5; public void Start() { lock (_lockObject) { _cancellationSource = new CancellationTokenSource(); Task.Factory.StartNew(() => InitializeReceiveLoop(_cancellationSource.Token), _cancellationSource.Token); } } private void InitializeReceiveLoop(CancellationToken cancellationToken) { _receiveMessage = () => { while (!cancellationToken.IsCancellationRequested) { using (MessageQueueTransaction msgTx = new MessageQueueTransaction()) { try { msgTx.Begin(); //   MessageQueue queue = new MessageQueue(); Message message = queue.Receive(msgTx); //    ,    if (!cancellationToken.IsCancellationRequested) { ProcessMessage(message); } msgTx.Commit(); } catch (Exception ex) { // some logging } } } }; for (int n = 0; n < _listenerThreads; n++) { StartProcessingLoop(cancellationToken); } } private void ProcessMessage(Message message) { //   ,     } private void StartProcessingLoop(CancellationToken cancellationToken) { Task.Factory.StartNew(_receiveMessage, cancellationToken); } } } 

A classe mostra que o único método público é Start (). Você pode testá-lo se alterar a assinatura, mas, neste caso, a interface pública será alterada. Além disso, você precisará alterar vários métodos para retornar os threads em execução e aguardar a conclusão deles no teste.

Mas, como lembramos, o requisito dizia respeito apenas à implementação do sinalizador no processo de processamento de uma mensagem e não implicava uma alteração na operação do mecanismo de recebimento de mensagens. Portanto, não importa quem faça as alterações, eu esperaria que apenas um método fosse corrigido e o teste de unidade se relacionaria apenas à lógica mutável. Para isso, é difícil permanecer dentro da estrutura do princípio: o teste não será trivial, o que implicará uma recusa em escrevê-lo ou um código complicado. É assim que o teste começa através do método público. E então alguém escreverá emocionalmente: "TDD é muito tempo", "O cliente não paga" ou "Os testes não funcionam bem".

Em geral, é necessário testar esse código, mas não com testes de unidade, mas com testes de integração.

"Vocês damas, ou vão"


Obviamente, é necessário escrever um teste de unidade para a lógica alterada. Acredito que o dilema, neste caso, é escolher a maneira mais barata de escrever um teste, em vez de um código útil. Aqui, quero dizer o que você faz: refatoração para o método público ou outra solução - você fará isso para escrever um teste e não resolver o requisito da tarefa do cliente. Nesse caso, é aconselhável avaliar o custo e o efeito. Além da solução acima com refatoração, existem várias soluções alternativas. Trago tudo, abaixo discutiremos os prós e os contras:

  1. método privado pode ser testado
  2. o método pode ser tornado público
  3. pode ser feito interno
  4. pode ser puxado para uma classe separada como um método público

Se compararmos os três primeiros métodos com a solução através do método público, mas todos eles exigem menos custos de mão-de-obra (com o privado não é um fato). Além disso, todos eles têm a mesma solução, com pequenas diferenças estilísticas. Porque o resultado será obtido de qualquer forma, portanto, na minha opinião, a escolha a favor de uma dessas soluções não deve se basear nas capacidades técnicas da linguagem, mas no que você deseja mostrar a outros desenvolvedores:

  1. se o método puder ser executado de qualquer lugar da solução e fizer parte do comportamento da classe, torne-o público e puxe-o para a interface da classe;
  2. se o método puder ser usado de qualquer outra classe, mas somente dentro deste assembly, faça-o interno ;
  3. se o método puder ser usado apenas dentro da classe principal, onde pode ser chamado de qualquer outro método, torne-o protegido interno .

Métodos internos podem ser tornados visíveis para o assembly de teste usando o atributo InternalsVisibleTo e chamado como de costume. Essa abordagem facilita a escrita de testes e o resultado será o mesmo.

Processo de Teste


Vamos voltar à tarefa. Seguindo a abordagem acima, eu faria algumas alterações:

  • tornaria ProcessMessage público
  • criaria um novo método interno protegido para lógica de sinalizador (por exemplo, GetFlagValue)
  • escreveria testes para GetFlagValue para todos os casos
  • Eu escreveria um teste para ProcessMessage para garantir que GetFlagValue seja usado corretamente: os parâmetros são passados ​​corretamente e realmente são usados

Esclarecerei que não escreveria testes de unidade para todos os casos de uso de GetFlagValue no método ProcessMessage, desde que eu tenha testado esses casos em testes de unidade GetFlagValue. No caso de casos descobertos, eles devem ser adicionados. Nesse caso, a principal abordagem permanece:

  • todos os casos são cobertos pelos testes de unidade GetFlagValue
  • Os testes de unidade ProcessMessage verificam se o método flag é usado corretamente

Acredito que, de acordo com isso, você pode escrever apenas um teste de unidade para ProcessMessage e vários para GetFlagValue.

Algo assim. Suas opiniões

Source: https://habr.com/ru/post/pt420567/


All Articles