Penso, logo automatizo

Estamos com muitas discussões sobre o tema: “Automatizar os meus testes é mais caro/demorado/arriscado e tudo de ruim se comparado com executar os testes manualmente”.  Bom. . . Se pensarmos com bons certificados que somos, sim, qualquer resposta diferente disso é menos um ponto na maioria das provas de certificação.

Muitos slides por aí na internet, inclusive meus (Y_Y) também falam sobre isso. Então não tem mais o que discutir certo? Nem tanto . . .

O problema da automatização não é a automatização em si, mas sim, a automatização sem planejamento, sem uma arquitetura mínima. E quando falamos de planejamento, não estou falando de documentação extensiva nem de horas e horas nas salas de reunião com o seu gerente, o presidente da empresa e mais três pessoas que não fazem a mínima ideia do que seja arquitetura de software e padrões de projeto, mas sim de bons QAs e bons developers pareando e pensando juntos em como desenvolver uma arquitetura para testes automatizados que seja adequado ao seu projeto, sustentável, fácil de entender, extensível e acima de tudo flexível a mudanças.

Estamos falando justamente sobre isso no GUTS-RS e também no DFTestes, quando um colega na discussão questionou como isso funciona em termos práticos, por isso estou postando este exemplo usando um framework gratuito e portável para automação de testes funcionais, o Watir.

Para rodar todos os exemplos deste post você precisa apenas instalar o ruby 1.8.7 ou superior na sua máquina com windows ou linux e em seguida executar o seguinte comando:

Windows: ~$ gem install watir-webdriver
Linux: ~$ sudo gem install watir-webdriver

Pronto :)

Para exemplificar como isso funciona na prática, vamos imaginar  um cenário bem simples que vai ficando mais complexo, exatamente como um projeto de software funciona:

Um QA qualquer recebeu a seguinte tarefa: Inclua o texto “Automação Rocks!” no google e em seguida verifique se é apresentado o texto “The Bug Bang Theory 2.0″. Esse QA poderia optar por três alternativas:

  1. Executar testes exploratórios: Sem evidenciar ou documentar os testes (planejar != documentar). Em teoria, esse é o teste mais rápido que existe, logo mais simples também. As suas desvantagens são exatamente a falta de evidências e a incerteza ao relatar a cobertura dos testes.
  2. Documentar os testes em uma ferramenta de gestão de testes: Documentando e evidenciando as execuções manuais dos testes em uma ferramenta como por exemplo o TestLink. Esse é o segundo teste mais rápido*, e agora com a vantagem que temos a nossa cobertura bem definida e evidencias das execuções para nossos PMs e ou clientes.
  3. Automatizar os testes: Escrever testes automatizados para que os testadores fiquem livres para criar novos cenários e testar a aplicação em outros aspectos. O problema aqui é que esse teste é muito caro**, as empresas não tem ambiente para isso**, e segundo alguns CONSULTORES da nossa área, dificilmente o ROI de automatizar um projeto é sustentável***.

*Argumento que contrario neste post :D
**Outros argumentos que contrario aqui . . . :p
***Argumento ultrapassado usado nos anos noventa e que continua sendo usado na atualidade. . .  tsc tsc tsc

Lembrando que BDD e TDD também são formas de automação.

Vamos imaginar que a tabela abaixo represente os tempos para cada tarefa:

=> Só para alinhar: Sabemos que no caso do teste exploratório não teremos elaboração de testes, apenas o planejaento. Sabemos que o planejamento indeferi da abordagem usada, ou seja, será uma constante daqui pra frente. A manutenção também deve ser considerada daqui pra frente, visto que começamos o projeto e ao londo do tempo teremos que mudar os entregáveis do projeto. O tempo de execução para os três casos foi o mesmo, já que consideramos aqui que nenhum tempo será menor que um minuto. A unidade de medição será minutos. Essa planilha é ilustrativa e estou usando apenas dados de suposições.

De qualquer forma, muitas pessoas, inclusive EU já passaram por experiências desagradáveis com automação de teste de software. O caso abaixo aconteceu comigo em um projeto de longos meses, que ao fim, acabou fracassando. Possivelmente aconteceu com muitos leitores (ou iria acontecer), pois é um dos erros mais comuns.

O nosso caso de teste acima pode ser automatizado da seguinte maneira:

EXEMPLO ILUSTRATIVO DE AUTOMAÇÃO RUIM (bad automation):

require "rubygems"
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

Mais uma vez está provado :) Testar manualmente é mais rápido e se não for documentado é mais rápido ainda. Isso prova que automatizar é mais demorado e mais caro se voce tiver que executar uma vez o teste, imagina se o projeto ficar mudando . . .  Vamos conferir?

Agora o nosso requisito se estendeu, a nova versão do software vai ter que testar mais cinco textos, são eles:

Entrada :::::                  Saída
Automação Rocks! ::::: The Bug Bang Theory 2.0
Selenium history :::::  ThoughtWorks
Desafio Selenium ::::: sembugs.blogspot.com.br
Teste de software Brasil ::::: www.testexpert.com.br
mesa redonda teste de software ::::: www.qualidadebr.wordpress.com

Novamente, conferir a tabela e ver os resultados:

Meu raciocínio foi bem linear: Planejamos cinco vezes mais. O planejamento é uma constante para todos os casos. A elaboração para os testes exploratórios manteve como nula, enquanto a elaboração para testes documentados foi de três vezes o planejamento. A elaboração de testes automatizados se mostra mais demorada, por isso multipliquei por quatro. A execução exploratória é mais rápida também, cerca de três vezes o planejamento, já a execução documentada necessita da coleta de evidências e preenchimento de formulários, por isso quatro vezes.

Abaixo um exemplo do script que poderia ser gerado:

EXEMPLO ILUSTRATIVO DE AUTOMAÇÃO RUIM (bad automation):

require "rubygems"
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
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('Selenium history')
browser.button(:name => 'btnG').click
if browser.text.include? 'ThoughtWorks'
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('Desafio Selenium')
browser.button(:name => 'btnG').click
if browser.text.include? 'sembugs.blogspot.com.br'
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('Teste de software Brasil')
browser.button(:name => 'btnG').click
if browser.text.include? 'www.testexpert.com.br'
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('Agile Tester')
browser.button(:name => 'btnG').click
if browser.text.include? 'Agile Testing: A Practical Guide for Testers and Agile Teams'
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('mesa redonda teste de software')
browser.button(:name => 'btnG').click
if browser.text.include? 'www.qualidadebr.wordpress.com'
puts 'Sucess'
else puts 'fail'
end
browser.close

Agora, vamos imaginar que tivemos um bug no caso 4 e vamos ter que reexecutar os testes. Vamos ter mais ou menos uma tabela assim:

Mantivemos os tempos anteriores e reexecutamos os testes, tanto manuais quanto qutomatizados. Aqui o teste automatizado já se mostrou uma opção melhor que os testes documentados e puramente exploratórios. Mas antes de tirar conclusões precipitadas, vamos ver o problema que vamos ter nos testes automatizados agora.

O exemplo acima, é um típico teste automatizado sem nenhum planejamento. Ainda arrisco dizer que este código (apesar de tudo) ainda é MUITO mais claro, limpo e compreendível do que o código gerado pela maioria das ferramentas de record and play-back do mercado.

Muitos profissionais, principalmente com cargos de liderança, digo isso pois muitos dos nossos líderes tiveram mais de 10 anos de experiência, onde tiveram que lidar com ferramentas hoje descontinuadas e com processos arcaicos de automação (baseada gravação de scripts por exemplo).

No dia que o google resolveu mudar o nome do campo “q” para “p”, o trabalho de refatoração foi muito grande, grande o suficiente para abortar o projeto. Ou seja, gastaram meses usando ferramentas caras, ou mesmo gratuitas como o Selenium IDE, que prometem reduzir tempo e ganhar muita produtividade, mas não explicam sobre os problemas de refatoração quando usamos o record and play-back.

Nossa tabela ficaria assim:

O tempo de refatoramento de scripts ruins é no meu ponto de vista, umas dez vezes o tempo de planejamento, por isso, muitas vezes é mais interessante começar tudo de novo. No nosso exemplo simplista, claro que eu mudaria tudo em um minuto usando o search and replace de qualquer editor de texto, mas em um projeto com vários fatores complicadores, eu teria que mudar isso em cada teste, muitos dos testes iriam quebrar e eu ia ter que debbugar e o custo disso seria muito alto. A reexecução se faz necessária em todas as abordagens, pois um elemento técnico foi modificado e pode introduzir novos bugs.

*Importante dizer, que em alguns casos, MUITO específicos, o Record and Replay é muito bem aproveitado, mas ele não deve ser usado em projetos que sofrerão mudanças com o tempo.

Voltando ao nosso colega que liderou um desses projetos fracassados, ele se basea nesses tipos de experiências para argumentar, juntamente com suas oito certificações, que automatizar é muito mais demorado, mais caro e mais arriscado do que manter a documentação atualizada. Agora, não mencionam que se esses testes estiverem mantidos em um repositório como TestLink, Manual Tester, Quality Manager ou mesmo documentos de texto com ou sem controle de versão, o custo da manutenção será quase o mesmo, se não for pior.

Quantas vezes eu ví minha amiga Vaz reclamando de ter que mudar todos os 63 casos de teste de um caso de uso porque o cliente mudou duas regras e um passo no caso de uso . . . Muitas vezes, a abordagem foi a mesma citada acima para os testes automatizados, simplesmente apagar tudo e começar de novo, pois assim é muito mais fácil.

Agora vamos voltar no tempo e  melhorar um pouco nosso script, incluir os elementos que podem mudar em variáveis e executar novamente os medidores:

EXEMPLO ILUSTRATIVO DE AUTOMAÇÃO RUIM (bad automation):

require "rubygems"
require 'watir-webdriver'

campo_texto = 'q'
botao_pesquisa = 'btnG'

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Automação Rocks!')
browser.button(:name => botao_pesquisa).click
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 => campo_texto).set('Selenium history')
browser.button(:name => botao_pesquisa).click
if browser.text.include? 'ThoughtWorks'
puts 'Sucess'
else
puts 'fail'
end
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Desafio Selenium')
browser.button(:name => botao_pesquisa).click
if browser.text.include? 'sembugs.blogspot.com.br'
puts 'Sucess'
else
puts 'fail'
end
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Teste de software Brasil')
browser.button(:name => botao_pesquisa).click
if browser.text.include? 'www.testexpert.com.br'
puts 'Sucess'
else
puts 'fail'
end
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Agile Tester')
browser.button(:name => botao_pesquisa).click
if browser.text.include? 'Agile Testing: A Practical Guide for Testers and Agile Teams'
puts 'Sucess'
else
puts 'fail'
end
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('mesa redonda teste de software')
browser.button(:name => botao_pesquisa).click
if browser.text.include? 'www.qualidadebr.wordpress.com'
puts 'Sucess'
else puts 'fail'
end
browser.close

O simples fato de criar duas variáveis “campo_texto” e “botao_pesquisa”, já muda o nosso resultado absurdamente:

Reduzimos o tempo de manutenção de dez vezes no script anterior para o tempo de encontrar as variáveis e mudar o id! Logo desta forma, ainda MUITO precária e ultrapassada, já começamos a notar as vantagens do uso de scripts automatizados.

Agora vamos imaginar que um defeito comum aos três casos foi encontrado durante a execução. Até aqui, nos três casos, o testador abriria um bug no Mantis, Bugzilla ou qualquer outra ferramenta de bugtracking, esperaria a correção e executaria novamente os testes. Nossa tabela fica assim:

Imagine que além deste tempo, foi necessário que o desenvolvedor gerasse uma versão, publicasse essa versão, você executasse todos os testes, cadastrasse esse bug, o desenvolvedor fica sujeito a sua descrição do bug, corrige ele e commita o código que pode ter novos bugs e só então você retesta isso. . . A velha vida de quem está costumado com testes de regressão . . . esse tempo todo nem vai ser contado aqui, pois ficaria muito complexo e não está dentro do proposto.

O fato é que vamos adicionar um novo framework que para o teste no momento que ele deu erro, e que pode ser executado pelo desenvolvedor ou mesmo por um gerenciador de integração continua, como o Hudson e Jenkins por exemplo:

EXEMPLO ILUSTRATIVO DE AUTOMAÇÃO RUIM (bad automation):

require "rubygems"
require 'watir-webdriver'
require 'test/unit'

class TestaTudo < Test::Unit::TestCase

def setup
@campo_texto = 'q'
@botao_pesquisa = 'btnG'
end

def testa_todos_os_casos

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Automação Rocks!')
browser.button(:name => botao_pesquisa).click
assert browser.text.include? 'The Bug Bang Theory 2.0'
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Selenium history')
browser.button(:name => botao_pesquisa).click
assert browser.text.include? 'ThouhtWorks'
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Desafio Selenium')
browser.button(:name => botao_pesquisa).click
assert browser.text.include? 'sembugs.blogspot.com'
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Teste de software Brasil')
browser.button(:name => botao_pesquisa).click
assert browser.text.include? 'www.testexpert.com.br'
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('Agile Tester')
browser.button(:name => botao_pesquisa).click
assert browser.text.include? 'Agile Testing: A Practical Guide for Testers and Agile Teams'
browser.close

browser = Watir::Browser.new :firefox
browser.goto "http://google.com.br"
browser.text_field(:name => campo_texto).set('mesa redonda teste de software')
browser.button(:name => botao_pesquisa).click
assert browser.text.include? 'qualidadebr.wordpress.com'
browser.close

end

end

Agora o nosso código pode finalmente ser usados para testar o código antes dos devs pusharem/commitarem, ou seja, o desenvolvedor vai saber que está commitando código com erros para o repositório. E mesmo que ele o faça, o nosso amigo Hudson ou Jenkins vai nos avisar que existe alguma coisa cheirando muito mal no nosso código fonte e inclusive o que está quebrado e quem, conscientemente, subiu bugs cobertos para o repositório. Em outras palavras, a integração continua existe para que esse ciclo de achar defeito e esperar nova versão deixe de existir, menos trabalho repetitivo para nós QAs, build sempre saudável para o cliente e resposta mais rápida aos defeitos para o desenvolvedor. Com testes documentados ou somente exploratórios isso não é possível.

O exemplo acima, apesar de ser um exemplo de automação pobre, representa uma grande evolução se comparado a testes gravados ou mal elaborados, mas ainda assim está longe de ser um bom exemplo de automação. Este exemplo, é o mínimo que um QA pode fazer para apresentar alguma melhoria no código que algum gravador gerou, ou seja, remover os elementos hardcode, sustentá-los em variáveis e estabelecer uma forma de quantificar os cenários que falharam e tiveram sucesso.

O script anterior ainda apresenta vários outros problemas, tais como:

  • Forte dependencia de dados: Sempre que uma informação mudar, o QA deve editar o código fonte para mudar os dados. O mesmo acontece para acrescentar novos testes.
  • Baixa abstração: Um QA com menos experiência no código fonte ainda vai ter MUITA dificuldade para manter os testes, e possivelmente será necessário um papel como engenheiro de testes para ficar acrescentando testes.
  • Baixo reuso de código: Muito código repetido. Com exceção das linhas em que os dados são introduzidos e comparados, o restante do código fonte é o mesmo.
  • Baixa flexibilidade: Se precisarmos mudar o browser por exemplo, vamos ter que editar todos os testes. O mesmo acontece com o link da aplicação. O ideal é que estes itens sejam configuraveis.
  • Baixa Extensibilidade: O script acima também requer um esforço muito grande para ser evoluído. Se precisarmos incluir alguma nova verificação no script, vamos ter que incluir em cada grupo de linhas que representa um teste.

Todos os itens acima, são alguns dos problemas que sempre existiram e continuam existindo nas nossas empresas. Esse é um problema cronico, pois a maioria dos automatizadores que temos hoje não tem real experiência fora de sua ferramenta de record and play-back. Os nossos gerentes, em sua ânsia por uma solução mais rápida, optam por escolher essas ferramentas e pressionar esses profissionais para cobertura de testes rápida. Métricas sem sentido como por exemplo, número de linhas de teste são criadas para avaliar o trabalho dos desenvolvedores de teste. Esses problemas só podem ser vencidos por pessoas inovadoras, fora de sua zona de conforto, estudando como podem melhorar seu ambiente de trabalho e com atitude o suficiente para demonstrar que melhores soluções existem e estão ao alcance de todos. :)

São atitudes como a do Elias Nogueira com o Desafio Selenium, que infelizmente acabam por fracassar quando as pessoas deixam de participar. Essas mesmas pessoas que todos os dias perguntam sobre como tirar a certificação A ou B, mas não tem interesse em aprender como testar de verdade se isso não der um papel ou um link, que fale ele passou numa prova e por isso pode ser um “Arquiteto de testes” a partir de agora. Sem falar nas nossas centenas de consultores sêniors que são muito bons para participar dessas atividades e não pensam que eles são os exemplos que nossos novatos estão seguindo. Ninguém é bom o suficiente que não possa aprender um pouco mais com quem não sabe nada ou quase nada, principalmente em TI.

Abaixo vou mostrar um exemplo da aplicação de page object model usando Watir e Unit Test do Ruby com o conceito de DDT (Testes Dirigido pot Dados), como uma proposta de solução para alguns dos problemas acima:

require "rubygems"
require "watir-webdriver"

class GoogleHomePage

def initialize
@url = "http://www.google.com"
@search_field = "q"
@search_button = "btnK"
@luck_button = ""
end

def visit
$browser.goto @url
end

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

def run_search
$browser.button(:name => @search_button).click
end

def im_feeling_luck
$browser.button(:name => @luck_button).click
end

end

class GoogleResultPage
def should_have_text_present(text_at_the_page)
$browser.text.include? text_at_the_page
end
end

class GoogleLuckResult
def should_have_title(title)
$browser.title == title
end
end

class SuportClass
def open_browser
$browser = Watir::Browser.new
end

def close_browser
$browser.close
end

end

O script acima contem as páginas que vamos testar, ou seja, a Home Page do Google e a página de Resultados do Google. Escrevendo os testes dessa forma, não importa quantos requisitos novos forem implementados, os componentes da tela serão reusados para todas as funcionalidades. Assim ganhamos flexibilidade e extensibilidade, sem falar que não precisamos mais ficar perguntando qual caso de uso ou story estamos dando manutenção, estamos na página X e temos testes para todas as stories e UCs que interagem com ela.

require "rubygems"
require "pages"

module SearchTestHelper

def classic_search(configuration,text_to_search, expected_result)

home = Search::HomePage.new(configuration)
home.visit
home.input_search(text_to_search)
home.run_search
sleep 2
result = Search::ResultPage.new(home.browser)
assert result.should_have_text_present(expected_result)
(home.browser).close

end

end

Acima escreemos o nosso “cenário de teste” ou critério de aceite (Critério de aceite). Observe que este é um cenário totalmente construído a partir dos itens do arquivo pages.rb, ou seja, se alguma mudança acontecer na página, mudamos os procedimentos sem afetar o teste do comportamento.

require "rubygems"
require "flows.rb"
require 'test/unit'

class SearchTestCase < Test::Unit::TestCase

include SearchTestHelper

def test_classic_searchs

classic_search("Automação Rocks", "The Bug Bang Theory 2.0")
classic_search("Selenium history", "ThoughtWorks")
classic_search("Desafio Selenium", "sembugs.blogspot.com")
classic_search("Teste de software Brasil", "www.testexpert.com.br")
classic_search("Agile Tester", "Agile Testing: A Practical Guide for Testers and Agile Teams")
classic_search("mesa redonda teste de software", "qualidadebr.wordpress.com")

end

end

Acima está a nossa ultima camada, os casos de teste. Coloquei os testes em um arquivo ruby também, para facilitar a exemplificação no blog, mas como já é conhecido dos leitores, caso seja necessário, podemos incluir esses testes em um arquivo csv, xml, xls, xlsx, txt, um serviço ou em qualquer banco de dados que o ruby suporte. Também não evoluímos muito a parte de relatório, pois não é o nosso objetivo coletar métricas de cada execução dos testes, mas caso seja necessário, podemos elaborar mecanismos de escrita de relatórios em HTML por exemplo. Tudo depende da sua necessídade, e claro, criatividade.

Só para demonstrar o poder da automação, vamos supor que o Google comprou o Bing e o Yahoo. Além disso, nosso PM nos pediu que testassemos também no IE, Opera, Safari e Chrome. Além disso, ele também incluir mais 900 dados de entrada e saída que são obrigatórios.

Você que defende que o teste manual é mais rápido ou que a documentação de teste é algo extremamente importante? Vamos apostar quem responde mais rápido a essa mudança?

Abaixo o script com as modificações para todos os requisitos acima:

require "rubygems"
require "watir-webdriver"

module Search

class HomePage

attr_reader :browser

def initialize(configuration)

application = configuration[:application]

browser = configuration[:browser]

@url = application.url
@search_field = application.search_field
@search_button = application.search_button

@browser = Watir::Browser.new browser

end

def visit

@browser.goto @url
end

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

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

end

class ResultPage
attr_accessor :browser

def initialize(browser)
@browser = browser
end

def should_have_text_present(text_at_the_page)
@browser.text.include? text_at_the_page
end
end
end

Agora nosso arquivo da pagina recebe o browser e as configurações, ou seja, podemos escolher qual aplicativo será testado entre o bing, o yahoo e o google além de definir qual o browser e os dados. Isso nos permite criar dezenas, talvez centenas de possibilidades sem mudar uma virgula no código. Inclusive se um novo browser sair no mercado, basta esperar que o webdriver tenha um driver para ele que os testes estarão prontos para executar neste novo browser.

require "rubygems"
require "pages"

module SearchTestHelper

def classic_search(configuration,text_to_search, expected_result)

home = Search::HomePage.new(configuration)
home.visit
home.input_search(text_to_search)
home.run_search
sleep 2
result = Search::ResultPage.new(home.browser)
assert result.should_have_text_present(expected_result)
(home.browser).close

end
end

Limpamos o script de flows, tirando algumas coisas desnecessárias, e mudamos a forma que ele recebe e trata os argumentos, mas no geral, alguns cinco minutos de manutenção.

require "rubygems"
require "flows"
require 'ostruct'
require 'test/unit'

class SearchTestCase < Test::Unit::TestCase
include SearchTestHelper
def setup
@google = OpenStruct.new({:name => "google",:url => "http://www.google.com.br",:search_field => "q",:search_button => "lsb"})
@yahoo = OpenStruct.new({:name => "yahoo",:url => "http://www.yahoo.com.br",:search_field => "p",:search_button => "searchsubmit med-large y-fp-pg-grad"})
@bing = OpenStruct.new({:name => "bing",:url => "http://www.bing.com.br",:search_field => "q",:search_button => "sw_qbtn"})
@applications = [@google, @yahoo, @bing]
end

def test_classic_searchs

["firefox", "chrome","safari","opera","ie"].each do |browser|

@applications.each do |application|

configuration = {:browser=>browser, :application=>application}

classic_search(configuration,"Automação Rocks", "The Bug Bang Theory 2.0")
classic_search(configuration,"Selenium history", "ThoughtWorks")
classic_search(configuration,"Desafio Selenium", "sembugs.blogspot.com")
classic_search(configuration,"Teste de software Brasil", "www.testexpert.com.br")
classic_search(configuration,"Agile Tester", "Agile Testing: A Practical Guide for Testers and Agile Teams")
classic_search(configuration,"mesa redonda teste de software", "qualidadebr.wordpress.com")

end

end

end

end

Nosso script de test_cases foi o mais modificado. Tivemos que tratar de algumas novidades, como as collections de browsers e de aplicações. Também criamos uma estrutura para cada aplicação, uma pro bing, uma pro google e uma pro yahoo. Essas estruturas são desconstruídas e seus dados usados para mudar os testes em tempo de execução.

Usando o nosso script acima, mudamos para aplicar as mudanças que eu comentei, e vamos ver como ficou a nossa tabela:

Meu raciocínio novamente foi bem linear. Pra cada três testes vamos levar um minuto planejando. Isso é constante para todas as abordagens. Para elaborar testes exploratórios não gastamos tempo, para elaborar os documentos dos testes manuais vamos gastar três vezes o planejado e para elaborar os testes usando esse modelo, vamos gastar uma hora elaborando as três classes mais 1 minuto para cara três linhas incluídas no arquivo de test cases (isso considerando que vamos fazer os testes dessa forma, pois poderíamos gastar mais 30 minutos importando de um xml por exemplo). Para executar os testes exploratórios levamos três vezes e os manuais, como temos que preencher documentos e evidências vamos quatro vezes (ainda é pouco!), os automáticos vão levar pouco mais de 10 minutos. A cada novo release, vamos repetir a execução . . .

Agora vamos imaginar que o cliente resolveu mudar os 900 itens que ele usava para garantir que o sistema está ok:

Estou sendo MUITO gentil com os testes manuais documentados, e considerando que eles custam só três vezes o tempo, pois na realidade em um caso como esse, os casos de teste seriam jogados fora e elaborados novos. Nossos testes automatizados, agora customizados para o cenário que estamos inseridos, custaria apenas o tempo de escrever as nossas linhas no arquivo test cases :)

Não ficou tão explicito, mas podemos observar também, que o total de uma execução dos testes exploratórios, foi de 1200 minutos, e o total da execução dos testes automatizados foi de 480 minutos, o que nos deixa com 720 minutos para executar testes exploratórios em outros cenários, testes de segurança e até para performance. Isso para uma execução.

Assim como o conjunto que eu usei aqui (Watir e UnitTest), qualquer outro framework e linguagem pode fazer da mesma forma. Claro que alguns mais complicados que outros, mas sempre existe uma maneira de implementar padrões de projeto como Data-Driven Testing, Page-Object Model, Test Data Builder Pattern entre vários outros.

Tudo que estou falando acima não está provado cientificamente, nem por um doutorado :p Mas funciona em vários projetos rsrs

Importante ressaltar que a solução acima não é bala de prata, logo, não funciona para todos os casos. Cada projeto é único, não interessa se o cliente, a tecnologia, as pessoas e o ambiente é o mesmo, sempre existirão desafios que só podem ser vencidos por pessoas inteligentes, usando soluções inovadoras e com conhecimento do contexto. Nenhuma ferramenta pode superar o conhecimento e pra automação essa afirmação é MUITO pertinente.

Camilo! O exemplo acima é muito simples e esse tipo de abordagem não funcionaria em um projeto de verdade!

Projetos com abordagens semelhantes a essa:

https://github.com/jorgej/RapidFTR (Usando Cucumber)
https://github.com/mifos/head (Usando Selenium)

Fora muitos outros que infelizmente não posso publicar aqui :(

A automação não prega escrever código pra tudo, ser especialista em código fonte ou etc., mas prega que as atividades repetitiveis que não exijam criatividade de um QA ou desenvolvedor sejam realizadas por máquinas.

É muito estranho nós, profissionais da computação falarmos que executar manualmente é melhor, menos arriscado ou a mais garantido em quase todos os casos, afinal de contas, nós só existimos para isso, agilizar e mitigar riscos de negócios alheios a computação através da tecnologia. Pare e pense, no fundo, TODO sistema de informação é automação de algum processo que já existia. Falar que automatizar é um caminho ruim na maioria dos casos é falar que nossos sistemas também são ;)

O exemplo já está disponível como código no meu github: https://github.com/camiloribeiro/Evolutionary-Automation-Based-on-Page-Object-Model-Example (vai sofrer mudanças)

Importante lembrar que o exemplo acima implementa todos os testes do zero, sem usar nenhum framework de produtividade para Page Object Model ou Data-Driven Test com existem vários no mercado.

Lembro também que o modelo adotado acima não representa a melhor solução, mas sim uma solução possível. Para testar em múltiplos browsers por exemplo, usar o Selenium grid seria uma solução mais robusta e elegante, mas adicionaria uma complexidade desnecessária ao post.

Participe: Sugira uma nova feature desafiadora ou mesmo uma aplicação aberta e vamos ver como podemos evoluir mais esse exemplo :)

Outro exemplo de automacao inteligente é o uso de BDD (Behavior Driven-Development), que vamos falar no futuro, mas que já pode ser visto em outrs blogs:

http://www.thoughtworks.com/articles/future-test-automation
http://blog.caelum.com.br/behavior-driven-development-com-junit/
http://qualidadebr.wordpress.com/2010/06/13/bdd-behavior-driven-development/
http://dannorth.net/introducing-bdd/
http://brunograsselli.com.br/2010/01/como-executar-o-cucumber-em-portugues.html
http://cukes.info/

Agradecimento (Thanks):

Guilherme Motta, Krishna Kumar Sure e Fabío Rehm pela revisão de codigo :)

Abaixo algumas referências e links recomendados:

https://github.com/cv/bdd-and-selenium-talk –  Material sobre BDD com Cucumber, Page Object Models e RSpec, inclusive usado como inspiração para esse post (thanks CV e Pedro)
https://github.com/camiloribeiro/Evolutionary-Automation-Based-on-Page-Object-Model-Example – Exemplo usado nesse post.
http://xunitpatterns.com/ –  Wiki com muitos exemplos de aplicações de padrões de projeto para automação em várias linguagens.
http://knorrium.info/2010/11/15/better-reporting-with-reportng/ –  Exemplo da aplicação de DDT no Selenium usando Java.
http://watirmelon.com/2011/03/15/specdriver/ – Outro exemplo de Page Object Model para C#
http://www.google.com.br/search?source=hp&q=Page+Object+Model – Link da pesquisa de page object model no google :)

Camilo Ribeiro

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

10 thoughts on “Penso, logo automatizo

  1. Olá Camilo!

    Dizer que o artigo está ótimo, é chover no molhado rsrs…
    Então vamos ao que interessa: Automação!

    Estou com uma dificuldade imensa de obter resultados através da automação, ainda mais por que o que eu preciso é mais direcionado a desktop. A explicação é simples: estou pegando mais bugs manualmente do que automatizando… Resultados!

    Contudo, este post serviu um pouco como uma “injeção de ânimo”, ainda mais por que já estava começando a desacreditar…

    Obrigada, e parabéns pela iniciativa!

    Ana Ludmila

  2. Muito boa levantar essa bola Camilo. Essa comparação de tempos entre os testes faz bastante sentido quando tu mostras o código refatorado. E sim, continuous integration é o ouro! Muita dor de cabeça poderia ser evitada se esse método de integração fosse usado mais frequentemente e da forma correta. Este modelo final de descrição de página e fluxo lembra muito o framework taza (http://taza.rubyforge.org/files/README_txt.html).

    Parabéns pelo post.

  3. Obrigado Lud,

    Minha experiência com automação para ambiente desktop é quase nula e sei que devem existir dezenas de restrições que não enfrentei, por isso não sei como opinar nesse caso :(

    Mas acredito que a modularidade aplicada neste post pode ser parcialmente implementa. É claro que alguns desafios da web como a mudança de nomes e ids, ajax, javascript entre outras também não existem neste tipo de ambiente, o que pode tornar a criação do código de teste mais fácil. Acho que vale a pena uma conversa com a sua equipe de desenvolvimento e a avaliação das ferramentas usadas para a automação, buscando criar um “mini-framework” para facilitar os seus testes.

    De qualquer forma, é totalmente natural que seus testes exploratórios pequem mais defeitos que os testes automatizados, afinal de contas, não sei como podemos usar TDD no seu contexto, e TDD é o primeiro passo para evitar defeitos antes dos seus testes exploratórios. Mas defeitos de regressão por exemplo, que são os mais comuns e responsáveis pela maioria do retrabalho de teste e desenvolvimento em produtos de software (COTS), são pegos rapidamente com automação, inclusive em ambientes desktop.

    Sei que automatizar, principalmente quando temos pressão de entrega, muitos defeitos e pouco tempo para testar, pode se mostrar um desafio muito grande e principalmente arriscado. O ideal é introduzir algumas rotinas aos poucos, e deixar o valor aparecer. Pense nas rotinas que apresentam mais defeitos no sistema e avalie a possibilidade de automatizá-las. Verifique quanto tempo elas estão economizando do seu reteste e então ataque a nova campeã de retrabalho. Uma abordagem baby-steps nesses casos agrega valor ao mesmo tempo que mitiga os riscos :)

    O importante é não desanimar. Se observar o post, ele fala sobre esse tipo de experiências ruins com automação, que acabam criando uma falsa ideia na cabeça da equipe, principalmente de quem não entendeu o motivo do fracasso. Por isso realmente não é uma boa automatizar tudo até que aprenda os limites do teu ambiente e tenha mais domínio da sua aplicação.

    Abraços

  4. Olá Camilo,
    muito legal você levantar essa questão no seu post!
    Como usuária de linux, apenas sugiro que não se use “sudo gem install *”
    Eu já tive várias dores de cabeça por causa disso logo que comecei a programar em ruby. Minha sugestão é usar rvm (http://beginrescueend.com/) .
    Aqui tem dois links que explicam o porquê não se deve usar o sudo, caso queiras saber mais:
    http://stackoverflow.com/questions/2119064/sudo-gem-install-or-gem-install-and-gem-locations
    http://masanjin.net/blog/sudo-gem-install-considered-harmful

  5. Parabens pelo post e pela iniciativa de compartilhar o conhecimento, concordo em gênero, número e grau quando cita alguns exemplos de pessoas e situações enfrentadas no dia-a-dia.

    Abraço e keep up the good work.

  6. Muito bom o post. Ainda bem que a comunidade de QA está abrindo o olho para automação de testes. Isso faz com que o trabalho seja mais recompensador e o profissional melhor valorizado.

    Camilo, só uma sugestão: O post ficou GIGANTE. Porque não separá-lo em vários menores? Assim como no TDD, podemos aplicar ‘baby steps’ ao blog para garantir que cada etapa do processo foi compreendida.

    E ainda falta o post sobre o dia a dia e os desafios de POA, hein.

  7. É isso Camilo. Muito bom o post. É isso que quero começar aqui. Eu sempre fui mais do lado da Análise tradicional. Porém penso que com a cobrança e os desafios dos projetos esta é a forma mais interessante de testar, principalmente projetos grandes e com muita regressão. Continue com o bom trabalho. Como diz o site da Thoughtworks “Stop working. Start amazing.”

  8. Muito Obrigado pelo Comentário Gustavo!

    Acho que usou uma palavra muito boa para descrever automação: Desafios.
    Automação de testes ainda é um tabu e é visto como um monstro muito maior do que realmente é. Praticando você vai aprender bastante e em pouco tempo vai ver o quanto isso vai te ajudar no trabalho.

    Sinta-se a vontade para sugerir melhorias ou tópicos para posts futuros aqui do blog.
    Parabéns pela iniciativa e Keep amazing!

    Camilo

Leave a Reply