e="FONT-SIZE: 10pt; COLOR: windowtext; FONT-FAMILY: Verdana; mso-no-proof: no; mso-ansi-language: PT-BR">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"><</asp:LinkButton>
<asp:LinkButton ID="lnkNext" runat="server">></asp:LinkButton>
<asp:LinkButton ID="lnkbutGoToPage" runat="server">Go to page:</asp:LinkButton>
<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>
/ <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
...