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.
1. Test-Driven Development O que é? O que não é? E o que isto tem a ver com você? Carlos Eduardo Miranda Arquiteto .Net Add Technologies
2. Agenda Introdução Por que 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 assunto nunca 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 - O que é? 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ícios Melhora 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 – O que 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 o TDD Test-Driven Development != QA Test-Driven Development != Testes Unitários Test-Driven Development != Design Patterns
9. Prática do TDD 0. 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 algum teste 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ção do 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 isto tem 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ção de 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 Test List 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 TestList Analisando 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 somente uma 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 O primeiro 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 O segundo 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 O terceiro 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 O quarto 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 O sé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]; } }