É uma boa idéia ter métodos de teste separados para cada etapa?

10

Estou testando uma API REST. Digamos que ele retorne uma estrutura JSON. Qual é a melhor abordagem para testar o servidor? Cada etapa do teste só pode ter sucesso se todas as anteriores tiverem sido bem-sucedidas.

Estrutura A: teste tudo de uma vez

- Test method 1:
    - make server request
    - assert http response code was 200
    - assert returned file is not empty
    - assert returned file has valid JSON syntax
    - assert returned JSON contains key X

Esta parece ser a melhor solução.

Vantagens:

  • Apenas uma solicitação do servidor
  • Estou testando o comportamento como um todo "O servidor retorna um JSON com a chave X?"

Estrutura B: adicione gradualmente declarações a cada teste

 - Test method 1:
     - make server request
     - assert http response code was 200
 - Test method 2:
     - make server request
     - assert returned file is not empty
 - Test method 3:
     - make server request
     - assert returned file has valid JSON syntax
 - Test method 4:
     - make server request
     - assert returned JSON contains key X

Foi assim que comecei a fazê-lo e fiquei convencido de que esse deveria ser o caminho a seguir, porque todo método testa apenas uma coisa e isso cria uma melhor separação. Mas agora penso que, como esses não são testes de unidade, minha separação não é apropriada e devo testar o comportamento como um todo.

Estrutura C: faça a solicitação uma vez e execute métodos de teste separados na resposta em cache

- make server request and cache it (allow read-only access)

 - Test method 1:
     - assert http response code was 200 on cached server request
 - Test method 2:
     - assert returned file is not empty on cached server request
 - Test method 3:
     - assert returned file has valid JSON syntax on cached server request
 - Test method 4:
     - assert returned JSON contains key X on cached server request

Vantagens:

  • Nenhuma solicitação de servidor repetida (cara)
  • Ainda possui métodos de teste de afirmação única

Qual é a estrutura de teste mais sensata a ser usada?

mrplow
fonte
Pare de alterar sua pergunta posteriormente de uma maneira que invalide as respostas existentes! Obrigado.
Doc Brown
Desculpe por lhe incomodar, mas você propõe fazer o contrário?
Mrplow
Primeiro, pense duas vezes se você realmente precisar alterar sua pergunta dessa maneira. Se você realmente acha que deve adicionar algo que invalide algumas respostas, informe todos os autores dessas respostas, deixando um comentário abaixo da resposta, perguntando se eles desejam alterar ou adicionar algo no texto.
Doc Brown
2
Na verdade, assumi que os autores das respostas são notificados se a pergunta for alterada. É por isso que eu não queria enviar comentários de spam com declarações fora do tópico. Notificarei os autores no futuro. E obrigado por fornecer uma resposta para minha pergunta.
precisa saber é o seguinte

Respostas:

3

As melhores práticas sempre têm um objetivo, uma razão por trás delas. É sempre uma boa ideia considerar esses motivos em seu design - especialmente quando você está tentando decidir como e quão difícil seguir essas práticas recomendadas.

Nesse caso, o principal raciocínio por trás de tornar cada teste de teste uma coisa única é que, se a primeira coisa falhar, a segunda não será testada. Como muitos formadores de opinião parecem encontrar mérito em dividir tudo ao mínimo possível e envolver o máximo possível de inchaços, isso deu origem à ideia de que todo teste deveria conter uma única afirmação.

Não siga isso cegamente. Mesmo que todo teste teste uma coisa, você ainda deve pensar em quão grande ou pequeno cada "coisa" deve ser; para isso, lembre-se de por que deseja que cada teste teste uma coisa - para garantir um bug na primeira coisa não está deixando a segunda coisa não testada.

Então, você precisa se perguntar: "Eu realmente preciso dessa garantia aqui?"

Digamos que haja um erro no primeiro caso de teste - o código de resposta HTTP não é 200. Então você começa a invadir o código, descubra por que você não obteve o código de resposta que deveria ter e corrija o problema. E agora?

  • Se você executar o teste manualmente novamente, para verificar se sua correção resolveu o problema, você deve executar qualquer outro problema oculto pela primeira falha.
  • Se você não executá-lo manualmente (talvez porque demore muito?), E apenas pressionar sua correção aguardando o servidor de testes automatizados executar tudo, convém colocar afirmações diferentes em testes diferentes. Os ciclos neste caso são muito longos, portanto vale a pena fazer um esforço para descobrir o máximo de bugs em cada ciclo.

Há mais algumas coisas a considerar:

Dependências de asserções

Eu sei que os testes que você descreveu são apenas um exemplo, e seus testes reais provavelmente são mais complicados - então o que vou dizer pode não ser válido com tanta força nos testes reais, mas ainda pode ser um pouco eficaz para você pode querer considerar.

Se você possui um serviço REST (ou qualquer outro protocolo HTTP) que retorna respostas no formato JSON, normalmente escreve uma classe de cliente simples que permite usar os métodos REST como métodos regulares que retornam objetos regulares. Supondo que o cliente tenha testes separados para garantir que funcione, eu teria abandonado os três primeiros assertivos e ficado com apenas quatro!

Por quê?

  • A primeira declaração é redundante - a classe do cliente deve lançar uma exceção se o código de resposta HTTP não for 200.
  • A segunda afirmação é redundante - se a resposta estiver vazia, o objeto resultante será nulo ou alguma outra representação de um objeto vazio, e você não terá onde colocar a chave X.
  • A terceira afirmação é redundante - se o JSON for inválido, você receberá uma exceção ao tentar analisá-lo.

Portanto, você não precisa executar todos esses testes - basta executar o quarto teste e, se algum dos erros das três primeiras tentativas de detectar acontecer, o teste falhará com uma exceção apropriada antes mesmo de você obter a afirmação real.

Como você deseja receber os relatórios?

Digamos que você não receba emails de um servidor de teste, mas, em vez disso, o departamento de controle de qualidade executa os testes e o notifica de falhas nos testes.

Jack do controle de qualidade bate à sua porta. Ele diz que o primeiro método de teste falhou e o método REST retornou um código de resposta incorreto. Você agradece e começa a procurar a causa raiz.

Depois vem Jen do controle de qualidade e diz que o terceiro método de teste falhou - o método REST não retornou um JSON válido no corpo da resposta. Você diz a ela que já está analisando esse método e acredita que a mesma coisa que causou o retorno de um código de saída incorreto também causou o retorno de algo que não é um JSON válido e se parece mais com um rastreamento de pilha de exceção.

Você volta a trabalhar, mas Jim do controle de qualidade chega, dizendo que o quarto método de teste falhou e que não há chave X na resposta ...

Você não pode nem procurar o motivo, porque é difícil olhar o código quando você não tem uma tela de computador. Se Jim fosse rápido o suficiente, ele poderia ter esquecido a tempo ...

Os e-mails do servidor de teste são mais fáceis de descartar, mas ainda assim - você não gostaria de ser notificado apenas uma vez que algo está errado com o método de teste e verificar os logs de teste relevantes?

Idan Arye
fonte
3

Se você pode assumir com segurança que fazer uma solicitação de servidor com os mesmos parâmetros sempre se comportará da mesma forma, o método B é quase inútil - por que você deve chamar quatro vezes o mesmo método para obter os mesmos dados de resposta quatro vezes quando uma chamada é suficiente?

E se você não puder assumir isso com segurança e quiser fazer parte do teste, é melhor executar o teste A várias vezes.

A única situação hipotética que vejo onde B pode ter um benefício é quando sua estrutura de teste permite que apenas métodos explícitos sejam ativados e desativados, e você espera que seja necessário fazer isso nas etapas individuais do seu teste.

A alternativa C parece combinar A com o benefício que mencionei acima para B. Se sua estrutura de teste permite que seu código seja estruturado facilmente assim, sem muita sobrecarga acima de B, essa é uma abordagem viável. No entanto, isso adiciona alguma complexidade adicional a A, então eu a usaria apenas se quisesse ativar e desativar os testes individuais; caso contrário, aplique o princípio YAGNI e atue na solução mais simples (A).

TLDR: comece com A se tiver certeza de que sempre deseja que todos os assertivos sejam executados em um teste, refatorar para C se perceber que precisa ter um controle mais fácil de fora sobre os assertivos individuais.

Doc Brown
fonte
0

Como qualquer código, evite a otimização prematura. Primeiro, escreva seus testes para que sejam simples de ler e simples de manter. Quando os testes começarem a ficar muito lentos, otimize-os. No seu exemplo bastante simples, A e B serão fáceis de ler e manter, portanto escolha qual você quiser até que as coisas fiquem muito lentas (estrutura B) ou muito complicadas (estrutura A).

Se o servidor estiver sem estado, você poderá otimizar comparando a resposta real com a resposta esperada para verificar toda a mensagem de uma só vez. Obviamente, isso será à custa da legibilidade.

Se o servidor estiver cheio de estado e você precisar fazer várias chamadas lentas da API para colocar o servidor em um estado para o teste, use uma abordagem diferente ou seus testes poderão levar alguns minutos para serem executados. Por exemplo, você pode executar uma atualização do banco de dados para injetar dados em um banco de dados de teste, para poder obter rapidamente um objeto em um estado apropriado para teste. O teste é rápido e legível, mas mais difícil de manter. Como alternativa, você pode escrever uma fachada na frente da API, para que várias chamadas de API se tornem uma única API que corresponda mais de perto ao processo de negócios que você está testando.

Mattelli Helliwell
fonte
0

Os testes não devem compartilhar coisas - a partir do zero, você evita a influência de um teste em outro. Isso também permite executar testes em ordem aleatória.
Portanto, o caminho C não deve ser aceito.


Ao escrever qualquer código (ou talvez até criar qualquer outra coisa), sempre se pergunte: "por que existe tal prática?"
Por que dizemos que deve haver testes diferentes para tudo?

Existem dois casos em que você precisa disso:

  1. quando você não pode confiar na "cada etapa do teste só pode ter sucesso se todas as anteriores tiverem êxito"
  2. quando seus testes não tiverem mensagens descritivas de afirmação

Há duas razões pelas quais você enfrenta esses casos:

  1. "cada etapa do teste só pode ser bem-sucedida se todas as anteriores foram bem-sucedidas" não é realmente aplicável ao recurso do seu produto
  2. você não tem conhecimento suficiente do produto devido à falta de experiência ou tempo ou à grande complexidade do produto

Se você por algum motivo não pode declarar pelo menos uma dessas razões para ter lugar apenas cegamente tomar a estrutura B .


Caso contrário (eu espero que você chegar aqui) que você escolher A .


Você também pode fazer esta pergunta no site Software Quality Assurance & Testing Stackexchange.

Nakilon
fonte