Oi pessoal, já faz algum tempo desde a última vez que eu postei aqui no blog. Infelizmente eu estive ocupado com a minha mudança para o exterior e não pude dedicar muito tempo aqui. Finalmente consegui ajustar toda minha rotina  e consequentemente voltei a ter tempo para compartilhar com vocês a pequena quantidade de conhecimento que eu possuo.

Pois bem, chega de lero-lero e vamos ao que realmente interessa. Neste artigo vamos discutir sobre testes de regressão de interface e como automatizá-los. Abaixo uma definição de teste de regressão de acordo com o site Eclipse.org:

O teste de regressão é o ato de assegurar-se que as mudanças feitas no código não afetaram nenhuma funcionalidade já existente. É importante reconhecer que o desenvolvimento incremental faz com que o teste de regressão seja crítico. Sempre que se libera uma aplicação deve-se assegurar que as funcionalidades pré-existentes continuem funcionando. Quando do uso da abordagem incremental as aplicações são liberadas com mais frequência, significando que os testes de regressão tornam-se muito mais importantes.

Agora que temos o conceito do que é um teste de regressão, vamos falar sobre como podemos implementar esse tipo de testes de forma automatizadas em projetos de Web Applications.

Selenium IDE (E porque não vamos falar dele neste artigo)

O Selenium IDE é uma ferramenta de automação de testes de interfaces web que já existe no mercado há algum tempo. Você pode instalar a última versão oficial em seu Firefox (Sim, ele é uma extensão que funciona somente no Firefox) e começar a gravar e reproduzir alguns testes em seu navegador.

Contudo, para grandes projetos, esta não é a opção ideal. A medida que as funcionalidades do sistema vão aumentando e a quantidade de testes vão crescendo, fica difícil dar manutenção nestes testes de interface, sendo praticamente impossível reutilizar os passos que são executados a fim de se chegar a determinada parte/seção/funcionalidade do aplicativo Web.

Meu pequeno resumo sobre o Selenium IDE: É uma ferramenta interessante, boa para testers de sistemas pequenos (aqueles produzidos em pequenas fábricas de Software), mas não é uma ferramenta robusta para utilizarmos em grandes projetos com aplicativos que possuem uma grande quantidade de recursos, muitos usuários e com um ciclo de vida relativamente grande. É uma grande ferramenta para testers que possuem conhecimento ZERO de programação, mas existem soluções mais sofisticadas e adequadas no mercado e vamos discutir sobre uma delas logo abaixo.

Selenium Webdriver

Felizmente a equipe do Selenium possui uma solução muito mais robusta a oferecer para os desenvolvedores e equipes de qualidade de software. Podemos programar nossos testes de UI (Sim, desenvolver código para testes! Isto é emocionante!) utilizando uma biblioteca oferecida por eles.

Esta biblioteca é conhecida como Selenium Webdriver, e nos primórdios era disponibilizada como Selenium RC, mas foi refatorada e melhorada com o passar dos anos.

No decorrer do artigo estaremos utilizando o Selenium Webdriver para desenvolver um projeto de testes de exemplo completo para que você possa aplicar técnicas mais avançadas e robustas de testes de UI em seus projetos.

Configurando o Workspace

Antes de começarmos a trabalhar em nosso projeto, é necessário instalar uma extensão para o Visual Studio. Esta extensão se chama NUnit Test Adapter e ela é responsável por detectar e rodar os testes de nossa Solution.

Com o Visual Studio iniciado, vá até o menu Ferramentas => Extensões e Atualizações.

Clique na opção "Online" e pesquise por "NUnit"

Clique na opção “Online” e pesquise por “NUnit”.

Clique na opção "Download" e autorize a instalação da extensão. Após instalada, reinicie o Visual Studio.

Clique na opção “Download” e autorize a instalação da extensão. Após instalada, reinicie o Visual Studio.

 

Atenção:

Nós vamos precisar do Firefox para executar nossos testes. Se você não possui ele em seu computador você pode efetuar o download do instalador através do seguinte link:

https://www.mozilla.org/en-US/firefox/new/

Criando o Projeto

Agora que instalamos com sucesso a extensão, podemos dar continuidade ao nosso projeto de exemplo para o artigo. Vamos começar criando um novo projeto do tipo Class Library.

Crie um projeto do tipo Class Library em C# e nomeie ele como "TestsUI".

Crie um projeto do tipo Class Library em C# e nomeie ele como “TestsUI”.

Agora que temos o projeto criado, vamos adicionar as referências das bibliotecas da API do Selenium Webdriver. Siga os próximos passos:

Clique com o botão direito em "References" e selecione a opção "Manage Nuget Packages".

Clique com o botão direito em “References” e selecione a opção “Manage Nuget Packages”.

O gerenciador de pacotes Nuget irá se abrir. É necessário que você adicione os seguintes pacotes:

  • NUnit;
  • Selenium Webdriver;
  • Selenium Support Classes.

Para adicionar estes pacotes, basta seguir o que foi feito nas imagens a seguir:

Procure na opção "Online" pelo pacote "Nunit". Instale a primeira opção que está em destaque na imagem.

Procure na opção “Online” pelo pacote “Nunit”. Instale a primeira opção que está em destaque na imagem.

Procure por "Selenium" e adicione os 2 primeiros resultados da pesquisa ao projeto.

Procure por “Selenium” e instale os pacotes “Selenium Webdriver” e “Selenium Webdriver Support Classes” ao projeto.

Pronto! Agora temos todos os pacotes necessários para começarmos a desenvolver nosso projeto de testes. A sua lista de referências deve estar parecida com o que segue abaixo:

Lista de referências do projeto de testes.

Lista de referências do projeto de testes.

Antes de finalizarmos esta seção de criação do projeto, vamos criar 3 novas pastas:

  • Configuration;
  • Pages;
  • TestCases.

A estrutura do seu projeto deve ficar assim:

Estrutura de pastas do projeto.

Estrutura de pastas do projeto.

Configuração básica para execução dos testes

Agora que temos nossa estrutura do projeto criado e todas referências necessárias adicionadas, é hora de começar a codificação. O NUnit disponibiliza dois eventos referentes aos testes que são:

  • Setup: Executado toda vez que uma classe de testes for instanciada;
  • TearDown: Executado toda vez que uma classe de testes for finalizada.

Pois bem, agora que conhecemos estes dois eventos, vamos implementar uma classe Base para os nossos testes. Crie uma nova classe dentro da pasta Configuration e nomeie esta classe como BaseUITest. O código da implementação segue abaixo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Support.PageObjects;

namespace TestsUI.Configuration
{
    public abstract class BaseUITest
    {
        public static IWebDriver Driver { get; private set; }

        [SetUp]
        public void SetupTest()
        {
            if (BaseUITest.Driver == null)
            {
                Driver = new FirefoxDriver();
            }

            Driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(30));
            Driver.Manage().Timeouts().SetPageLoadTimeout(TimeSpan.FromSeconds(30));
            Driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromSeconds(30));

            Driver.Manage().Window.Maximize();
        }

        [TearDown]
        public void TeardownTest()
        {
            try
            {
                Driver.Quit();
                Driver = null;
            }
            catch (Exception)
            {
                // Ignore errors if unable to close the browser
            }
        }
    }
}

Utilizaremos essa classe mais a frente quando implementarmos nosso primeiro método de testes. O código acima é responsável por criar um driver para o Firefox (Existe a possibilidade de criar um driver para o Chrome também, mas neste artigo vamos trabalhar com o Firefox), realizar configurações de Timeout referente à manipulação dos elementos da página e maximizar a janela do navegador que será criada toda vez que um teste for executado.

Por fim, quando os testes são finalizadas, nosso código se encarrega de finalizar o driver do Firefox.

Page Object Model

Antes de prosseguirmos com nosso projeto de testes, é importante entendermos um padrão de design de projetos automatizados de testes de interface. Este padrão é exemplificado no próprio site do Selenium e explicado no próprio site do grande Martin Fowler.

O padrão defende que devemos criar classes que representem as propriedades e funcionalidades das páginas do sistema web com que vamos interagir. Desta forma fica mais fácil reutilizar comportamentos e funcionalidades das páginas, uma vez que estamos programando conforme as funcionalidades que são expostas pelas próprias páginas.

Um resumo sobre este padrão:

  • Representa as telas do seu aplicativo web encapsulando as funcionalidades que são disponibilizadas por suas respectivas páginas;
  • Permite que modelemos a UI em nossos projetos de testes.

Vantagens:

  • Reduz os códigos duplicados;
  • Facilita a leitura do que foi implementado no projeto de testes.
  • Facilita a manutenção do projeto de testes.

Criação de uma página baseada no Page Object Model

A fim de dar continuidade ao nosso exemplo, vamos criar um teste que realize uma pesquisa no Google pelo blog .NET Coders e verificar se o título do primeiro resultado confere com o que esperamos. Este é um cenário hipotético, dificilmente você vai precisar fazer isso na sua vida profissional, mas para o nosso artigo eu acho que está OK.

Antes de implementarmos a página da busca, vamos criar uma página de base para que possamos utilizar ela em todas as nossas novas páginas. Crie uma nova classe e configure seu nome como BasePage. Segue abaixo a implementação da nossa classe BasePage:

using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TestsUI.Configuration;

namespace TestsUI.Pages
{
    public abstract class BasePage
    {
        /// <summary>
        /// Driver to run the UI tests.
        /// </summary>
        protected IWebDriver Driver
        {
            get
            {
                return BaseUITest.Driver;
            }
        }

        /// <summary>
        /// Base URL used for the UI tests.
        /// </summary>
        protected string baseURL { get; set; }

        /// <summary>
        /// Default constructor.
        /// Initializes page objectos within DOM.
        /// </summary>
        public BasePage()
        {
            PageFactory.InitElements(BaseUITest.Driver, this);

            baseURL = "http://www.google.com/";
        }

        /// <summary>
        /// Runs a JS script on the current page.
        /// </summary>
        /// <param name="jsScript">The JS string that holds the script to be executed within the browser.</param>
        protected internal void ExecuteScript(string jsScript)
        {
            ((IJavaScriptExecutor)BaseUITest.Driver).ExecuteScript(jsScript);
        }
    }
}

Nossa classe base para nossas páginas apenas configura nossa URL de nossa WebApplication e possui um método para executar JavaScript. Você pode implementar mais comportamentos padrões para suas páginas conforme a necessidade de seu projeto.

A linha PageFactory.InitElements(BaseUITest.Driver, this); é muito importante pois ela é responsável por inicializar todos os objetos de uma página que vamos mapear logo em seguida.

Enfim, para implementarmos nossa pesquisa, vamos criar uma página que represente a página do Google. Para isto crie uma nova classe dentro da pasta Pages e nomeie ela como GooglePage. O código da implementação desta página (Seguindo o Page Object Model) segue abaixo:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestsUI.Pages
{
    public class GooglePage : BasePage
    {
        [FindsBy(How = How.Id, Using = "lst-ib")]
        public IWebElement txtSearch { get; set; }

        [FindsBy(How = How.Name, Using = "btnG")]
        public IWebElement btnSearch { get; set; }

        [FindsBy(How = How.CssSelector, Using = "#rso a:nth-child(1)")]
        public IWebElement firstResult { get; set; }

        /// <summary>
        /// Goes to Google's main URL.
        /// </summary>
        public void NavigateToMainPage()
        {
            Driver.Navigate().GoToUrl(baseURL);
        }

        /// <summary>
        /// Query a search to Google's official page.
        /// </summary>
        /// <param name="text">The text to be searched.</param>
        public void Search(string text)
        {
            txtSearch.SendKeys(text);
            btnSearch.Click();

            // Waits for browser result.
            Thread.Sleep(3000);
        }
    }
}

Veja que o atributo FindsBy é responsável por localizar um elemento da página em nosso Page Object Model. Podemos localizar através dos seguintes métodos:

  • ClassName
  • Css
  • Id
  • LinkText
  • Name
  • PartialLinkText
  • TagName
  • XPath

Implementando um teste

Agora que nossa página está pronta para realizar a busca, vamos implementar um teste. Para isso crie uma nova classe chamada SearchTest dentro da pasta TestCases. A implementação desta classe segue abaixo:

using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TestsUI.Configuration;
using TestsUI.Pages;

namespace TestsUI.TestCases
{
    [TestFixture]
    public class SearchTest : BaseUITest
    {
        [Test]
        public void UserCanSearchBlogNETCoders()
        {
            string searchText = "Blog netcoders";
            string expectedTitle = ".NET Coders | Um dos maiores e mais ativos grupos de ...";

            GooglePage gPage = new GooglePage();
            gPage.NavigateToMainPage();
            gPage.Search(searchText);

            Assert.AreEqual(expectedTitle, gPage.firstResult.Text);
        }
    }
}

A partir deste momento você já pode compilar o projeto e ir até a aba “Test Explorer” e visualizar o novo teste. Basta rodar e ver os resultados e a tarefa automatizada sendo executada em uma nova instância do Firefox (Se voce tem um problema para visualizar os testes na janela de Tests Explorer, confira a secao “Executando os Testes” logo abaixo).

Criando um mini-framework de testes

Antes de finalizarmos nosso artigo, quero falar um pouco mais sobre como podemos deixar nosso projeto de testes bem encapsulado e objetivo. Vamos criar um pequeno Framework para fazer uso das principais funcionalidades do Webdriver. Desta forma o codigo fica ainda mais reduzido e se precisarmos mudar o Driver no futuro, o trabalho da equipe será menor do que utilizando os métodos da Biblioteca no meio dos Page Object Models.

Vamos utilizar um recurso do C# que se chama Extension Methods. Crie uma pasta na raiz do projeto chamada Helpers. Dentro desta pasta vamos criar duas classes. Nomeie as novas classes como GetterHelpers e SetterHelpers. Abaixo segue o respectivo codigo de cada uma delas:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestsUI.Helpers
{
    /// <summary>
    /// Wrapper class for getter methods.
    /// </summary>
    public static class GetterHelpers
    {
        public static string GetText(this IWebElement element)
        {
            return element.Text;
        }

        public static string GetValue(this IWebElement element)
        {
            return element.GetAttribute("value");
        }

        public static string GetTextFromDDL(this IWebElement element)
        {
            return new SelectElement(element).AllSelectedOptions.SingleOrDefault().Text;
        }
    }
}
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestsUI.Helpers
{
    /// <summary>
    /// Wrapper class for setter methods.
    /// </summary>
    public static class SetterHelpers
    {
        public static void EnterText(this IWebElement element, string value)
        {
            element.SendKeys(value);
        }

        public static void EnterText(this IWebElement element, long value)
        {
            SetterHelpers.EnterText(element, value.ToString());
        }

        public static void EnterTextAndWait(this IWebElement element, string value)
        {
            EnterText(element, value);
            Thread.Sleep(3000);
        }

        public static void DoClick(this IWebElement element)
        {
            element.Click();
        }

        public static void DoClickAndWait(this IWebElement element)
        {
            element.Click();
            Thread.Sleep(5000);
        }

        public static void SelectDropDown(this IWebElement element, string value)
        {
            new SelectElement(element).SelectByText(value);
        }
    }
}

Veja agora como ficou nossa classe que representa nossa página do Google (GooglePage) após atualizar os métodos com a utilização do nosso mini-framework:

using OpenQA.Selenium;
using OpenQA.Selenium.Support.PageObjects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using TestsUI.Helpers;

namespace TestsUI.Pages
{
    public class GooglePage : BasePage
    {
        [FindsBy(How = How.Id, Using = "lst-ib")]
        public IWebElement txtSearch { get; set; }

        [FindsBy(How = How.Name, Using = "btnG")]
        public IWebElement btnSearch { get; set; }

        [FindsBy(How = How.CssSelector, Using = "#rso a:nth-child(1)")]
        public IWebElement firstResult { get; set; }

        /// <summary>
        /// Goes to Google's main URL.
        /// </summary>
        public void NavigateToMainPage()
        {
            Driver.Navigate().GoToUrl(baseURL);
        }

        /// <summary>
        /// Query a search to Google's official page.
        /// </summary>
        /// <param name="text">The text to be searched.</param>
        public void Search(string text)
        {
            txtSearch.EnterText(text);
            btnSearch.DoClickAndWait();
        }
    }
}

Percebam dentro do método Search como ficou muito mais claro e objetivo, além de ter reduzido a quantidade de código.

Executando o teste

Pessoal, agora podemos executar nosso teste. Para isto basta compilar o projeto e o teste deverá ficar visível na barra Test Explorer dentro do Visual Studio.

Atenção:

Caso o método não tenha sido listado na sua janela de Test Explorer, você deve verificar a arquitetura de processador que esta configurada para compilação do seu projeto de teste. Para isso, basta configurar a arquitetura de acordo com a arquitetura do seu build conforme a imagem abaixo:

NUnit Arch

Conclusão

Testes sempre foram parte do processo de desenvolvimento de Software, não importa o tamanho da aplicação. Precisamos tirar vantagem das ferramentas disponíveis e otimizar cada vez mais nossos processos de testes em nosso projetos.

Os testes de regressão são importantes pois ajudam a capturar problemas que podem ser causados por novas implementações do Software.

Um feliz Natal e Ano Novo a todos vocês! Até a próxima!

Links de referência

http://epf.eclipse.org/wikis/openuppt/openup_basic/guidances/guidelines/types_of_developer_tests,_eRutgC5QEduVhuZHT5jKZQ.html

https://code.google.com/p/selenium/wiki/PageObjects

http://martinfowler.com/bliki/PageObject.html

http://stackoverflow.com/questions/29463953/why-does-visual-studio-not-give-me-a-test-in-test-explorer-when-i-build-my-proje

https://msdn.microsoft.com/pt-br/library/bb383977.aspx

Desenvolvedor .NET há mais de 5 anos, apaixonado por Arquitetura e Desenvolvimento de Software.

Atuo hoje como Senior Software Developer em [email protected] Zelândia, mas ao longo da minha carreira profissional trabalhei como Analista de Sistemas e Desenvolvedor em algumas empresas brasileiras e em vários projetos de Software!

Microsoft Certified Professional
MS: Programming in HTML5 with JavaScript and CSS3
MCTS: Web Applications Development with Microsoft .NET Framework 4

Website: http://rafaelpc.com/
LinkedIn: https://br.linkedin.com/in/rafaelpaschoal

Comentários

comentarios