Logo

dev-resources.site

for different kinds of informations.

Desafios Comuns na Escrita de Testes Automatizados: Rumo à Clareza e Padronização - Parte 3

Published at
2/27/2024
Categories
singleresponsibility
bdd
tdd
webdev
Author
ricardobsilva
Author
13 person written this
ricardobsilva
open
Desafios Comuns na Escrita de Testes Automatizados: Rumo à Clareza e Padronização - Parte 3

Na parte dois, nós vimos as três etapas que compõem um teste automatizado: Preparação, execução e resultado.

Com isso em mente, vamos dedicar nossa terceira parte a responder às seguintes perguntas:

  • O que devo testar?
  • O que não devo testar?

O que devo testar?

Apesar de ser uma atividade técnica, o ato de testar é influenciado por alguns fatores subjetivos que podem dificultar a vida de uma pessoa na hora de definir o escopo que precisa ser testado. Cada membro pode ter sua própria interpretação do que é importante testar, e isso é um fator subjetivo. Determinadas partes do sistema têm mais criticidade que outras, e isso é outro fator subjetivo.

Antes de mais nada, o time precisa ter discussões claras que irão colocar todos na mesma página a respeito do que é importante testar, pois estabelecendo esse tipo de acordo, ficará mais fácil identificar quando determinada entrega (um PR, por exemplo) contém testes que fogem muito da estrutura dos demais testes existentes no projeto.

Eu costumo propor que os testes precisam usar como referência principal os requisitos da tarefa, levando em conta os cenários "felizes" e "não felizes", onde o caminho feliz é o percurso ideal de execução, seguindo todas as condições esperadas sem ocorrência de erros, contrastando com o caminho não feliz, que abrange situações indesejadas ou desvios do comportamento previsto.

Se essas informações não estão claras na descrição da tarefa, é importante mapeá-los (ao menos os iniciais, uma vez que dúvidas e requisitos não identificados antes poderão aparecer durante o processo de desenvolvimento).

Vamos considerar que a seguinte task foi criada no board do projeto:

Cadastro de Chamado no Sistema

História:
Como usuário do sistema, desejo poder cadastrar um chamado para reportar problemas ou solicitar suporte.

Critérios de Aceitação:

  1. Número Único de Identificação:
    • Após o cadastro bem-sucedido, com usuário informando título e descrião do chamado, o sistema deve gerar automaticamente um número único de identificação para o chamado.
    • Número único de identificação precisa conter letras maísculas e números

Cenários de Teste:

  • Caminho Feliz:
    • O sistema gera um número único de identificação para o chamado.
  • Caminho Não Feliz:
    • O usuário tenta cadastrar um chamado sem preencher todos os campos obrigatórios, resultando em uma mensagem de erro.

Vamos supor que estamos na etapa de construir um Service Object que aplicará essas regras descritas na tarefa. Um bom ponto de partida seria “transferir os requisitos” da tarefa para uma estrutura mais ou menos assim:

# ticket_creator_spec.rb

RSpec.describe TicketCreator do
  describe '#call' do
    context 'When title and description are filled in' do
      it 'generates a unique identification number with uppercase letters and numbers' do
        # etapas do caminho feliz ficarão aqui
      end
    end

    context 'When title is not filled in' do
      it 'raises an error' do
        #etapas do caminho não feliz ficarão aqui
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

O próximo passo é focar em escrever as etapas dos testes. Vamos começar pelo caminho feliz?

Podemos dar início através do setup do teste, sempre tentando montar essa etapa somente com o que é estritamente necessário. Nesse caso, seriam os dados de título e descrição.

# ticket_creator_spec.rb

RSpec.describe TicketCreator do
  describe '#call' do
    context 'When title and description are filled in' do
      it 'generates a unique identification number with uppercase letters and numbers' do
         valid_attributes = { 
                   title: 'Issue Title', 
                     description: 'Detailed description of the issue' 
                 }
      end
    end

    context 'When title is not filled in' do
      it 'raises an error' do
        #etapas do caminho não feliz ficarão aqui
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Como eu considero que isso é muito pouco para executar o teste, vou seguir com o segundo passo, que seria a execução, colocando em nosso teste a unidade de código testada.

# ticket_creator_spec.rb

RSpec.describe TicketCreator do
  describe '#call' do
    context 'When title and description are filled in' do
      it 'generates a unique identification number with uppercase letters and numbers' do
         valid_attributes = { 
                   title: 'Issue Title', 
                     description: 'Detailed description of the issue' 
                 }

                ticket = TicketCreator.new(valid_attributes).call
      end
    end

    context 'When title is not filled in' do
      it 'raises an error' do
        #etapas do caminho não feliz ficarão aqui
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Aqui já conseguimos ter duas escolhas:

  • Rodar o teste e receber o feedback de que a classe TicketCreator ainda não existe, mais ou menos assim:

    NameError:
      uninitialized constant TicketCreator
    
  • Partir para a terceira etapa, que seria determinar que após a execução da unidade de código (TicketCreator), um identification_number precisa ter sido gerado

Se a ideia for seguir pelo segundo caminho, nosso teste poderia ficar mais ou menos assim:

# ticket_creator_spec.rb

RSpec.describe TicketCreator do
  describe '#call' do
    context 'When title and description are filled in' do
      it 'generates a unique identification number with uppercase letters and numbers' do
         valid_attributes = { 
                   title: 'Issue Title', 
                     description: 'Detailed description of the issue' 
                 }

                ticket = TicketCreator.new(valid_attributes).call

                expect(ticket.identification_number).not_to be_nil
                expect(ticket.identification_number).to match(/\A[A-Z0-9]+\z/)
      end
    end

    context 'When title is not filled in' do
      it 'raises an error' do
        #etapas do caminho não feliz ficarão aqui
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Na medida em que formos rodando os testes, coletando os feedbacks e implementando o código até que os testes passem
(ciclo Red-Green-Refactor), no final teremos uma classe mais ou menos assim:

# app/services/ticket_creator.rb

class TicketCreator
  attr_reader :title, :description

  def initialize(attributes = {})
    @title = attributes[:title]
    @description = attributes[:description]
  end

  def call
    ticket = Ticket.create(
      title: title,
      description: description,
      identification_number: generate_identification_number
    )

    ticket
  end

  private

  def generate_identification_number
    SecureRandom.hex(6).upcase
  end
end
Enter fullscreen mode Exit fullscreen mode

Uma vez que o caminho feliz foi implementado, podemos partir para a implementação do cenário não feliz com a confiança de que, caso façamos besteira e o caminho feliz pare de funcionar, seremos devidamente advertidos pelos testes.

Primeiro os testes

# ticket_creator_spec.rb

RSpec.describe TicketCreator do
  describe '#call' do
    context 'when title and description are filled in' do
      it 'generates a unique identification number with uppercase letters and numbers' do
         valid_attributes = { 
                   title: 'Issue Title', 
                     description: 'Detailed description of the issue' 
                 }

                ticket = TicketCreator.new(valid_attributes).call

                expect(ticket.identification_number).not_to be_nil
                expect(ticket.identification_number).to match(/\A[A-Z0-9]+\z/)
      end
    end

    context 'when title is not filled in' do
      it 'raises an error' do
        invalid_attributes = { 
          title: '', 
          description: 'Detailed description of the issue' 
        }

        expect { TicketCreator.new(invalid_attributes).call }.to raise_error(ValidationError, 'Title can\'t be blank')
      end
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

Agora a implementação

# app/services/ticket_creator.rb

class TicketCreator
  attr_reader :title, :description

  def initialize(attributes = {})
    @title = attributes[:title]
    @description = attributes[:description]
  end

  def call
    validate_attributes

    ticket = Ticket.create(
      title: title,
      description: description,
      identification_number: generate_identification_number
    )

    ticket
  end

  private

  def validate_attributes
    raise ValidationError, 'Title can\'t be blank' if title.blank?
  end

  def generate_identification_number
    SecureRandom.hex(6).upcase
  end
end

Enter fullscreen mode Exit fullscreen mode

Obviamente, o código final da classe irá variar de acordo com o gosto da pessoa/time que estiver desenvolvendo, mas o importante aqui é que, desde o início, o foco sempre foi atender aos requisitos determinados na tarefa e avançar em direção a um código legível e organizado, uma prova de que os testes têm influência direta no design do código implementado, ao mesmo tempo em que diminui os riscos de se gerar novos bugs durante o processo.

Outro ponto que vale destacar é a relevância dos dados de entrada das classes em relação aos dados de saída. Através dos dados de entrada, nós conseguimos ter total controle a respeito dos resultados esperados, baseado nos contextos definidos. É por isso que passar um título preenchido ou não foi o suficiente para simular o caminho feliz e o caminho não feliz.

O que não devo testar?

Existe um motivo para que o método call seja o único método levado em consideração nos testes: métodos privados são detalhes de implementação e não devem ser testados diretamente, ao mesmo passo em que, se uma classe tem somente um método público, isso será uma forma de garantir que ela assumirá somente uma responsabilidade (aqui não é sobre testes necessariamente, mas sobre o princípio de responsabilidade única)

O foco do teste precisa ser garantir que o comportamento externo da unidade de código (a classe TicketCreator) atenda aos requisitos especificados. E isso acontece através dos métodos públicos, que são de quem os usuários e outros componentes dependerão.

Testar métodos privados pode (e provavelmente vai) resultar em testes frágeis e poluídos, uma vez que estarão sujeitos a mudanças internas na implementação que não afetarão o comportamento externo da unidade de código testada, ou seja: a saída do método público. Isso quer dizer que mudanças nos métodos privados poderão ou não causar quebra ou mudança no comportamento da classe, mas caso cause, os testes do método público já estarão cobrindo isso.

Se um método privado executa rotinas muito complexas, vale a pena extrair esse código para uma classe contendo seus próprios testes.

Testes simples, implementação simples… De complicado já basta a vida, não é verdade?

E assim finalizamos a parte três da nossa série.

Nos vemos na parte quatro!

bdd Article's
30 articles in total
Favicon
Strategies to simplify your BDD step definitions
Favicon
Announcing "The Cucumber Field Guide: Practical Examples for Automated Software Testing"
Favicon
Test Automation with BDD, Specflow and Selenium
Favicon
Understanding the BDD, Gherkin Language & Main Rules for BDD UI Scenarios
Favicon
Behavior-Driven Development (BDD) Explained: How It Enhances Collaboration and Testing
Favicon
Cucumber: Bridging the Gap Between Tech and Non-Tech in Testing
Favicon
What's testing in software development?
Favicon
Mastering Cucumber Framework: A Comprehensive Guide to Behavior-Driven Development
Favicon
The Hidden Dangers of Programming: A Lesson From Childhood 🛠️💻
Favicon
Solving a problem of duplicate steps in Cucumber BDD testing
Favicon
Implementing BDD with `pytest-bdd` and `pytest-playwright` for Web Testing
Favicon
Understanding Behavior Driven Development (BDD)
Favicon
O que é BDD e quando você deve considerar
Favicon
BDD Testing in .NET8
Favicon
BDD Testing: Is the Juice Worth the Squeeze?
Favicon
The Crucial Role of Software Quality Assurance Engineering in Ensuring Product Reliability
Favicon
Guide to Integrating Cypress and Cucumber with Angular
Favicon
Concise Gherkin - How brevity improves BDD scenarios
Favicon
Desafios Comuns na Escrita de Testes Automatizados: Rumo à Clareza e Padronização - Parte 3
Favicon
Desafios Comuns na Escrita de Testes Automatizados: Rumo à Clareza e Padronização - Parte 2
Favicon
Desafios Comuns na Escrita de Testes Automatizados: Rumo à Clareza e Padronização - Parte 1
Favicon
Mastering Behat Testing: A Comprehensive Guide for Implementing BDD in PHP Projects
Favicon
Why do BDD Testing and Example tools?
Favicon
How to take advantage of BDD Framework in Test Automation
Favicon
What is BDD Framework? - A Detailed Guide
Favicon
BDD Testing with Cucumber-js
Favicon
Research Inquiry!
Favicon
📚 Understanding the Difference Between Data Tables and Scenario Outlines in BDD Framework 💡
Favicon
Cooeee World! Passenger 🗞️🐦
Favicon
Behavior Driven Development - Definitive Guide

Featured ones: