Resumo
Eu tenho apenas 5 meses de experiência com WATIR, mas já me convenci de certas práticas de utilização que considero boas, e que de alguma forma estão trazendo resultados positivos.
Vale lembrar que são todas baseadas no bom-senso.
Conteúdo
- Utilizar sempre a interface Watir::Browser
- Buscar componentes HTML por id sempre que possível
- Extrair dados de entrada/saída e mensagens em constantes
- Quebrar a lógica em métodos privados para promover reutilização
- Isolar a discrepância entre IE e Firefox em métodos polimórficos usando o design pattern Strategy
- Escrever no console mensagens de controle e andamento
- Evitar ter de manipular mensagens pop-up javascript
- Cuidado com inclusões de páginas
- Recuperar mensagens de arquivos de texto
- Ao testar inclusões com sucesso, realizar a limpeza depois
1. Utilizar sempre a interface Watir::Browser
A partir da versão 1.6.2, foi introduzida a interface Watir::Browser, que veio para permitir o tratamento do browser genericamente, sem fazer referência a um browser específico como antigamente, quando eram instanciados objetos Watir::IE ou FireWatir::Firefox.
É boa prática sempre instanciar objetos Watir::Browser, e deixar a definição de qual browser será utilizado fora de seu código-fonte, ou seja, esta decisão não deve ser hard-coded. Assim, pode-se alterar o browser a ser utilizado sem alterar o código-fonte, alavancando desta forma processos automáticos de execução de testes (como scripts ANT) alterarem o browser a ser testado em tempo de execução.
2. Buscar componentes HTML por id sempre que possível
Existem várias maneiras de se localizar um componente HTML em uma página, como se pode ver no link Methods Supported by Element. Cada componente tem vários critérios de localização como id, name, value, text, href, index, xpath, class…
No entanto, cada um destes critérios tem um motivo de estar sendo usado – e este motivo pode precisar de flexibilidade.
Por exemplo, o critério name representa o nome deste componente em um formulário, e isto comumente é utilizado para refletir em propriedades de form beans do lado do servidor. Ou seja, pode ser um reflexo de uma lógica de negócio – que PODE MUDAR FACILMENTE!
Outros exemplos:
- o critério href representa uma URL – que PODE MUDAR FACILMENTE!
- o critério text representa texto de página – que PODE MUDAR FACILMENTE!
- o critério value representa o valor padrão de um componente – que PODE MUDAR FACILMENTE!
Todavia, o id não tem motivos para mudar facilmente assim, pois seu propósito é geralmente apenas identificar um componente HTML, o que teoricamente não sofre implicações da lógica de negócio. Portanto, é vantajoso localizar programaticamente por id, sempre que possível.
Se o componente desejado não houver atributo id, criá-lo não causará impacto algum na página (ENTRETANTO, verifique se o id desejado já não foi utilizado em sua página ou em alguma página incluída dela). Recomendação: escolha o id longo, claro e baseado no propósito do componente, de forma a não sobrarem motivos para dar este mesmo id a outro componente.
Ao mesmo tempo, essa prática de utilizar id em todos os componentes promove outro aspecto: melhora a legibilidade do código HTML, identificando cada componente quanto a seu propósito.
3. Extrair dados de entrada/saída e mensagens em constantes
Dados de entrada e saída de um teste geralmente não mudam durante sua execução. Nem mensagens esperadas de sucesso ou falha.
Desta forma, é uma boa prática extraí-los para constantes globais e colocar todos no início do código. Assim ganhamos performance, legibilidade e reuso.
Constantes em Ruby são definidas simplesmente como variáveis que iniciam com letra maiúscula. Aqui no exemplo também começam com $ pois são globais.
Antes:
-------------------- test.rb
# requires
require "test/unit"
require "watir"
require "watir/testcase"
##
# Test case for sign up scenarios
# @author Tiago Romero Garcia
#
class Test < Watir::TestCase
# Method for setting up a test method
def setup
@browser = Watir::Browser.start "http://localhost/myapp/index.jsp"
@browser.maximize
end
# (...)
# Method for testing sign up
def test_sign_up
# (...)
# tries to sign up
@browser.text_field(:id, "emailId").set("test@provider.com")
@browser.text_field(:id, "passwordId").set("testing")
@browser.text_field(:id, "confirmPasswordId").set("testing")
@browser.button(:name, "register").click
# (...)
# checks if sign up was successful
test_condition = @browser.text.include?("Thank you for signing up")
fail_message = "It should show this message:\n" +
"- Thank you for signing up"
assert(test_condition, fail_message)
# (...)
end
# (...)
end
-------------------- end of test.rb
Depois:
-------------------- test.rb
# requires
require "test/unit"
require "watir"
require "watir/testcase"
# set variables
$Index_page = "http://localhost/myapp/index.jsp"
$New_email = "test@provider.com"
$New_password = "testing"
# messages
$Success_message = "Thank you for signing up"
# (...)
##
# Test case for sign up scenarios
# @author Tiago Romero Garcia
#
class Test < Watir::TestCase
# Method for setting up a test method
def setup
@browser = Watir::Browser.start $Index_page
@browser.maximize
end
# (...)
# Method for testing sign up
def test_sign_up
# (...)
# tries to sign up
@browser.text_field(:id, "emailId").set($New_email)
@browser.text_field(:id, "passwordId").set($New_password)
@browser.text_field(:id, "confirmPasswordId").set($New_password)
@browser.button(:name, "register").click
# (...)
# checks if sign up was successful
test_condition = @browser.text.include?($Success_message)
fail_message = "It should show this message:\n" +
"- " + $Success_message
assert(test_condition, fail_message)
# (...)
end
# (...)
end
-------------------- end of test.rb
4. Quebrar a lógica em métodos privados para promover reutilização
Esta prática é bem conhecida e utilizada em outras plataformas, e aqui não tem nada de novo: é boa prática que o desenvolvedor identifique blocos de código repetitivos, e extraia tais blocos em métodos privados que serão chamados por outros.
Blocos de código repetitivos comuns em scripts WATIR: ação de login, ação de logoff, ação de clicar em um botão ou link e testar se houve sucesso, ação de preencher um determinado formulário e testar se houve sucesso, ação de aceitar a falta de certificado quando estiver usando HTTPS, etc.
Mesmo que inicialmente não haja outros métodos chamando um bloco de código potencialmente repetitivo, ainda pode ser bom extrair para um método por motivos de legibilidade e mantenibilidade do código.
5. Isolar a discrepância entre IE e Firefox em métodos polimórficos usando o design pattern Strategy
A versão 1.6.2 introduziu um grau de proximidade nunca antes alcançado entre o código feito para o IE e Firefox. Entretanto, muitos detalhes ainda são dependentes de browser, principalmente devido ao modo pelo qual WATIR se comunica com cada browser (IE usa a API do Windows e Firefox usa o plugin JSSH).
Desta forma, para determinadas operações, teremos que construir código específico de browser, como manipulação de pop-ups e DOM.
Mesmo assim, é possível evitar de, para um mesmo teste, construir um test case para IE e outro para Firefox. A solução é simples: aplicação do design pattern Strategy (Estratégia).
Para isto, basta fazer o seguinte:
- Definir a Estratégia: Definir um método polimórfico que invocará a operação específica de browser, que será invocado na classe do test case. Lembre-se que em Ruby não existem interfaces como em Java, portanto para isto funcionar, basta escrever da mesma forma este método polimórfico nas classes que implementarem a Estratégia.
- Escrever as Estratégias: Criar as classes que irão conter o código específico de browser, dentro do método polimórfico anteriormente definido (implementação da Estratégia).
- Usar a Estratégia: Agora, a classe do test case deverá construir ou receber um objeto de uma classe que implementou a Estragégia. Com este objeto, invocar o método polimórfico ao invés dos trechos de código específico de browser.
Exemplo:
1. Definir a Estratégia: é necessário um método para clicar em um botão que exibirá um popup em javascript, e checar se este popup possui uma determinada mensagem. Nosso método será:
clickAndCheckPopup(message)
2. Escrever as Estratégias: escreveremos 2 classes para manipular código específico, uma para IE e outra para Firefox:
-------------------- ie_handler.rb
require "Win32API"
require 'dl/import'
require 'dl/struct'
require "watir/win32"
require "watir/contrib/enabled_popup"
##
# Handler for IE-specific code (strategy design pattern).
# Its goal is to promove independency to test cases.
# They shall not know particularities from browsers.
#
# @author Tiago Romero Garcia
#
class IEHandler
private
# Method for investigating a popup
def popup_checker(button_text, message, waitTime=9)
# get a handle if one exists
hwnd = @browser.enabled_popup(waitTime)
if (hwnd) # yes there is a popup
w = WinClicker.new
# I put this in to see the text being input it is not necessary
# to work "OK" or whatever the name on the button is
test_condition = w.getStaticText_hWnd(hwnd).include?(message)
w.clickWindowsButton_hwnd( hwnd, "#{button_text}" )
# this is just cleanup
w=nil
return test_condition
end
end
public
def initialize(browser)
@browser = browser
end
def clickAndCheckPopup(message)
@browser.button(:id, "myButtonId").click_no_wait
puts "Clicked on my button"
popup_checker("OK", message, 7)
end
end
-------------------- end of ie_handler.rb
-------------------- firefox_handler.rb
require "test/unit"
##
# Handler for Firefox-specific code (strategy design pattern).
# Its goal is to promove independency to test cases.
# They shall not know particularities from browsers.
#
# @author Tiago Romero Garcia
#
class FirefoxHandler
public
def initialize(browser)
@browser = browser
end
def clickAndCheckPopup(message)
@browser.startClicker("OK")
@browser.button(:id, "myButtonId").click
puts "Clicked on my button"
popupText = @browser.get_popup_text
popupText.include?(message)
end
end
-------------------- end of firefox_handler.rb
3. Usar a Estratégia: a classe do test case ficará assim:
# requires
require "test/unit"
require "watir"
require "watir/testcase"
require "tests/extras/ie_handler"
require "tests/extras/firefox_handler"
# set variables
$Index_page = "http://localhost/jsp/index.jsp"
$Success_message= "Sucessfully operation!"
##
# My test case
# @author Tiago Romero Garcia
#
class MyTest < Watir::TestCase
# Define private methods
private
# Method for setting up a test method
def setup
puts "------------------------------------------------------------"
@browser = Watir::Browser.start $Index_page
@browser.maximize
if @browser.kind_of?Watir::IE
@browser_handler = IEHandler.new @browser
else
@browser_handler = FirefoxHandler.new @browser
end
end
# Method for tearing down a test method
def teardown
@browser.close
end
# (...)
# Define public methods
public
# Method for testing my stuff
def test_my_stuff
puts "TEST METHOD BEGIN - My stuff"
# (...)
test_condition = @browser_handler.clickAndCheckPopup $Success_msg
fail_message = "It should show this string:" +
"\n-" + $Success_msg
assert(test_condition, fail_message)
# (...)
puts "TEST METHOD END WITH SUCCESS - My stuff"
end
# (...)
end
6. Escrever no console mensagens de controle e andamento
Geralmente, para todo processo em lote é interessante exibir mensagens de controle e andamento (log). As principais vantagens dessa prática são:
- Em caso de erro, saber qual o ponto da execução que originou o problema.
- Ter mais detalhes ao acompanhar a execução do processo “ao vivo”.
- Documentar todos os passos do processo.
Portanto, é boa prática escrever no console mensagens de texto detalhando o que acabou de ser feito. Isto é especialmente útil após ações como: carregar uma página, clicar em botão ou link, enviar formulário, escolher dentre uma lista de seleção, etc.
7. Evitar ter de manipular mensagens pop-up javascript
Mensagens pop-up javascript contendo texto são uma fonte de problemas, quando se deseja compatibilidade plena entre IE e Firefox. Isto se dá pois cada browser manipula de forma diferente seus pop-ups, e ainda assim em momentos diferentes!
Por exemplo, na boa prática n° 5, o código para IE dentro de ie_handler.rb executa um programa externo ao browser que será um listener de pop-ups javascript. Possui uma vantagem: consegue capturar pop-ups mesmo que o browser mude de página.
Já o trecho para Firefox dentro de firefox_handler.rb realiza o listening dentro do código. Este não consegue capturar pop-ups se o browser mudar de página.
A boa prática aqui é evitar o máximo possível de se usar este critério para avaliar os resultados de um teste. Em caso de ser uma mensagem revelando o resultado de algum processamento, verifique se após este processamento a página sofrerá alguma alteração. Se sofrer, ao invés de avaliar a mensagem do pop-up, pode-se avaliar se a página sofreu tal alteração.
8. Cuidado com inclusões de páginas
Sites programados em linguagens de scripting server-side como JSP e PHP permitem a inclusão de páginas em outras. Esta é uma prática muito utilizada, especialmente para a reutilização de trechos estáticos do site como menu, cabeçalho e rodapé.
Entretanto, isto pode bagunçar demais a cabeça do programador WATIR, pois pode ser que alguma página incluída tenha tags com as mesmas características que tags na página que as inclui. Assim, ao procurar por tags na página, pode ser que WATIR encontre primeiro tags das páginas incluídas!
As boas práticas para evitar este problema são:
- Quando lidar com páginas que incluam outras páginas, verifique o HTML gerado no browser se a tag que você quer achar será realmente identificada pelo critério que você escolheu. Exiba o código-fonte do browser e faça uma busca no texto para ver se não encontra outras tags com o mesmo critério.
- Escreva tags com ids realmente “identificadores”, ou seja, que descrevam realmente o propósito da tag, mesmo que seja um nome longo. Por exemplo, ao invés de fazer
<input type="text" id="name">
prefira fazer
<input type="text" id="signupFormName">
Assim, você reduz a chance de acontecerem problemas assim.
9. Recuperar mensagens de arquivos de texto
Aplicações web podem recuperar mensagens de arquivos de texto ou arquivos properties, principalmente quando utiliza-se internacionalização (i18n). Quando for este caso, é uma boa prática recuperar as mensagens diretamente destes arquivos, e não escrevê-las novamente na classe de teste. Quanto menos hard-coded puderem ser as mensagens, melhor!
Eu utilizo para isto a seguinte classe leitora de arquivos properties de java, disponível neste link. Agradeço a Devender Gollapally por permitir a reprodução e utilização de seu código aqui.
-------------------- java_props.rb
#
# Class for handling java-style properties files
# Author: Devender Gollapally
# From: http://devender.wordpress.com/2006/05/01/reading-and-writing-
java-property-files-with-ruby/
#
class JavaProps
attr_reader :file, :properties
#Takes a file and loads the properties in that file
def initialize file
@file = file
@properties = {}
IO.foreach(file) do |line|
@properties[$1.strip] = $2
if line =~ /([^=]*)=(.*)\/\/(.*)/ || line =~ /([^=]*)=(.*)/
end
end
#Helpfull to string
def to_s
output = "File Name #{@file} \n"
@properties.each {|key,value| output += " #{key}= #{value} \n" }
output
end
#Write a property
def write_property (key,value)
@properties[key] = value
end
#Save the properties back to file
def save
file = File.new(@file,"w+")
@properties.each {|key,value| file.puts " #{key}= #{value} \n" }
end
end
-------------------- end of java_props.rb
10. Ao testar inclusões com sucesso, realizar a limpeza depois
Quando é necessário testar uma página de inclusão, o fluxo principal deve ser testado, ou seja, uma inclusão deve ser feita com sucesso. Geralmente são utilizados dados de teste para isto.
Isto tem uma contrapartida: os dados ficam armazenados no banco de dados, e isto representa lixo. Quanto mais vezes o teste for executado, mais lixo será gerado e assim vai…
O ideal é que seja realizada uma limpeza imediatamente após o sucesso do teste. Deve ser projetada uma maneira segura para isto, que não coloque a integridade dos dados em risco.
Uma possibilidade para isto é construir um processo de negócio para essa limpeza, e disponibilizar uma URL que delegue a chamada a este processo de negócio. Em uma arquitetura Java EE, o processo de negócio pode ser um método de um EJB, ou um serviço de um BusinessObject; e a URL pode chamar um servlet que invoque tal EJB ou BusinessObject.
Sugiro as seguintes etapas:
- Passagem por parâmetro da URL do identificador do dado recém-incluído
- Autenticação do usuário de teste (ou do cliente que estiver chamando a URL)
- Deleção do dado recém-incluído baseado no identificador
Terça-feira, 17 de Fevereiro de 2009 at 21:30
[...] post anterior 10 boas práticas em Ruby WATIR, a Boa Prática 7 - Evitar ter de manipular mensagens pop-up javascript enfatiza conseqüências e alternativas para isso. [...]
Sábado, 21 de Fevereiro de 2009 at 10:27
[...] no Ruby (mostrei uma implementação disso no post anterior 10 boas práticas em Ruby WATIR, na Boa Prática 9 - Recuperar mensagens de arquivos de texto), mas seria ideal se houvesse algum recurso similar nativo do [...]