Este documento discute Test-Driven Development (TDD), incluindo o que é TDD, seus benefícios, como funciona na prática e desafios de implementação. Também fornece um exemplo de como usar TDD para criar uma classe pilha.
Test-Driven Development Oque é? O que não é? E o que isto tem a ver com você? Carlos Eduardo Miranda Arquiteto .Net Add Technologies
2.
Agenda Introdução Porque ela “ainda” não é um padrão amplamente utilizado pelo mercado? TDD - O que é? TDD - Benefícios TDD – O que não é? Mitos sobre o TDD Prática do TDD E caso algum teste tenha falhado? Encontrei um bug, e agora? Tudo Verde! Done! Dificuldades na implantação do TDD na Empresa O que isto tem a ver com você? Glossário Solução – Criação de uma classe de Pilha – Stack
3.
Introdução O assuntonunca esteve tão em voga quanto nos últimos anos; Não se trata de mais apenas um “modismo“ do mercado; Traz diversos benefícios para as empresas e clientes; Maior parte dos projetos de software não atigem os seus objetivos em pelo menos uma das dimensões (tempo, custo, qualidade, escopo); Desenvolver software não é desenvolver um produto físico; Softwares apoiam as áreas de negócio das empresas e estes mercados mudam constantemente; Os requisitos vão mudar!
4.
Por que ela“ainda” não é um padrão amplamente utilizado pelo mercado? Contra-Produtiva e Contra-Intuitiva à primeira vista (“Como assim criar um teste de uma classe que ainda não existe?!”); Desenvolvedores não gostam de criar testes unitários, pois são chatos; Falta de tempo e recursos (cronogramas apertados e poucos recursos); Resistência à mudanças; Desconhecimento por parte de Gerentes e Desenvolvedores; Desenvolvimento ágil ainda é visto com desconfiança e como “sem controle”;
5.
TDD - Oque é? Prática que foi originada nas rodas de desenvolvedores SmallTalk; Amplamente utilizada em metodologias ágeis como o XP (eXtreme Programming) e o Scrum; Prática para “Desenvolvimento de Software” que prega que todo e qualquer desenvolvimento deva ser precedido da criação de uma suíte de testes e que toda duplicação de código deve ser eliminada; Testes são desenvolvidos na mesma linguagem de programação do sistema em desenvolvimento; Testes quando criados podem até mesmo não compilar, neste momento eles não necessitam compilar; A única regra é que os testes sejam criados antes da implementação das funcionalidades do sistema;
6.
TDD - BenefíciosMelhora na interface das classes da aplicação; Maior desacoplamento das classes ; Aumento na Produtividade; Aumento da confiança pelo desenvolvedor em fazer alterações; Possibilidade de fazer Refactor (Refatoração de código); Cobertura de cenários da aplicação; Possibilidade de auto documentação a partir da Suite de Testes; Flexibilidade – maior facilidade em abraçar às mudanças nos requisitos; Deploy simplificado – g arantia de uma versão estável a todo o momento, pronta para subir para a homologação ou produção ;
7.
TDD – Oque não é? Ferramenta de teste; A Resolução de todos os problemas no desenvolvimento de sistemas; Padrões de Projeto (Design Patterns);
8.
Mitos sobre oTDD Test-Driven Development != QA Test-Driven Development != Testes Unitários Test-Driven Development != Design Patterns
9.
Prática do TDD0. Setup (somente executado uma vez no ciclo de vida do projeto); Red – Criar um teste que falhe (eliminar falsos positivos); Green – Criar a implementação mais simples possível que faça o teste passar; Refactor! – Refatorar a implementação, melhorando-a de modo incremental e controlada, fazendo com que o código comunique melhor o seu intento, eliminando duplicidades no código, fazendo as alterações arquiteturais (se estas se mostrarem necess á rias) e e xecutando os testes a cada alteração por menor que esta seja;
10.
E caso algumteste tenha falhado? Desfazer as últimas alterações, e refazer a funcionalidade de modo a não quebrar as funcionalidades construídas previamente; O ideal é que a aplicação não seja quebrada (testes em vermelho) por mais do que poucos minutos; Passos curtos, incrementais e controlados;
11.
Encontrei um bug,e agora? Criar um teste que exponha este cenário descoberto, realizar a prática do TDD ( Red – Green – Refactor! ), assim como qualquer outro teste previamente planejado; No longo prazo com a utilização desta prática teremos um produto cada vez melhor com uma quantidade maior de cenários cobertos, menos retrabalho e um número menor de bugs;
12.
Tudo Verde! Done!Após a criação da sua Suíte de testes e a implementação do código que faz com que todos estes testes passem (assim como os testes para resolução de bugs ), ou seja, tudo está verde, podemos concluir que o trabalho foi terminado para a iteração corrente;
13.
Dificuldades na implantaçãodo TDD na Empresa Investimento em treinamento; Grande barreiras culturais na empresa; Quebra de paradigma muito forte, pois põem em cheque conceitos maduros na área de Desenvolvimento de Software (considerados como verdades absolutas);
14.
O que istotem a ver com você? Mais e mais empresas estão abraçando a idéia de metodologias de desenvolvimento ágeis como XP e SCRUM; Demanda por profissionais que conheçam e já se utilizem destas metodologias e pr á ticas deve crescer ao longo dos próximos anos; Desenvolvedores e empresas que abraçarem a mudança e assumirem que os requisitos vão mudar e que a maneira como estamos gerenciando os nossos projetos não é produtiva, certamente se beneficiarão mais e estarão na ponta quando o mercado como um todo fizer a transição; Eu estarei lá. E você?
15.
Glossário TDD –Test-Driven Development – Desenvolvimento Dirigido a Testes. QA – Quality Assurance – Garantia da Qualidade. XP – eXtreme Programming – Programação Extrema é uma metodologia ágil de desenvolvimento de software ágil, criada por Kent Beck. SCRUM – é uma metodologia de gerenciamento de projetos, cujo nome vem de uma jogada do esporte rugby, em que os jogadores de cada time se encaixam formando uma espécie de muralha e onde outro jogador joga a bola no meio do túnel formado para que os dois grupos a disputem. Assim como em todas as metodologias ágeis o trabalho em equipe é fundamental.
16.
Solução – Criaçãode uma classe de Pilha – Stack Problema: Criar uma classe que represente uma pilha ilimitada em memória e que o acesso seja restrito ao último elemento inserido na pilha;
17.
Criar a TestList para a Pilha Analisando o requisito passado (Stack), foram observados os seguintes testes iniciais: Criar uma pilha e verificar que está vazia; Inserir um objeto na Pilha e verificar que não está vazia; Inserir um objeto na Pilha , retirar este, e verificar que está vazia; Inserir um objeto na Pilha (guardando o seu valor), retirar este da Pilha e verificar que estes são iguais; Inserir 3 objetos em sequência (guardando seus valores), e removê-los verificando se estes são removidos na ordem correta; Retirar um elemento de uma Pilha sem elementos; Inserir um objeto na Pilha, buscar quem é o topo da Pilha e verificar que a Pilha não está vazia; Inserir um objeto na Pilha (guardando o seu valor), buscar quem é o topo da Pilha e verificar que estes são iguais; Buscar o topo de uma Pilha vazia;
18.
Análise da TestListAnalisando a TestList vemos que existem 3 operações e uma propriedade que podem ser executadas na Pilha: Push(Object) (inserir um item na pilha); Pop() (remover um item da pilha, retonando-o); Top() (retornar o primeiro elemento sem retirá-lo da pilha); IsEmpty (retorna um booleano indicando se a pilha está vazia);
19.
Setup (executado somenteuma vez) Baixar do site http://www.nunit.org , o programa msi instalador; Executar o programa de instalação do Nunit; Criar um projeto de testes para a classe a ser criada Stack ; Referenciar a “dll” do framework de testes Nunit ( Nunit.Framework.dll ) no projeto de testes; Referenciar o projeto da classe a ser testada; Fazer deste projeto, o projeto inicial da Solução; Configurar a inicialização automática do programa Nunit.exe (interface visual dos testes unitários) na compilação do Projeto de Testes;
20.
Teste 1 Oprimeiro teste escolhido foi verificar se uma pilha nova está vazia: // StackTests .cs using System; using NUnit.Framework; [ TestFixture ] public class StackTests { [ Test ] public void Empty() { Stack stack = new Stack (); Assert .IsTrue(stack.IsEmpty); } } Executar a solução; Erros de compilação (Stack não existe);
21.
Teste 1 (Red ): Implementar o menor código que faça o teste compilar // Stack.cs using System; public class Stack { public Boolean IsEmpty { get { return false ; } } } Executar a solução; Solução compilada com sucesso; Teste Empty falha (Red);
22.
Teste 1 (Green ): Fazer o teste passar com a mais simples implementação // Stack.cs using System; public class Stack { public Boolean IsEmpty { get { return true ; } } } Executar a solução; Solução compilada com sucesso; Teste Empty executado com sucesso;
23.
Teste 1 (Refactor! ): Melhorar a implementação // Stack.cs using System; public class Stack { public Stack() { this . _isEmpty = true ; } private Boolean _isEmpty; public Boolean IsEmpty { get { return this ._isEmpty; } } } Executar a solução; Solução compilada com sucesso; Teste Empty continua executando com sucesso;
24.
Teste 2 Osegundo teste escolhido foi verificar se ao adicionar um elemento a pilha não está vazia: // StackTests .cs [ Test ] public void PushOne() { Stack stack = new Stack (); stack.Push( “primeiro elemento” ); Assert .IsFalse(stack.IsEmpty, “Após a inclusão, IsEmpty deve ser false.” ); } Executar a solução; Erros de compilação (Stack não possui o método Push(Object));
25.
Teste 2 (Red ): Implementar o menor código que faça o teste compilar // Stack.cs using System; public class Stack { public Stack() { this ._isEmpty = true ; } private Boolean _isEmpty; public Boolean IsEmpty { get { return this ._isEmpty; } } public void Push( Object element_) { } } Executar a solução; Solução compilada com sucesso; Teste PushOne falha (Red);
26.
Teste 2 (Green ): Fazer o teste passar com a mais simples implementação // Stack.cs using System; public class Stack { public Stack() { this ._isEmpty = true ; } private Boolean _isEmpty; public Boolean IsEmpty { get { return this ._isEmpty; } } public void Push( Object element_) { this ._isEmpty = false; } } Executar a solução; Solução compilada com sucesso; Teste PushOne executado com sucesso;
27.
Teste 2 (Refactor! ): Melhorar a implementação // StackTests .cs using System; using NUnit.Framework; [ TestFixture ] public class StackTests { private Stack stack; [ SetUp ] public void Init() { stack = new Stack (); } [ Test ] public void Empty() { Assert .IsTrue(stack.IsEmpty); } [ Test ] public void PushOne() { stack.Push( “primeiro elemento” ); Assert .IsFalse(stack.IsEmpty, “Após um Push, IsEmpty deve ser false.” ); } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
28.
Teste 3 Oterceiro teste escolhido foi verificar se ao adicionar um elemento e retirá-lo a pilha está vazia: // StackTests .cs [ Test ] public void PushAndPop() { stack.Push( “primeiro elemento” ); stack.Pop(); Assert .IsTrue(stack.IsEmpty, “Após uma operação Push - Pop, IsEmpty deve ser true.” ); } Executar a solução; Erros de compilação (Stack não possui o método Pop());
29.
Teste 3 (Red ): Implementar o menor código que faça o teste compilar // Stack.cs using System; public class Stack { public Stack() { this . _isEmpty = true ; } private Boolean _isEmpty; public Boolean IsEmpty { get { return this . _isEmpty ; } } public void Push( Object element_) { this . _isEmpty = false ; } public void Pop() { } } Executar a solução; Solução compilada com sucesso; Teste PushAndPop falha (Red);
30.
Teste 3 (Green ): Fazer o teste passar com a mais simples implementação // Stack.cs using System; public class Stack { public Stack() { this ._isEmpty = true ; } private Boolean _isEmpty; public Boolean IsEmpty { get { return this ._isEmpty; } } public void Push( Object element_) { this ._isEmpty = false ; } public void Pop() { this ._isEmpty = true ; } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
31.
Teste 3 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
32.
Teste 4 Oquarto teste escolhido foi adicionar um elemento (lembrando o seu valor), retirá-lo e verificar se o elemento é o mesmo: // StackTests .cs [ Test ] public void PushAndPopContentCheck() { Int32 expected = 1234; stack.Push(expected); Int32 actual = ( Int32 )stack.Pop(); Assert .AreEqual(expected , actual); } Executar a solução; Erros de compilação (o método Pop() retorna void);
33.
Teste 4 (Red ): Implementar o menor código que faça o teste compilar // Stack.cs using System; public class Stack { public Stack() { this ._isEmpty = true ; } private Boolean _isEmpty; public Boolean IsEmpty { get { return this ._isEmpty; } } public void Push( Object element_) { this ._isEmpty = false ; } public Object Pop() { this . _isEmpty = true ; return null ; } } Executar a solução; Solução compilada com sucesso; Teste PushAndPopContentCheck falha (Red);
34.
Teste 4 (Green ): Fazer o teste passar com a mais simples implementação // Stack.cs using System; public class Stack { private Object _element; public Stack() { this ._isEmpty = true ; } private Boolean _isEmpty; public Boolean IsEmpty { get { return this ._isEmpty; } } public void Push( Object element_) { this ._element = element_; this ._isEmpty = false ; } public Object Pop() { this ._isEmpty = true ; Object top = this ._element; this ._element = null ; return top; } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
35.
Teste 4 (Refactor! ): Melhorar a implementação // Stack.cs using System; public class Stack { private Object _element; public Stack() { } public Boolean IsEmpty { get { return this ._element == null ; } } public void Push( Object element_) { this ._element = element_; } public Object Pop() { Object top = this ._element ; this ._element = null ; return top; } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
36.
Teste 5 (Red ) O quinto teste escolhido foi adicionar 3 elementos (lembrando os seus valores), retirá-los e verificar se os elementos são os mesmos: // StackTests .cs [ Test ] public void PushAndPopMultipleContentCheck() { String pushed1 = “1” ; stack.Push(pushed1); String pushed2 = “2” ; stack.Push(pushed2); String pushed3 = “3” ; stack.Push(pushed3); String popped = stack.Pop() as String ; Assert .AreEqual(pushed3, popped); popped = stack.Pop() as String ; Assert .AreEqual(pushed2, popped); popped = stack.Pop() as String ; Assert .AreEqual(pushed1, popped); } Executar a solução; Solução compilada com sucesso; Teste PushAndPopMultipleContentCheck falha (Red);
37.
Teste 5 (Green ): Fazer o teste passar com a mais simples implementação // Stack.cs using System; using System.Collections; public class Stack { private ArrayList _elements = new ArrayList (); public Boolean IsEmpty { get { return this ._elements.Count == 0; } } public void Push( Object element_) { this ._elements.Insert(0, element_); } public Object Pop() { Object top = this ._elements[0]; this ._elements.RemoveAt(0); return top; } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
38.
Teste 5 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
39.
Teste 6 (Red ) O sexto teste escolhido foi tentar retirar um elemento de uma pilha sem elementos: // StackTests .cs [ ExpectedException ( typeof ( InvalidOperationException ))] [ Test ] public void PopEmptyStack() { stack.Pop(); } Executar a solução; Solução compilada com sucesso; Teste PopEmptyStack falha (Red);
40.
Teste 6 (Green ): Fazer o teste passar com a mais simples implementação // Stack.cs using System; using System.Collections; public class Stack { private ArrayList _elements = new ArrayList (); public Boolean IsEmpty { get { return this ._elements.Count == 0; } } public void Push( Object element_) { this ._elements.Insert(0, element_); } public Object Pop() { if ( this .IsEmpty) throw new InvalidOperationException ( “A pilha está vazia.” ); Object top = this ._elements[0]; this ._elements.RemoveAt(0); return top; } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
41.
Teste 6 (Refactor! ): Melhorar a implementação Ao realizar este teste vieram à tona mais alguns testes que deveriam ser incluídos em nossa TestList: Inserir nulo em uma pilha e verificar que ela não está vazia. Inserir nulo em uma Pilha, retirá-lo e verificar que o valor retornado é nulo. Inserir nulo em uma Pilha, chamar Top e verificar que o valor retornado é nulo.
42.
Teste 7 Osétimo teste escolhido foi incluir um único objeto, chamar Top e verificar se a lista não está vazia: // StackTests .cs [ Test ] public void PushTop() { stack.Push(“42”); stack.Top(); Assert .IsFalse(stack.IsEmpty); } Executar a solução; Erros de compilação (o método Top() não existe);
43.
Teste 7 (Green ): Implementar o menor código que faça o teste compilar // Stack.cs using System; using System.Collections; public class Stack { private ArrayList _elements = new ArrayList (); public Boolean IsEmpty { get { return this ._elements.Count == 0; } } public void Push( Object element_) { this ._elements.Insert(0, element_); } public Object Pop() { if ( this . IsEmpty) throw new InvalidOperationException ( “A pilha está vazia.” ); Object top = this ._elements[0]; this ._elements.RemoveAt(0); return top; } public Object Top() { return null ; } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
44.
Teste 7 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
45.
Teste 8 (Red ) O oitavo teste escolhido foi inserir um elemento na Pilha (guardando o seu valor), chamar Top e verificar que os elementos são iguais: // StackTests .cs [ Test ] public void PushTopContentCheckElement() { String pushed = “42” ; stack.Push(pushed); String topped = stack.Top() as String ; Assert .AreEqual(pushed, topped); } Executar a solução; Solução compilada com sucesso; Teste PushTopContentCheckElement falha (Red);
46.
Teste 8 (Green ): Fazer o teste passar com a mais simples implementação using System; using System.Collections; public class Stack { private ArrayList _elements = new ArrayList (); public Boolean IsEmpty { get { return this ._elements.Count == 0; } } public void Push( Object element_) { this ._elements.Insert(0, element_); } public Object Pop() { if ( this .IsEmpty) throw new InvalidOperationException ( “A pilha está vazia.” ); Object top = this ._elements[0]; this ._elements.RemoveAt(0); return top; } public Object Top() { return this ._elements[0]; } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
47.
Teste 8 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
48.
Teste 9 (Green ) O nono teste escolhido foi inserir múltiplos elementos na Pilha (guardando o valor do último inserido), chamar Top e verificar que os elementos são iguais: // StackTests .cs [ Test ] public void PushMultipleTopContentCheckElement() { String pushed1 = “1” ; stack.Push(pushed1); String pushed2 = “2” ; stack.Push(pushed2); String pushed3 = “3” ; stack.Push(pushed3); String topped = stack.Top() as String ; Assert .AreEqual(pushed3, topped); } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
49.
Teste 9 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
50.
Teste 10 (Green ) O décimo teste chamar Top diversas vezes e verificar que os elementos são iguais: // StackTests .cs [ Test ] public void MultipleTopContentCheckElement() { String pushed = “1”; stack.Push(pushed); String topped1 = stack.Top() as String ; Assert .AreEqual(pushed , topped1); String topped2 = stack.Top() as String ; Assert .AreEqual(topped1, topped2); String topped3 = stack.Top() as String ; Assert .AreEqual(topped2, topped3); } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
51.
Teste 10 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
52.
Teste 11 (Red ) O décimo primeiro teste é chamar Top em uma pilha vazia // StackTests .cs [ ExpectedException ( typeof ( InvalidOperationException ))] [ Test ] public void TopEmpty() { stack.Top(); } Executar a solução; Solução compilada com sucesso; Teste TopEmpty falha (Red);
53.
Teste 11 (Green ): Fazer o teste passar com a mais simples implementação // Stack.cs using System; using System.Collections; public class Stack { … public Object Pop() { if ( this .IsEmpty) throw new InvalidOperationException ( “A pilha está vazia.” ); Object top = this ._elements[0]; this ._elements.RemoveAt(0); return top; } public Object Top() { if ( this .IsEmpty) throw new InvalidOperationException ( “A pilha está vazia.” ); return this ._elements[0]; } } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
54.
Teste 11 (Refactor! ): Melhorar a implementação … public Object Pop() { Object top = Top(); this ._elements.RemoveAt(0); return top; } public Object Top() { if ( this .IsEmpty) throw new InvalidOperationException ( “A pilha está vazia.” ); return this ._elements[0]; } … Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
55.
Teste 12 (Green ) O décimo segundo teste é inserir nulo e verificar que a pilha não está vazia. // StackTests .cs [ Test ] public void PushNull () { stack.Push( null ); Assert .IsFalse(stack.IsEmpty); } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
56.
Teste 12 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
57.
Teste 13 (Green ) O décimo terceiro teste é inserir um objeto nulo chamar Pop e verificar que o valor retornado é nulo e que a pilha está vazia. // StackTests .cs [ Test ] public void PushNullCheckPop() { stack.Push( null ); Assert .IsNull(stack.Pop()); Assert .IsTrue(stack.IsEmpty); } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
58.
Teste 13 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
59.
Teste 14 (Green ) O décimo quarto teste é inserir um objeto nulo chamar Top e verificar que o valor retornado é nulo. // StackTests .cs [ Test ] public void PushNullCheckTop() { stack.Push( null ); Assert .IsNull(stack.Top()); } Executar a solução; Solução compilada com sucesso; Após às alterações, todos os testes continuam passando com sucesso.
60.
Teste 14 (Refactor! ): Melhorar a implementação Neste ponto não há nenhuma replicação de código que possa ser retirada do código; Então seguimos em frente;
61.
Stack.cs using System; using System.Collections; public class Stack { private ArrayList _elements = new ArrayList (); public Boolean IsEmpty { get { return this ._elements.Count == 0; } } public void Push( Object element_) { this ._elements.Insert(0, element_); } public Object Pop() { Object top = Top(); this ._elements.RemoveAt(0); return top; } public Object Top() { if ( this .IsEmpty) throw new InvalidOperationException ( “A pilha está vazia.” ); return this ._elements[0]; } }