Agile Brazil 2012: Boas Práticas de Teste Automatizado

Esse ano tive o prazer de falar para uma plateia gigantesca sobre algo do dia a dia, testes automatizado. O Agile Brazil é o maior evento da cultura ágil da América Latina e uma das maiores conferências sobre agilidade do mundo. Um palco onde figuras como Martin Fowler (ThoughtWorks), Philippe Kruchten (Rational Software), Klaus Wuestefeld (sneer.me), Jim Highsmith (ThoughtWorks), Joshua Kerievsky (Refactoring to Patterns) e tantos outros compartilharam suas ideias. Esse ano, outros nomes marcaram presença como Neil Ford (ThoughtWorks), James Shore (The Art of Agile Development) e Khawaja Shams (NASA), entre outros tantos grandes nomes do Agile. Sem falar na galera presente na comunidade de teste brasileira como o Mauricio Aniche (Caelum) e Jorge Diz (MAPS S/A).

Agile Brazil 2012

A minha palestra em par com o Carlos Palhares (ThoughtWorks) falou sobre testes automatizados, e tentamos guiar a palestra para mostrar situações ou desafios que encontramos durante o dia a dia quando automatizamos testes, os erros comuns que cometemos ao tentar resolver esses desafios e algumas técnicas que podemos usar para evitar os problemas desses erros. Para quem acompanha o blog, foi uma abordagem parecida com o post “Penso, logo automatizo“.

Importante: Não estamos em momento algum propondo uma “bala de prata” para nenhum dos problemas que serão apresentados aqui, mas uma solução mais elegante para tratar de alguns problemas comuns que encontramos em códigos por aí.

Cada uma das implementações que tentamos evitar aqui pode ser útil em alguma situação específica ou mesmo as soluções que recomendamos podem ser um problema em dezenas de outras situações. Cada caso deve ser estudado separadamente e a decisão deve ser tomada por alguém com total contexto sobre todo o ambiente do problema. Entretanto, os casos apresentados aqui representam experiências positivas e negativas dos autores durante algum projeto real. 

A apresentação está disponível no slideshare e aqui vão algumas notas sobre ela:

Porque automatizar e porque falar sobre isso em um evento de Agilidade?

Automatizar testes ou qualquer outro tipo de tarefa é uma maneira de economizar tempo. Acredito já ter falado bastante sobre a questão do tempo e custo no post “Penso, logo automatizo“, mas não custa lembrar que em projetos ágeis, diferente de boa parte dos projetos cascata ou modelo V, o teste acontece desde o início do projeto (teste completo, não só planejamento e elaboração), logo lá pela terceira ou quarta iteração (ou sprint) a equipe já não vai conseguir executar todos os testes de regressão e ao mesmo tempo executar novos testes. Uma maneira de resolver esse problema é criando uma suíte de testes automatizados, que cubram as releases e iterações anteriores, permitindo que a equipe foque na iteração atual.

Outro motivo importante para isso é manter os testes como uma extensão do aceite do cliente para as histórias de usuário e para os requisitos não funcionais. Uma vez que esses testes foram aceitos e continuam a executar, podemos dormir tranquilos após um longo dia de trabalho, pois sabemos que reduzimos muito o risco de mudar algo que já havia sido aceito pelo cliente.

Record and Replay

Representação do Selenium IDE gravando um script baseado em execução manual de testes em uma página Web

O Record and Replay é o modelo usado por algumas ferramentas como o Selenium IDE para gravar o código de testes enquanto o desenvolvedor ou testador executa o teste de maneira manual. O Selenium IDE por exemplo, tem a sua própria “linguagem” e todos os testes podem ser mantidos na própria IDE, mas ele também exporta para selenium e selenium-webdriver em várias linguagens, entre elas Ruby e Java.

O record and replay foi elaborado por alguém com uma intenção muito boa, ajudar a desenvolver testes de forma mais rápida, por pessoas sem domínio da ferramenta ou linguagem de programação e com feedback rápido da construção, o que se mostra muito eficiente quando precisamos executar um único teste de algo que já está pronto e jogar o script fora.

O problema disso é que nenhuma ferramenta tem o potencial criativo e o bom senso que desenvolvedores e testadores tem, e ao longo do tempo, alguns detalhes de codificação fazem com que essas ferramentas causem inúmeros problemas a um projeto sob integração continua e é sobre alguns desses problemas que vamos falar agora.

Em primeiro lugar, vamos ver um pedaço de código parecido com o que uma ferramenta dessa pode gerar:

require 'watir-webdriver'

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => 'q').set('Automação Rocks!')
browser.button(:name => 'btnG').click
sleep 1
if browser.text.include? 'The Bug Bang Theory 2.0'
puts 'Sucess'
else
puts 'fail'
end
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => 'q').set('Camilo Ribeiro')
browser.button(:name => 'btnG').click
sleep 1
if browser.text.include? 'The Bug Bang Theory 2.0'
puts 'Sucess'
else
puts 'fail'
end
browser.close

Como comentado no post “Penso, logo automatizo“, esse código tem muitos e diferentes smells e práticas não muito saudáveis, entre eles bare sleep, dados repetidos, comparação não determinística, controle sobre o browser, etc. Ao longo desse post vamos falar sobre esses problemas e como evitá-los, começando pelo bare sleep.

Bare Sleep

O bare sleep ou sleep é um método que quanto chamado pausa a execução do teste por uma quantidade determinada de tempo. O bare sleep é comumente utilizado em páginas com requisições Ajax ou com execução de javascript, quando o teste é mais rápido do que a aplicação, como no exemplo que vamos usar a seguir:

Exemplo de site assíncrono por javascript

1 – Vamos a uma página web, e nessa página temos um botão para logar no Facebook;

2 – Clicamos nesse botão para efetuar login e ele apresenta um formulário de login e senha

3 – Preenchemos esse formulário e postamos a nossa credencial

Então estamos logados.

Vamos supor que tenhamos gravado um script para durante esse teste, e agora estamos executando esse teste automatizado, mas ele falha na primeira execução. Como acabamos de gravar esse teste e sabemos que o sistema está funcionando, executamos o teste novamente e então ele passa… testamos novamente só para desencargo de consciência e o teste falha novamente… :(

A esse tipo de teste damos alguns nomes, entre eles flaky ou flakey tests, testes intermitentes ou testes não determinísticos. Um post muito bom sobre esse assunto é o post “Avoiding non-determinism in tests” do Martin Fowler além de ter uma palestra muito boa falando sobre alguns dos exemplos que temos aqui.

Essa intermitência nesse tipo de teste acontece porque eventualmente o sistema reponde a mais rápido do que o teste e em outras ocasiões ele responde mais devagar. Nessas requisições que o sistema responde depois do teste, o teste não encontra o elemento e nos retorna um erro parecido com o erro abaixo:

#pseudo-code
click_login_button
fill_user “Mario”
Erro: Campo “user” não encontrado

Para evitar esse problema, a solução mais simples é usar um bare sleep, que vai forçar o teste a pausar por algum tempo, evitando que o teste seja mais rápido do que a aplicação, algo parecido com isso:

#pseudo-code
click_login_button
sleep 3000
fill_user “Mario”

O problema dessa abordagem é que se colocamos um tempo muito pequeno, o teste continua intermitente (falhando e tendo sucesso eventualmente) e se colocamos um tempo mais longo (por exemplo o timeout da aplicação), os nossos scripts vão ficando cada vez mais lentos, e no final, quando tivermos centenas de testes com alguns segundos de espera para seus cenários, o nosso teste vai deixar de prover feedback rápido.

Para evitar esse problemas podemos utilizar algumas técnicas como o Pooling e implicit Wait Time.

Pooling e Implicit Wait Time

O pooling é basicamente uma repetição de sleeps muito pequenos, onde sempre antes de um desses sleeps pequenos, o teste verifica se a condição para sair do sleep já foi atendida. Além disso temos um valor máximo que quando atingido nos retorna o erro correto.

O código é mais ou menos esse:

#pseudo-code
click_login_button
TIMEOUT.times do
  break if user_field.present?
  sleep 1
end
fill_user “Mario”

O código acima repete um sleep de um milissegundo até alcançar o tempo máximo da aplicação ou até que o elemento ou serviço que vamos usar fique disponível. Caso o elemento que esperamos fique disponível, ele sai da nossa repetição e acessa o elemento continuando o teste, caso o tempo máximo seja alcançado, podemos levantar uma exceção de tempo de resposta.

O Implicit Wait Time é um pooling automático que algumas ferramentas como o Selenium tem nativamente. Com ele, ao invés de escrever poolings no nosso código de teste, nos só informamos qual é o nosso tempo limite da nossa app para o framework que estamos usando e ele se encarrega de executar poolings para cada um dos elementos que tentarmos acessar.

No caso do selenium podemos ver o exemplo abaixo:

require 'selenium-webdriver'
driver = Selenium::WebDriver.for :firefox
driver.manage.timeouts.implicit_wait = TIMEOUT

click_login_button
element = driver.find_element(:id =ß ”user")

Desta maneira, o tempo de resposta é o mais rápido possível de acordo com a variação da aplicação, caso o tempo chegue ao limite o erro é disparado e não existe intermitência, ou seja, caso o teste falhe antes do time out podemos ter certeza que existe alguma coisa errada.

Lack of Isolation

Martin Fowler falando sobre Testes não determinísticos no Agile Connect 2011 (click para ver o vídeo)

Temos dezenas de testes no nosso build automatizado, e todos eles estão usando a implementação acima para mantê-los o mais rápido possível sem que eles sejam flaky nessas chamadas assíncronas, mas mesmo assim eles ficaram um pouco lentos e em uma reunião de projeto decidimos não deletar ou comentar testes, nem mesmo separar o build pois todos os testes são importantes atualmente, mas também não podemos ficar com o nosso build levando sete minutos, precisamos que ele execute em menos tempo.

Depois de pensar em algumas alternativas decidimos paralelizar os testes, ou seja, rodar testes ao mesmo tempo para economizar tempo.

Após criar as máquinas virtuais e preparar o selenium grid para distribuir a carga em servidores diferentes, executamos o nosso build e o teste “X” quebrou. Executamos o teste “X” sozinho e para nossa surpresa ele passou. Executamos novamente o build e esse mesmo teste “X” falhou e para nossa surpresa outros dois testes que tinham passado na outra execução também falharam, ou seja, ainda temos flaky tests :(

Para facilitar o entendimento cenário acima, vamos supor que temos os seguintes testes:

Teste 1 – Verifica Soma dos produtos cadastrados:
Produto A     =   100,00
Produto B     =   50,00
Soma  produtos = 150,00

Teste 2 – Cadastra um novo produto:
Cadastra Produto C    =   200,00
Verifica se existe um produto chamado “Produto C”

No exemplo acima, quando executamos os testes na seguinte ordem temos o seguinte resultado:

Ordem dos testes:

Produtos
Verificando soma dos produtos
        A soma dos produtos deve ser 150

Cadastrando novo produto
        Devo cadastrar o produto C

Todos os testes foram executados com sucesso. 2 testes, 0 pendentes, 0 erros

Mas se invertemos a ordem temos o seguinte erro:

Produtos

Cadastrando novo produto
        Devo cadastrar o produto C

Verificando soma dos produtos
        A soma dos produtos deve ser 150 recebido 450

ExpectationNotMetError: expected: 4
Experado = 150,00

O problema nesse caso é que um teste interfere no resultado do outro, ou seja, os testes possuem dependência. Existem duas formas de corrigir esse problema:

A primeira é fazendo que os testes sempre executem na ordem que precisamos, ou seja, no caso acima o teste 1 sempre vai executar antes do teste 2. Isso soluciona o problema, mas ainda assim não resolveria a nossa estratégia de jogar os testes em vários servidores, pois não saberíamos em qual servidor nossos testes estão rodando. Além disso, adicionaríamos uma complexidade muito grande no teste e a pior parte é que se um teste falhasse por uma razão muito específica, cancelaríamos a execução de todos os testes que possuem esse teste como antecessor na pilha de execução.

A segunda solução é isolando os testes, ou seja, tornando-os independentes de quaisquer outros testes ou rotinas que não fazem parte do próprio teste. Um ótimo exemplo de como podemos buscar isso sempre para o nosso dia a dia está no princípio do BDD (Behavior Driven-Development) Given – When – Then, mais especificamente no Given.

Vamos reescrever o teste acima no formato BDD:

Cenário: Cadastrar produto

Dado que eu estou na página de cadastro de produtos
Quando eu cadastro o produto “C”
E salvo
Então eu devo ver o produto cadastrado 

Cenário: Verificar soma dos produtos

Dado que os produtos A e B estão cadastrados
Quando eu solicito ver o valor total dos produtos
Então eu devo ver o valor total como 150,00

No formado exemplificado acima, todas as pré-condições para que o cenário “Cadastrar produto” seja executado devem estar no passo “Dado que eu estou na página de cadastro de produtos”. Esse passo é responsável por garantir que o ambiente, os dados, os recursos e tudo mais que o teste precisa para ser executado estão disponíveis e são exatamente os necessários para que o teste seja executado.

Vamos supor que os testes acima tenham essas características definidas em seus respectivos passos “Given”. Vamos executar os testes novamente começando pelo cenário “Cadastrar produto”:

Cenário: Cadastrar produto

    Dado que eu estou na página de cadastro de produtos
    Quando eu cadastro o produto “C”
    E salvo
    Então eu devo ver o produto cadastrado 

Cenário: Verificar soma dos produtos

    Dado que os produtos A e B estão cadastrados
    Quando eu solicito ver o valor total dos produtos
    Então eu devo ver o valor total como 150,00

Agora vamos executar primeiro o cenário “Verificar soma dos produtos”

Cenário: Verificar soma dos produtos

    Dado que os produtos A e B estão cadastrados
    Quando eu solicito ver o valor total dos produtos
    Então eu devo ver o valor total como 150,00

Cenário: Cadastrar produto

    Dado que eu estou na página de cadastro de produtos
    Quando eu cadastro o produto “C”
    E salvo
    Então eu devo ver o produto cadastrado 

E claro, um outro passo muito importante que pode ficar tanto no Given quanto no final do Then é o “Clean up your messi” ou “Limpe a sua bagunça”. O teste deve limpar tudo o que ele sujou na aplicação e isso inclui os dados, arquivos que foram modificados, urls criadas ou qualquer outro resíduo que tenha sido criado pelo teste.

Dessa forma evitamos que os testes sejam flaky tests novamente, já que não importa se removermos testes, se executarmos em ordens diferentes ou se testes falharem, cada teste sabe o que precisa para executar.

O “Given When Then”

Exemplo do uso do “given when then” centrado na visão e linguagem do usuário

O “Given, when, then” ou “Dado que, Quando, Então” é um velho conhecido dos QAs, não só dos ágeis como dos tradicionais que amam a IEEE 829, embora com nomes diferentes. Para ver uma comparação entre essa norma e esse modelo de escrita de testes de aceitação veja o post “Entendendo BDD com Cucumber – Part I” deste mesmo blog.

Na automação desses cenários é comum ver vários pequenos erros que muitas vezes passam despercebidos, mas que devemos ficar atentos para garantir que os nossos testes são realmente baseados em comportamento.

Esse modelo prega que ao escrever um teste de aceite sempre teremos três marcos bem definidos que são o estado inicial, as ações ou eventos e o resultado esperado. Por isso o formado é baseado nessa três palavras chave, o que torna os testes muito mais legíveis e compreensíveis para usuários de negócio do que os nossos antigos testcases.

O “Given” nunca executa ação/evento e nem faz asserção explicita (Confere resultado), ele prepara o ambiente. Não faz parte dele steps como:

  • Dado que eu estou entrando na página (Evento)
  • Dado que eu clico no botão “Ok” (Evento)
  • Dado que a mensagem de erro é igual a “Mensagem” (Resultado)

O Given é focado em estado. Exemplos de steps seriam:

  • Dado que eu tenho um usuário logado
  • Dado que existe um produto chamado “Refrigerante”
  • Dado a home page do google.com 

Já o “When” indica uma ação do usuário ou um evento sistêmico. São maus exemplos:

  • Quando o usuário “Camilo” está logado (Estado)
  • Quando o processamento dos dados terminar (No sentido de conferir, não de evento)
  • Quando eu estou na home page do google.com

São bons exemplos do uso do “When”:

  • Quando eu preencho o formulário de cadastro do cliente “Camilo”
  • Quando o sistema desliga o servidor
  • Quando eu envio um e-mail através da ajuda ao cliente

O “Then” é o teste em si. Esse passo verifica se com o nosso estado inicial e as nossas ações nós chegamos ao nosso resultado esperado. São maus exemplos do uso do “then”:

  • Então eu devo clicar no botão “Ok”
  • Então eu envio um e-mail através da ajuda ao cliente
  • Então eu Busco pela frase “Voto Como Vamos”

São bons exemplos do uso do “Then”:

  • Então eu devo ver o usuário cadastrado
  • Então o servidor deve estar desligado
  • Então eu devo receber um e-mail da central de ajuda ao cliente

DRY – Don’t Repeat Yourself

Dry é um conceito usado tanto para desenvolvimento quanto para teste e embora o objetivo seja o mesmo (evitar redundâncias desnecessárias), o caminho para chegar à menor redundância é diferente. É importante dizer isso porque o teste deve ser fácil de entender e ninguém deve ficar lendo dezenas de arquivos externos ou classes em uma arquitetura complexa para entender um teste, o teste deve ser auto explicativo, logo existe um certo nível em que o Dry pode atrapalhar os testes. Esse é um ótimo assunto para um post futuro, mas nesse post vamos focar em outros exemplos.

DDT – Data-Driven Testing

Em alguns casos um desenvolvedor ou testador pode precisar executar um código com dezenas ou centenas de dados diferentes. Se lembrarmos do nosso primeiro exemplo de código gerado pelo Selenium IDE vamos perceber que o conteúdo dos testes eram os mesmos, mas que algumas informações como o browser, os dados de entrada e o resultado esperado mudavam de um teste para o outro. Esse é um problema comum em scripts gerados automaticamente e também é um erro comum de pessoas com pouca experiência com teste automatizado.

Vamos supor que temos um teste para um triângulo e que precisamos testar pelo menos um triângulo equilátero, um escaleno e isósceles:

var lado1 = 10
var lado2 = 10
var lado3 = 10

mathService.isTriangulo(lado1, lado2, lado3).should().beTruth()
mathService.tipoTriangulo(lado1, lado2, lado3).should().be(‘equilatero’)

var lado1 = 3
var lado2 = 4
var lado3 = 5

mathService.isTriangulo(lado1, lado2, lado3).should().beTruth()
mathService.tipoTriangulo(lado1, lado2, lado3).should().be(‘escaleno’)

var lado1 = 4
var lado2 = 4
var lado3 = 5

mathService.isTriangulo(lado1, lado2, lado3).should().beTruth()
mathService.tipoTriangulo(lado1, lado2, lado3).should().be(‘isósceles’)

Um testador diria que o teste acima executa o mesmo cenário de teste usando test cases diferentes, ou seja, existe um conjunto de procedimentos padrão e um conjunto de dados de entrada e resultados esperados diferentes, mas que compartilham do mesmo comportamento para serem executados.

Caso o teste validasse somente esses três casos seria até interessante deixar essa redundância, mas vamos imaginar que precisamos validar uma centena de triângulos usando essa mesma abordagem. Teríamos centenas de testes iguais e se precisarmos mudar qualquer coisa no futuro, precisaremos passar em teste por teste para fazer essa mudança. Para evitar esse cenário podemos utilizar o que chamamos de Data-Driven Testing, separando o comportamento e os dados em lugares diferentes.

Em um script ficaria mais ou menos assim:

var arquivo = File.read(“dados.csv”)

arquivo.each do
    var lado1 = arquivo[‘lado 1’]
    var lado2 = arquivo[‘lado 2’]
    var lado3 = arquivo[‘lado 3’]
    var tipoTriangulo = arquivo[‘tipo esperado’]

    mathService.isTriangulo(lado1, lado2, lado3).should().beTruth()
    var tipoRetornado = mathService.tipoTriangulo(lado1, lado2, lado3) 
    tipoRetornado.should().be(tipoTriangulo)
end

A linha 1 tem um papel muito importante, ela carrega as informações de um arquivo externo ao código de teste e em seguida a linha 3 executa um loop no arquivo para buscar as informações que serão atribuídas mais tarde as quatro variáveis que são usadas para fazer esse teste. Desta forma evitamos a repetição dos casos de teste. Claro que essas informações não precisam estar fora do código. Elas também podem ser armazenadas na forma de um array ou qualquer outra estrutura de dados e executado de forma sequencial, randômica ou ordenada, de acordo com a necessidade do teste.

Um vídeo contendo esse exemplo no visual studio 2010 pode ser visto no post “Teste de unidade para quem não sabe nada de programação” deste mesmo blog.

Factory Pattern

Outro Design Pattern muito usado por desenvolvedores e que também pode ser usado para facilitar testes de unidade, funcionais e de aceite.

Exemplo clássico: Um formulário de cliente com dezenas de campos. Alguns deles com regras de obrigatoriedade, outros com regras de tamanho de campo, outros com regras na edição, etc. Normalmente criamos os testes com os dados que usaremos para preencher o formulário de cadastro, e sempre que precisamos de validar uma regra especial, criamos mais um conjunto de dados para isso, ou pior, copiamos e colamos o mesmo código ou o mesmo conjunto de dados que usamos no teste anterior… não precisamos fazer isso mais :)

O factory pattern nos permite criar uma “fábrica” de alguma coisa, no nosso caso de dados para um formulário. Para isso cadastramos uma vez o conjunto de dados padrão e sempre que precisarmos chamamos esse padrão indicando quais as mudanças precisamos nele. Por exemplo:

FactoryGirl.define do
  factory :candidate do
    name       "Candidato Fulano de Tal"
    short_name "Fulano Legal
    role       "Vereador"
  end

  factory :candidate_without_role, :parent => :candidate do
    after(:build) do |candidate|
      candidate.role = nil
    end
  end
end

O exemplo acima exibe um candidato padrão que contem nome, nome curto e papel, e um segundo exemplo onde o candidato de cima é usado como base para a criação de um candidato sem papel. Em qualquer momento durante o teste, quando precisarmos de um candidato completo podemos chamar a fábrica de candidatos pedindo “candidate”, mas se precisarmos de um candidato sem papel podemos pedir “candidate_without_role” e receberemos sempre o candidato que solicitarmos.

Além de simplificar a legibilidade do código de testes e de abstrair das dezenas de informações que um cadastro poderia precisar, esse modelo ainda facilita na manutentabilidade do teste, pois se precisarmos mudar algo nesse conjunto de dados mudamos só na implementação da fábrica e os testes continuam funcionando.

Para exemplos do uso do Factory Pattern em um projeto real acesse o github do projeto Voto Como Vamos: “https://github.com/thoughtworks/voto-como-vamos

Separation Of Concerns

Mesmo que os exemplos acima já separem algumas das preocupações, existe um padrão reconhecido por tornar os testes mais simples e fáceis de desenvolver e manter, o page-object model.

Representação de um test flow buscando serviços em diferente page objects

A figura acima representa um test flow ou cenário de teste, buscando os serviços para objetos que representam as páginas da aplicação. Para que isso seja possível temos que separar cada página em uma classe. Essas páginas devem fornecer de uma maneira bem fácil uma interface para as suas operações e idealmente suas propriedades devem estar separadas desses serviços de forma a evitar a duplicidade de informação.

Um exemplo de page pode ser visto abaixo:

module Search
  class HomePage  
    @url = “www.google.com”
    @search_field = “q”  
    @search_button = “search”

    def input_search(value) 
      @browser.text_field(:name => @search_field).set value
    end

    def run_search
      @browser.button(:class => @search_button).click
	return ResultPage.new
    end

    def visit
      @browser
    end
end

Podemos notar no exemplo acima, que as linhas 3, 4 e 5 são atributos ou variáveis que contem a identificação dos elementos de interface da tela, como botões, campos de texto ou mesmo informações como título ou url, enquanto o restante da classe é formado por métodos que proveem maneiras de usar essa página. Dessa forma separamos a complexidade da página em dois níveis, e caso precisemos mudar algum dos elementos de interface podemos mudar em um só lugar sem afetar a interface do nosso page-object ao mesmo tempo que podemos mudar nossa interface sem afetar nossos elementos de página.

Tendo page objects podemos implementar um flow que usará essas interfaces para executar um teste:

@page = Search.HomePage.new
@page.visit
@page.input_search(“Foo”)
@resultPage = @page.search
@first = @resultPage.results.first
@first.title.should eq(“bar”)

O exemplo acima mostra que além de não nos preocuparmos com a interface quando mudamos elementos e vice versa, também não precisamos nos preocupar com nada relativo as páginas na hora de implementar novos testes para elas, assim quando mudarmos os comportamentos internos da nossa page ou algum dos seus elementos, não precisamos mudar nada no comportamento ou fluxo de execução dos testes.

Para um exemplo mais elaborado em português acesse o post “Penso, logo automatizo” deste mesmo blog.

Resumindo

* Código de teste também é código; 
  – (quase) todas as boas práticas se aplicam

Preocupe-se com o seu código de testes tanto quanto com o seu código de produção. O seu código de testes é o que te apoia nas mudanças e é o que te da segurança para tomar decisões como “ir ou não para produção”. Se o seu código de testes não é confiável, o seu código de produção está em risco.

* Don’t Repeat yourself!

Evite a repetição desnecessária, mas não deixe de fazer a repetição necessária. Seu teste tem a obrigação de ser claro. Se o seu teste for complexo seja pela falta ou pelo excesso de informação, revisite-o e resolva esse problema antes que a complexidade aumente e não seja mais possível fazê-lo.

* Código de teste deve ser uma representação automática da interação do usuário
  – Seu sistema pode não dar feedback suficiente

Seus teste de aceitação e funcionais devem ser baseados em um usuário real. Seu teste não deve buscar uma mensagem de sucesso na tela, mas sim ter certeza que a operação foi realizadas com sucesso da mesma forma que um usuário vê. Se seu teste de aceitação não se baseia em um usuário real ele não tem o valor que deveria ter.

Livros recomentados

Por fim separamos um conjunto de livros que ajudam a entender melhor esse universo de teste automatizado:

Boa leitura :)

Camilo Ribeiro

Test Engineer at Klarna
Desenvolvedor, testador e agilista desde 2005, atualmente trabalhando na Suécia.

4 thoughts on “Agile Brazil 2012: Boas Práticas de Teste Automatizado

  1. Prezado Camilo Ribeiro, você está de parabéns por esse texto. Muito bem escrito e inteligível. Grato!

  2. Excelente texto. Eu estou tentando colocar estes princípios em prática no dia-a-dia da empresa na qual trabalho.
    Parabéns Camilo

  3. Camilo Ribeiro, meus parabéns pela excelente qualidade do texto. Raramente é possível encontrar bases de conhecimento com tamanha qualidade em Português.

Leave a Reply