Como usar TDD e Page Objects para construir interfaces web
Posted by Ivan Sanchez em Domingo, Abril 20, 2008
Quando o assunto é interface web, a maioria dos desenvolvedores sabe o quão complicado é escrever testes automatizados, e muita gente simplesmente acaba deixando de lado esse assunto na prática, talvez por não saber o tamanho da irresponsabilidade que isto significa. Como o Vínicius da ImproveIt já bem disse:
Desenvolver software sem testes não é apenas coisa de fanfarrão. É coisa de irresponsável. É impossível uma atividade tão complexa quanto desenvolver software ser conduzida sem testes automatizados, em uma quantidade absurda. Quer dizer, possível é, mas não é aceitável.
No caso específico de interfaces web, muitos usuários do Selenium já passaram pelo extremo da empolgação, quando em 5 minutos se instala a Selenium IDE no seu Firefox e sai gravando tudo que a sua aplicação pode fazer, e acabaram no desespero total quando a coleção de testes começa a aumentar e, mesmo depois de começar a usar o Selenium RC, manter os testes fica cada vez mais difícil a cada nova mudança. E o desenvolvimento guiado por testes por onde andou durante toda esta jornada? Provavelmente restrito a testes de unidade e integração, ou seja, sem tocar na interface com o usuário.
O engraçado é que a solução para tudo isso está bem mais perto do que a gente imagina: é a velha programação orientada a objetos, representada neste caso pelo padrão Page Objects (PO). A idéia é simples:
Representar os elementos da interface com o usuário como uma série de objetos que se comunicam entre si.
Se você prestou atenção, viu que este padrão está na documentação do WebDriver, uma outra ferramenta para automatizar interação com browsers, mas que para este post não tem muita importância, desde que você consiga conectar seus Page Objects com a aplicação real. Para provar isso, aqui vai um exemplo usando selenium-rc:
public class GoogleTest { private Selenium selenium; @Before public void setUp() throws Exception { selenium = new DefaultSelenium("localhost", 4444, "*firefox", "http://www.google.com/webhp?hl=en"); selenium.start(); } @Test public void codingDojoShouldBeInFirstPageOfResults() { GoogleHomePage home = new GoogleHomePage(selenium); GoogleSearchResults searchResults = home.searchFor("coding dojo"); String firstEntry = searchResults.getResult(0); assertEquals("Coding Dojo Wiki: FrontPage", firstEntry); } @After public void tearDown() throws Exception { selenium.stop(); } }
Este exemplo usa dois POs bastante simples (GoogleHomePage e GoogleSearchResults):
public class GoogleHomePage { private final Selenium selenium; public GoogleHomePage(Selenium selenium) { this.selenium = selenium; this.selenium.open("http://www.google.com/webhp?hl=en"); if (!"Google".equals(selenium.getTitle())) { throw new IllegalStateException("This is not the Google Home Page"); } } public GoogleSearchResults searchFor(String string) { selenium.type("q", string); selenium.click("btnG"); selenium.waitForPageToLoad("5000"); return new GoogleSearchResults(string, selenium); } }
public class GoogleSearchResults { private final Selenium selenium; public GoogleSearchResults(String string, Selenium selenium) { this.selenium = selenium; if (!(string + " - Google Search").equals(selenium.getTitle())) { throw new IllegalStateException( "This is not the Google Results Page"); } } public String getResult(int i) { String nameXPath = "xpath=id('res')/div[1]/div[" + (i + 1) + "]/h2/a"; return selenium.getText(nameXPath); } }
Quais as vantagens desta abordagem?
É possível guiar seu desenvolvimento usando testes em cima de POs.
Imagine a interação do usuário com a aplicação e crie um modelo de objetos para representar essa interação. Verifique este modelo na forma de um novo teste. Enquanto os elementos da interface não existirem o teste vai falhar, então os crie baseando-se no modelo para fazer o seu teste passar. Escreva novos testes e faça-os passarem aos poucos, e nunca inclua algo na tela que não foi descrito na forma de testes.
É muito mais fácil manter classes do que scripts.
Não demorará muito para você começar a reutilizar seus POs e poder aplicar todos os recursos de refatoração que você já conhece. Novos testes poderão ser incluídos mais facilmente e mudanças no layout afetarão apenas POs específicos.
Então, ainda existe alguma razão para não escrever testes para a interface com o usuário? Espero que não, mas se tiver, me avise!
leandro said
olá, gostei muito deste post em particular.
tentei fazer a instalação e tudo, mas só tá me dando dor de cabeça.
por acaso, não terias um tutorialzinho, um “how-to” pra baixar e instalar as bibliotecas?
principalmente se for para o ubuntu.
🙂
valeu,
leandro
Ivan Sanchez said
Olá Leandro,
Tudo que você precisa para rodar o exemplo que eu postei está aqui.
Para compilar seus testes inclua o selenium-java-client-driver.jar no seu classpath.
Para executá-los você precisa do selenium server rodando. Para iniciá-lo use o comando:
java -jar selenium-server.jar
Se tiver algum problema é só avisar…
Abraço!
Leandro said
Olá Ivan,
Está tudo certo agora.
Obrigado.
Leandro
BrunoPedroso said
Bem legal. Muito simples e útil.
Os detalhes e a complexidade da interface ficam contidos nos POs. É muito comum mudarmos besteiras na interface e quebrar vários testes a toa.
Valeu pela dica!
William said
Boa tarde,
Gostei muito do artigo, gostaria de saber se poderia me ajudar a compreendendo algumas coisa.
Como faço para compilar o exemplo dado? (GoogleHomePage e GoogleSearchResults)
qual comando tenho que dar para executar o exemplo? O Servidor eu já consegui deixar rodando.
Obrigado e parabéns pelo artigo.
Ivan Sanchez said
Olá William,
Você precisa do client driver do selenium-rc para compilar os Page Objects, e para rodar basta executar o teste do jUnit (GoogleTest) na sua IDE ou usando essas instruções
Espero ter ajudado.
Ivan
Seus testes com Selenium são muito lentos? Eu tenho a solução! « Coding Dojo Floripa said
[…] Como usar TDD e Page Objects para construir interfaces web […]
William said
Boa tarde Ivan,
Muito obrigado pela dica.
Acho que estou quase conseguindo mas falta alguma coisa, o Selenium IDE é simples, mas o Selenium RC não achei a forma correta de rodar, ainda bem que existe pessoas experiente como você para compartilhar conosco.
No momento tenho o seguinte:
1- Estou usando o Windows
2- O servidor rodando pelo comando
java.exe -jar selenium-server.jar
Esta localizado em minha maquina na pasta:
C:\PASTA\selenium-remote-control-1.0-beta-1\selenium-server-1.0-beta-1
3- tenho tambem o client driver localizado em:
C:\PASTA\selenium-remote-control-1.0-beta-1\selenium-java-client-driver-1.0-beta-1
4- jUnit em
C:\PASTA\junit
5- O arquivo GoogleSearchResults.java em (Exemplo mostrado acima)
c:\PASTA\teste
Pergunto:
Quais passos devo fazer para rodar o exemplo?
É atraves de linha de comando?
Os SET CLASSPATH já foram feitos.
Mais um vez obrigado
Rodrigo Manhães said
Para quem tentar rodar o código, a estrutura da página de resposta do Google mudou, de modo que o xpath deve ser substituído por xpath=id(‘res’)/li[” + (i + 1) + “]/h3/a
Otávio Landim said
Olá,
Bom post, simples e prático…
Vlw.