Artigo .net Magazine 53 - Testes Unitários - Mocks e Stubs

A finalidade deste artigo é explicar o conceito de testes unitários com mocks e stubs.

Esse artigo faz parte da revista .NET Magazine edição 53. Clique aqui para ler todos os artigos desta edição

Clique aqui para ler esse artigo em PDF.

Projeto/Análise - Expert

Testes Unitários

Mocks e Stubs – Parte 1

 

Neste artigo veremos

·Testes unitários;

·Mocks e Stubs;

·.Net 3.5, Visual Studio 2008 e VB 9.0;

·Rhino Mocks.

Qual a finalidade

·Explicar o conceito de testes unitários com mocks e stubs.

Quais situações utilizam esses recursos?

·Criação de testes unitários para quaisquer projetos que envolvam uma boa separação de responsabilidades.

 

Resumo do DevMan

Testes unitários são importantes para garantir a qualidade do código. Neste artigo, aprenda a fazer testes unitários com Mocks e Stubs, e entre em contato com conceitos de OO como injeção de dependência e separação de responsabilidades.

 

Falar de testes unitários é sempre interessante e tem ficado ainda mais interessante recentemente. A comunidade de desenvolvimento e as empresas têm se esforçado para criar soluções melhores, e novas abordagens têm surgido a cada dia.

Quando falamos de testes unitários logo vêm à mente termos como TDD, BDD, Stubs, Mocks, entre diversos outros. Ferramentas também surgem freqüentemente para apoiar todos esses nomes e siglas.

Neste artigo veremos porque usar testes unitários, como criar uma aplicação testável, e como utilizar ferramentas que nos auxiliem nos testes. Começaremos com uma aplicação simples, do tipo que vemos todos os dias, e vamos buscar entender porque a abordagem mais comum no desenvolvimento de sistemas traz problemas graves na hora de aplicarmos testes. Veremos também como alguns padrões de projeto (Design Patterns) vão nos ajudar a criar aplicações mais testáveis (falamos de Design Patterns em uma série de cinco artigos que foi da edição 47 à 51).

 

Aplicação de exemplo

Vamos montar uma aplicação de exemplo para podermos apoiar os futuros testes. A aplicação será extremamente simples. Para que possamos focar na solução de testes unitários, utilizaremos um banco de dados de exemplo da Microsoft para SQL Server 2005 e 2008 chamado AdventureWorks, disponível via Web no Codeplex no endereço http://www.codeplex.com/MSFTDBProdSamples/Release/ProjectReleases.aspx?ReleaseId=4004 . A aplicação estará separada em 6 camadas, sendo 4 projetos, gerando por isso 4 dlls, uma destas camadas sendo a de apresentação, nesse caso Web. Hoje esse tipo de interface é a mais comum (e portanto mais conhecida de todos), mas poderíamos utilizar qualquer outra.

O projeto tratará de exibir dados das tabelas SalesOrderHeader e Contact do banco AdventureWorks. A exibição será feita em um único formulário Web, conforme a Figura 1. Neste formulário, o grid exibe as colunas Order ID, Order Date e Total Due que têm a origem dos dados na tabela SalesOrderHeader, em colunas de nome semelhante, e a coluna Customer, que vêm das colunas Contact.FirstName e Contact.LastName concatenadas. Será possível paginar os dados com um engine customizado, e ir a uma página específica. O código desta página (default.aspx) e do code behind (default.aspx.vb) estão na Listagem 1. Este código serve apenas para que possamos montar a aplicação, mas não será importante para o objetivo do artigo, servindo apenas para exemplificar a interação da camada de apresentação com as outras camadas (procure a palavra-chave new e encontrará esses relacionamentos). Note que os únicos métodos de negócio chamados são os métodos GetOrders e GetQuantityOfOrders. O código da default.aspx contém somente as partes importantes, como o gridview e os outros controles, e o exemplo segue o inglês, já que os dados e os elementos do banco também estão nesta língua.

 

Figura 1. Resultado final da solução

 

Listagem 1. Arquivos default.aspx e default.aspx.vb

 

<asp:GridView ID="gvOrders" runat="server" AutoGenerateColumns="False">

<Columns>

<asp:BoundField DataField="SalesOrderID" HeaderText="Order ID" />

<asp:BoundField DataField="OrderDate" HeaderText="Order Date"

  DataFormatString="{0:d}"  />

<asp:TemplateField HeaderText="Customer">

<ItemTemplate>

<asp:Label ID="Label1" runat="server" Text='<%# Eval("Contact.FirstName") & " " & Eval("Contact.LastName") %>'></asp:Label>

</ItemTemplate>

</asp:TemplateField>

<asp:BoundField DataField="TotalDue" HeaderText="Total Due"

DataFormatString="{0:F}" />

</Columns>

</asp:GridView>

<br />

<asp:LinkButton ID="lnkPrevious" runat="server">&lt;</asp:LinkButton>

&nbsp;<asp:LinkButton ID="lnkNext" runat="server">&gt;</asp:LinkButton>

&nbsp;<asp:LinkButton ID="lnkbutGoToPage" runat="server">Go to page:</asp:LinkButton>

&nbsp;<asp:TextBox ID="txtGoToPage" runat="server" MaxLength="5" Width="38px">1</asp:TextBox>

<asp:RequiredFieldValidator ID="rvGoToPage" runat="server"

ControlToValidate="txtGoToPage" Display="Dynamic"

ErrorMessage="Insert a Number">*</asp:RequiredFieldValidator>

<asp:CompareValidator ID="cvGoToPage" runat="server"

ControlToValidate="txtGoToPage" Display="Dynamic"

ErrorMessage="Insert a valid number" Operator="DataTypeCheck">*</asp:CompareValidator>

<br />

<br />

Pages:

<asp:Label ID="lblCurrentPage" runat="server" Text="undefined"></asp:Label>

&nbsp;/<asp:Label ID="lblPages" runat="server" Text="undefined"></asp:Label>

<asp:ValidationSummary ID="ValidationSummary1" runat="server" />

 

Partial Public Class _Default

Inherits System.Web.UI.Page

 

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

If Not Page.IsPostBack Then

Dim bsOrders As New Business.Order()

Dim intQtty = bsOrders.GetQuantityOfOrders()

PagesQuantity = CInt(Math.Ceiling(intQtty / PageSize))

LoadGrid()

End If

End Sub

 

Property PagesQuantity() As Integer

Get

Return DirectCast(ViewState("PagesQuantity"), Integer)

End Get

Set(ByVal value As Integer)

ViewState("PagesQuantity") = value

lblPages.Text = Me.PagesQuantity.ToString()

End Set

End Property

 

Private Sub LoadGrid()

 

Dim bsOrders As New Business.Order()

Dim orders = bsOrders.GetOrders(CurrentPage, PageSize)

gvOrders.DataSource = orders

gvOrders.DataBind()

bsOrders.GetQuantityOfOrders()

 

End Sub

 

Private Const PageSize As Integer = 10

 

Public Property CurrentPage() As Integer

Get

If ViewState("CurrentPage") Is Nothing Then

Me.CurrentPage = 0

End If

Return DirectCast(ViewState("CurrentPage"), Integer)

End Get

Set(ByVal value As Integer)

If value >= 0 Then

If value >= PagesQuantity - 1 Then

ViewState("CurrentPage") = PagesQuantity - 1

 

Else

ViewState("CurrentPage") = value

End If

Else

ViewState("CurrentPage") = 0

End If

lblCurrentPage.Text = CStr(CurrentPage + 1)

End Set

End Property

 

Protected Sub lnkPrevious_Click(ByVal sender As Object, ByVal e As EventArgs) Handles lnkPrevious.Click

CurrentPage -= 1

LoadGrid()

End Sub

 

Protected Sub lnkNext_Click(ByVal sender As Object, ByVal e As EventArgs) Handles lnkNext.Click

CurrentPage += 1

LoadGrid()

End Sub

 

Protected Sub lnkbutGoToPage_Click(ByVal sender As Object, ByVal e As EventArgs) Handles lnkbutGoToPage.Click

CurrentPage = CInt(txtGoToPage.Text) - 1

LoadGrid()

End Sub

End Class

 

Como já vimos, a solução como um todo tem quatro projetos, expostos na Tabela 1. Os projetos têm poucas classes, expostas em um diagrama de classes na Figura 2. Nesta figura temos em vermelho as classes do projeto Web, em azul as classes de negócio, em verde as classes da camada de acesso a dados, em amarelo as classes da camada de dados e em roxo as classes da camada de utilidades. Utilizaremos LINQ to SQL para montar a camada de acesso a dados e a camada de entidades de negócio. Infelizmente a ferramenta impede a separação de projetos destas duas camadas, diferentemente do que é possível fazer hoje com os Datasets tipados no Visual Studio 2008, onde é possível colocar os datasets em um projeto diferente do projeto dos TableAdapters. O ideal seria termos estas camadas separadas também no LINQ to SQL, evitando assim uma referência direta entre o projeto de apresentação e de acesso a dados. Para melhor separá-los, separei os namespaces com a IDE do Visual Studio de LINQ to SQL, como veremos mais para frente.

A Figura 2 exibe também as dependências entre as camadas, ficando assim bem claro que uma classe depende explicitamente da outra para que possa funcionar, e que uma mudança na interface de uma classe pode afetar outra. O diagrama deixa claro que todas as classes dependem diretamente umas das outras. Dessa forma, qualquer mudança nas classes de DAL afeta toda a aplicação (isso quer dizer que ela precisaria ser inteiramente retestada em caso de uma mudança pontual). Estão ocultas as classes de Contact do namespace Dados (entidade de negócios, gerada pelo LINQ to SQL), e a classe Contact do namespace DAL (DAL ou Data Access Layer - camada de acesso a dados), para simplificar o diagrama. Sua responsabilidade é semelhante a da classe Order nos mesmos namespaces. A Tabela 2 anuncia a responsabilidade de cada classe.

 

"

Projeto

Objetivo

AppWeb

Projeto do tipo Web application, responsável pela interface com o usuário.

Business

Projeto do tipo class library, responsável pela camada de negócio.

[...] continue lendo...
Ebook exclusivo
Dê um upgrade no início da sua jornada. Crie sua conta grátis e baixe o e-book

Artigos relacionados