Baixe o app para aproveitar ainda mais
Prévia do material em texto
1 2 SUMÁRIO 1 INTRODUÇÃO ..................................................................................... 4 2 PROCESSOS DE SOFTWARE ........................................................... 5 2.1 PROCESSOS DE SOFTWARE E SUAS ETAPAS ....................... 5 3 REQUISITOS DE UM SOFTWARE ..................................................... 7 3.1 REQUISITOS FUNCIONAIS ......................................................... 8 3.2 REQUISITOS NÃO FUNCIONAIS ................................................ 8 4 PROCESSO DE DESENVOLVIMENTO E GERENCIAMENTO DE UM SOFTWARE .......................................................................................................... 12 5 PADRÕES DE PROJETO DE ARQUITETURA DE SISTEMAS ........ 15 5.1 DEFINIÇÃO E SURGIMENTO DOS PADRÕES DE PROJETOS....... ................................................................................................ 16 5.2 VANTAGENS EM UTILIZAR PADRÕES DE PROJETOS .......... 18 6 QUAL É O MELHOR PADRÃO DE PROJETO PARA O SEU SOFTWARE? ........................................................................................................ 20 7 TIPOS DE PADRÕES DE PROJETO ................................................ 24 7.1 CREATIONAL PATTERNS (PADRÕES DE CRIAÇÃO) ............. 24 7.2 STRUCTURAL PATTERNS (PADRÕES ESTRUTURAIS) ......... 25 7.3 BEHAVIORAL PATTERNS (PADRÕES COMPORTAMENTAIS) 26 8 MÉTODOS ÁGEIS ............................................................................. 28 8.1 CONCEITO DE MÉTODO ÁGIL ................................................. 29 8.2 FERRAMENTAS ELETRÔNICAS DE MÉTODOS ÁGEIS .......... 30 3 9 VALORES E PRINCÍPIOS ÁGEIS ..................................................... 32 10 PRINCIPAIS MÉTODOS ÁGEIS EXISTENTES ............................. 36 11 TEST-DRIVEN DEVELOPMENT ................................................... 44 11.1 SURGIMENTO DE UM MÉTODO DE DESENVOLVIMENTO DIRIGIDO POR TESTES ................................................................................... 44 11.2 HISTÓRICO ................................................................................ 45 11.3 CONCEITOS DO TEST-DRIVEN DEVELOPMENT ................... 46 11.4 O TEST-DRIVEN DEVELOPMENT NO CICLO DE VIDA DO SOFTWARE.... .................................................................................................. 50 11.5 DOCUMENTAÇÃO ..................................................................... 62 12 VANTAGENS E DESVANTAGENS DO TEST-DRIVEN DEVELOPMENT ................................................................................................... 63 12.1 VANTAGENS DA UTILIZAÇÃO DO TEST-DRIVEN DEVELOPMENT ................................................................................................ 64 12.2 DESVANTAGENS DA UTILIZAÇÃO DO TEST-DRIVEN DEVELOPMENT ................................................................................................ 65 13 REFERÊNCIAS BIBLIOGRÁFICAS ............................................... 68 4 1 INTRODUÇÃO Prezado aluno! O Grupo Educacional FAVENI, esclarece que o material virtual é semelhante ao da sala de aula presencial. Em uma sala de aula, é raro – quase improvável - um aluno se levantar, interromper a exposição, dirigir-se ao professor e fazer uma pergunta, para que seja esclarecida uma dúvida sobre o tema tratado. O comum é que esse aluno faça a pergunta em voz alta para todos ouvirem e todos ouvirão a resposta. No espaço virtual, é a mesma coisa. Não hesite em perguntar, as perguntas poderão ser direcionadas ao protocolo de atendimento que serão respondidas em tempo hábil. Os cursos à distância exigem do aluno tempo e organização. No caso da nossa disciplina é preciso ter um horário destinado à leitura do texto base e à execução das avaliações propostas. A vantagem é que poderá reservar o dia da semana e a hora que lhe convier para isso. A organização é o quesito indispensável, porque há uma sequência a ser seguida e prazos definidos para as atividades. Bons estudos! 5 2 PROCESSOS DE SOFTWARE Um dos itens primordiais de fator de sucesso em empresas de desenvolvimento de software é a utilização de um processo de software, que pode ser definido brevemente como um conjunto de atividades exigidas no desenvolvimento de um sistema de software. Logo, pode-se dizer que se trata de um conjunto de atividades e resultados que juntos produzem um software. Essas atividades são relacionadas entre elas e operam de acordo com padrões preestabelecidos para alcançar um objetivo ou resultado desejado. Ou seja, a partir dessa definição, você pode produzir um software de alta qualidade e custo baixo Sommerville (2011). A partir desses conceitos, pode-se considerar que um processo de software pode ser visto como um conjunto de atividades, métodos, ferramentas e práticas que podem ser utilizadas para construir um produto. Entre as atividades relacionadas ao processo de software, estão: especificação, desenvolvimento, verificação e manutenção Sommerville (2011). 2.1 PROCESSOS DE SOFTWARE E SUAS ETAPAS As principais fases de um processo de software são: a especificação de requisitos, na qual são traduzidas as necessidades ou requisitos operacionais para a descrição da funcionalidade a ser implementada; o projeto de sistema, no qual tudo o que foi traduzido terá uma descrição item a item de componentes necessários para a codificação do software; a programação propriamente dita, em que ocorre a codificação que controla o sistema e é implementada a lógica necessária; a verificação, onde se observa se todos os requisitos iniciais foram implementados e o objetivo alcançado Sommerville (2011). 6 Segundo Sommerville (2011), podemos identificar as seguintes atividades no processo de software: Especificação • Engenharia de sistema: solução para o problema em questão, envolve questões amplas, inclusive que sejam a parte em relação ao software, porém pertençam ao problema a ser solucionado. • Análise de requisitos: área em que as necessidades para atender ao cliente são levantadas, tendo como principal objetivo especificar os requisitos documentando-os. • Especificação do sistema: ocorre a descrição funcional do software, incluindo planos de testes para verificar se está adequado. Projeto • Arquitetura: neste item, ocorre o desenvolvimento do modelo conceitual para o software, composto de módulos que podem ser independentes. • Interface: é definida e estudada a interface de comunicação para cada módulo. • Detalhamento: neste item, os módulos são definidos e podem ser traduzidos para pseudocódigos. Implementação Codificação: a implementação e o desenvolvimento em si do software. Validação 7 • Teste unitário: ocorre a realização de testes para verificar se há erros e se o comportamento está adequado. • Integração: união de diferentes módulos do software em um só, assim como a verificação da interação entre eles quando estão funcionando em conjunto. Manutenção e evolução • O software desenvolvido e implementado entra em um ciclo que abrange todas as fases anteriores. 3 REQUISITOS DE UM SOFTWARE Requisitos são condições que devem ser alcançadas, ou seja, é o que um software ou componente deve possuir para satisfazer uma especificação de um sistema a fim de atender às necessidades dos clientes, restrições e serviços que devem ser contemplados. Normalmente são identificados a partir das regras de negócio estabelecidas, que estão diretamente ligadas à área específica em que o software será desenvolvido. Durante o levantamento de requisitos, os desenvolvedores devem ter conhecimento sobre o queo sistema deverá proporcionar e compreender a necessidade do usuário. Entre os requisitos, existem os funcionais e os não funcionais Sommerville (2011). 8 3.1 REQUISITOS FUNCIONAIS Requisitos funcionais são os que abordam o que o software deverá fazer, como deverá reagir a entradas específicas e o comportamento. Dependem do tipo de software que será desenvolvido, de quem o utilizará e da maneira como é feita a escrita dos requisitos pela empresa. Ou seja, pode ser descrito de uma forma mais abstrata para que o usuário do sistema tenha uma compreensão mais fácil ou podem ser mais específicos tecnicamente, com entradas, saídas, exceções e restrições Sommerville (2011). Como um dos problemas em desenvolvimento de software, pode-se citar a imprecisão na especificação dos requisitos, o que pode ocasionar atrasos e aumento de custos na engenharia do software. Por essa razão, preza-se que seja sempre completa e consistente, sendo todos os serviços requeridos pelo usuário explícitos plenamente Sommerville (2011). 3.2 REQUISITOS NÃO FUNCIONAIS Requisitos não funcionais são restrições aos serviços ou às funções oferecidos pelo software, incluindo normas e timing e, normalmente, aplicam-se ao software como um todo. Não estão relacionados diretamente a serviços específicos que sejam oferecidos pelo software, podem estar relacionados às propriedades do sistema, como: tempo de retorno, confiabilidade. Normalmente especificam ou restringem características do sistema e frequentemente são mais críticos que os funcionais, ou seja, se deixar de atendê-los, pode ser que o sistema seja inutilizado Sommerville (2011). Os requisitos não funcionais podem afetar a arquitetura do software, assegurando que sejam cumpridos requisitos de desempenho, organizando o sistema para que seja minimizada a comunicação entre componentes. Você poderá 9 verificar que um único requisito não funcional poderá gerar outros requisitos funcionais relacionados a ele, os quais possam definir serviços necessários, como um requisito de proteção. A Figura 1 apresenta tipos de requisitos não funcionais, segundo Sommerville (2011). Fonte: Adaptado de Sommerville (2011) Geralmente, os requisitos não funcionais conflitam com outros requisitos funcionais ou não funcionais. A especificação de requisitos de software nada mais é do que um documento oficial, em que os desenvolvedores baseiam-se para implementar o software solicitado. Esse documento deve incluir requisitos de usuário e uma especificação detalhada 10 dos requisitos (funcionais e não funcionais) de sistema, ou seja, esses documentos são essenciais quando alguém está desenvolvendo um sistema. O Quadro 1 mostra usuários desses documentos e como eles o utilizam, conforme Kotonya e Sommerville (1998). Fonte: Adaptado de Sommerville (2011) Dessa forma, você deverá incluir em documento de requisitos detalhes que dependerão do tipo de software que deverá ser desenvolvido e o processo utilizado Sommerville (2011). O quadro a seguir apresenta uma maneira de organizar o documento de requisitos com base na norma IEEE (IEEE, 1998). 11 12 Fonte: IEEE (IEEE, 1998). 4 PROCESSO DE DESENVOLVIMENTO E GERENCIAMENTO DE UM SOFTWARE Conforme abordado anteriormente, o documento de especificação é um item primordial para que o software seja desenvolvido de acordo com as necessidades dos clientes, com as restrições necessárias, abordando confiabilidade, portabilidade, segurança e usabilidade, assim como desempenho e hardware necessário. A Figura abaixo mostra as fases e cada uma de suas etapas antes do desenvolvimento do software Sommerville (2011). 13 Fonte: Adaptado de Sommerville (2011) A análise e a elicitação de requisitos envolvem vários tipos de pessoas de uma organização, geralmente o stakeholder, que inclui usuários finais que interagirão direto com o sistema, e alguma outra pessoa que será afetada direta ou indiretamente pelo software Sommerville (2011). 14 O gerenciamento de projetos em software leva em consideração primeiro a qualidade, a produtividade e a redução de riscos. Os pontos técnicos devem incluir a definição do ciclo de vida e tipos de planos a serem utilizados para testes, documentação, desenvolvimento, qualidade e gerenciamento Sommerville (2011). No quesito ciclo de vida do software, identificam-se três fases: definição, desenvolvimento e operação. Em definição, os requisitos são determinados e a viabilidade é estudada, assim como o planejamento de atividades é elaborado. Na fase do desenvolvimento, são realizadas atividades para a produção do software, como: concepção, especificação, design de interface, prototipação, arquitetura, codificação e verificação. Na fase da operação, o software será utilizado pelos usuários e podem ocorrer manutenções, para correções ou evoluções Sommerville (2011). Os principais motivos para problemas apresentados estão relacionados a falhas no gerenciamento de projetos durante a fase de desenvolvimento, segundo Standish Group (1994). Uma das tarefas de um gestor de projetos é identificar as partes mais difíceis e buscar soluções eficientes Sommerville (2011). O gestor de projetos deve trabalhar com ideias e com pessoas, tendo como principais atividades planejamento, assessoria, organização, saber dirigir e controlar o projeto. Da mesma forma que deve ter comprometimento com equipe, prazos, custos e qualidade em suas entregas Sommerville (2011). Como há muitas mudanças de requisitos, o ciclo mostrado na figura apresentada deve ser repetido toda vez que for necessário. As novas técnicas com metodologias ágeis facilitam a vida dos gerentes de projeto desde que estes tenham uma equipe qualificada e comprometida, pois podem facilitar a adaptação às mudanças que ocorrem no decorrer dos projetos Sommerville (2011). É importante que aspectos como arquitetura de sistema, linguagem de programação, sistema gerenciador de banco de dados e padrão de interface gráfica sejam considerados na fase do projeto Sommerville (2011). 15 É fundamental que gerentes de projeto estejam atualizados sobre novas tecnologias, ferramentas, metodologias, modelos e melhores práticas para que o projeto tenha sucesso e o desenvolvimento seja eficaz e eficiente Sommerville (2011). 5 PADRÕES DE PROJETO DE ARQUITETURA DE SISTEMAS O processo de desenvolvimento de software consiste em inúmeras atividades determinadas e organizadas, que têm como objetivo resolver um problema da área de tecnologia da informação. O objetivo principal dessas atividades é satisfazer as necessidades dos clientes/ usuários do sistema. Para tanto, é preciso analisar os requisitos do sistema e projetar e implementar um código padronizado, para evitar erros e falhas Zenker (2020). Padronizar consiste em empregar técnicas e padrões que auxiliam no desenvolvimento e aumentam a produtividade, a segurança e a qualidade no projeto. Um padrão é um molde, um exemplo, um protótipo, um paradigma ou uma referência de algo a seguir. Um projeto é um desenho, uma planta, um esquema, um plano ou um programa Zenker (2020). Padrões de projeto (do inglês design patterns) são alternativas ou modelos de soluções para problemas encontrados no desenvolvimento de um projeto de software independente do paradigma; porém, sua aplicabilidade maior é no paradigma orientado a objetos. Os padrões de projetos facilitam a reutilização de soluções e arquiteturas de software de forma fácil e flexível, reduzindo a complexidade do projeto e resolvendo os problemas apresentados Zenker(2020). 16 5.1 DEFINIÇÃO E SURGIMENTO DOS PADRÕES DE PROJETOS Os padrões de projetos, comumente conhecidos como design patterns, são modelos, isto é, referências aplicadas ao projeto, trazendo soluções para problemasespecíficos do desenvolvimento do projeto de software orientado a objetos. Os padrões são aplicáveis a outros paradigmas, porém, não são tão relevantes quanto no paradigma orientado Zenker(2020). Por meio dos design patterns, é possível definir nomes e descrever o problema e a solução a ser aplicada ao projeto. Eles trazem exemplos de implementações e uma organização geral de classes e objetos. Os padrões de projetos facilitam a reutilização de soluções e arquiteturas bem-sucedidas na construção de um projeto com paradigma orientado a objetos. Além disso, eles promovem a organização no código e garantem ao programador um código limpo e padronizado, além de melhorar a documentação e a manutenção do sistema, ao fornecer uma especificação de interações entre os objetos das classes Zenker(2020). Em resumo, os padrões de projetos auxiliam o projetista, o desenvolvedor e o analista a obterem um projeto adequado, sendo uma solução para um problema em um determinado contexto Zenker(2020). Os problemas surgem com o desenvolvimento do projeto, podendo ser uma dificuldade em desenvolver ou apenas um desafio. O contexto determina qual padrão deve ser aplicado para a resolução do problema. A solução surge com a escolha do padrão (pattern) adequado ao projeto em questão. Todo padrão de projeto possui elementos ou componentes essenciais, conforme listado a seguir. • Nome: descreve a essência do padrão; é uma referência para poder escrever o problema do projeto. • Objetivo: descreve como o padrão atua, em que modelo de software se aplica. 17 • Problema: descreve o problema, em que situação se aplica o padrão. • Contexto: descreve uma situação que complemente o problema. • Solução: descreve a solução; é uma descrição abstrata de um problema e de como as classes e os objetos o resolverão. • Consequências: descreve as vantagens e desvantagens da utilização do padrão; são críticas para a avaliação de alternativas de projetos e a compreensão de custos e benefícios para aplicação no software desenvolvido Zenker(2020). O padrão de projetos surgiu como um conceito de arquitetura, no final dos anos 1970, documentado no livro A Pattern Language: Towns, Buildings, Construction. Nesse livro, os arquitetos Christopher Alexander, Sara Ishikawa e Murray Silverstein catalogaram 253 tipos de problemas de projeto e desafios e analisaram cada situação, descrevendo e propondo um padrão de solução. O livro foi publicado em português em 2012, com o nome Uma Linguagem de Padrões — a Pattern Language Zenker(2020). Nos anos 1980, os engenheiros de software Kent Beck e Ward Cunninghan começaram a aplicar a ideia de padrões na área de programação em uma palestra denominada "Utilizando a linguagem dos padrões para programação orientada a objetos”, em tradução livre. Porém, somente nos anos 1990, os padrões ganharam credibilidade, após serem publicados no livro Design Patterns — Elements of Reusable Object-Oriented Software, escrito por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides Zenker(2020). O livro apresentou 23 padrões de projetos. Os autores ficaram conhecidos como a gangue dos quatro (GoF, do inglês gang of four), termo utilizado nos padrões definidos no livro Zenker(2020). Os padrões facilitam o desenvolvimento do projeto, conforme apontam Gamma et al. (2000, p. 17): 18 Uma coisa que os melhores projetistas sabem que não devem fazer é resolver cada problema a partir de princípios elementares ou do zero. Quando encontram uma boa solução, eles a utilizam repetidamente. Consequentemente, você encontrará padrões de classes e de comunicação entre objetos. Esses padrões resolvem problemas específicos de projetos e tornam os projetos orientados a objetos mais flexíveis e, em última instância, reutilizáveis. 5.2 VANTAGENS EM UTILIZAR PADRÕES DE PROJETOS Os padrões de projetos são modelos que já foram utilizados, implementados e testados em diversos projetos, o que traz segurança ao utilizá-los, além de outras vantagens, descritas a seguir Zenker(2020). • Com o uso de padrões de nomenclatura, o projeto possuirá nomes estandardizados, trazendo referência e credibilidade para o software desenvolvido. • Melhorar técnicas e princípios da programação orientada a objetos, como implementação de herança, composição e polimorfismo. • Aumentar a organização e a manutenção de projetos, já que esses padrões se baseiam em baixo acoplamento entre as classes e padronização do código. • Melhorar o desenvolvimento de projetos tanto individualmente quanto em equipe. • Implementar hierarquias de herança, diminuindo o tamanho do projeto e aprimorando o mesmo. • Facilitar a manutenção do projeto e a edição do código. • Utilizar o padrão para a refatoração de um código ruim, altamente acoplado e de baixa coesão. 19 Veja a seguir a terminologia relacionada ao programa orientado a objetos: • Classe: representa um conjunto de objetos com características afins. Uma classe define o comportamento do objeto, por meio de métodos, e quais estados ele é capaz de manter, por meio de atributos. • Atributos: são características de um objeto. Basicamente, trata-se da estrutura de dados que vai representar a classe. • Métodos: definem as habilidades dos objetos. São ações que o objeto pode fazer ou sofrer. • Objeto: instância da classe, elemento que foi gerado por meio da classe. • Interface: é um contrato entre a classe e o mundo externo. Quando uma classe implementa uma interface, ela está comprometida a fornecer o comportamento publicado pela interface. • Abstração: é a habilidade de se concentrar nos aspectos essenciais de um contexto qualquer, ignorando características menos importantes ou acidentais. Em modelagem orientada a objetos, uma classe é uma abstração de entidades existentes no domínio do sistema de software. • Associação: é o mecanismo pelo qual um objeto utiliza os recursos de outro. Pode tratar-se de uma associação simples "usa um" ou de um acoplamento "parte de". • Coesão: é quando as tarefas que um elemento realiza estão relacionadas a um mesmo conceito; é o princípio da responsabilidade única, isto é, a ideia de que a classe não deve assumir responsabilidades que não são suas. 20 • Sobrecarga: é a utilização do mesmo nome para símbolos ou métodos com operações ou funcionalidades distintas. Geralmente os métodos se diferenciam pela sua assinatura. • Acoplamento: significa o quanto uma classe depende da outra para funcionar. Surge da associação entre as classes; conforme for a dependência entre ambas, surge o termo “fortemente acopladas”. • Refatoração: é o processo de alterar um software de maneira que não mude o seu comportamento externo e ainda melhore a sua estrutura interna, modificando apenas a estrutura interna do código. 6 QUAL É O MELHOR PADRÃO DE PROJETO PARA O SEU SOFTWARE? Encontrar o padrão de projeto adequado ao software é difícil. Os padrões de GoF, segundo Gamma et al. (2000), apontam 23 padrões na lista de escolhas. Porém, é fundamental seguir alguns critérios, com o objetivo de solucionar o problema do projeto sem cometer erros e tornando o projeto o mais padronizado e organizado possível. Observe a figura abaixo: 21 Fonte: Gamma et al. (2000) Cada padrão de projeto é dividido em seções, consistindo em critérios que apresentam e justificam seu uso, possibilitando comparações, entendimento e aplicabilidade Zenker(2020). Entre os critérios para a escolha do melhor padrão, destacam-se os seguintes: 22 1. Considerar como os padrões de projeto solucionam problemas de projeto. Avaliar a necessidade do software e qual é a intenção ao utilizar o padrão. 2. Examinar qual é a intenção do padrão, o que faz, de fato, o padrão de projeto, quais são os princípios do modelo e se o mesmosolucionará o problema em questão. 3. Estudar como os padrões se relacionam. Avaliar, por meio da tabela de escopo e propósitos do projeto (Quadro 1), qual é a relação existente entre os padrões. 4. Estudar as semelhanças existentes entre os padrões. Verificar semelhanças entre os grupos de padrões de criação, estruturados e comportamentais. 5. Examinar uma causa de reformulação de projeto. 6. Considerar o que deveria ser variável no seu projeto; ou seja, em vez de considerar o que pode forçar uma mudança em um projeto, deve- se considerar o que se deseja mudar sem necessitar reprojetá-lo. 23 Fonte: Adaptado de Gamma et al. (2000). Analisando o Quadro acima, percebe-se que o primeiro critério abordado é a finalidade do padrão, separada em três propósitos: criação, estrutura e comportamento. Além do propósito, há o escopo definido, que estabelece se o padrão é relacionado a classes e subclasses ou envolve objetos. Os padrões relacionados a classes e subclasses são estáticos, enquanto os padrões relacionados a objetos são dinâmicos, isto é, podem ser modificados no tempo real do projeto. 24 7 TIPOS DE PADRÕES DE PROJETO Seguindo o repositório GoF definido no livro de Gamma et al. (2000), os padrões são listados de acordo com o contexto aplicado e o tipo de problema. Os principais padrões de projetos são divididos em segmentos de criação (creational patterns), estrutura (structural patterns) e padrões de comportamento (behavioral patterns) Zenker(2020). 7.1 CREATIONAL PATTERNS (PADRÕES DE CRIAÇÃO) O objetivo desse tipo de padrão é a abstração da instância de objetos. É possível criar um objeto sem se preocupar com o todo envolvido na criação desse componente. Esse padrão abstrai ou adia o processo de criação, tornando o sistema independente de como os seus objetos são criados. O mesmo pode impedir que um sistema crie mais de um objeto de uma classe, ou pode postergá-lo até o tempo de execução Zenker(2020). Suponha o projeto do jogo Imagem e Ação, em que os jogadores desenharão objetos para que os outros adivinhem. O usuário pode desenhar qualquer forma, sendo figuras vetoriais geométricas ou vetoriais à mão livre. Supondo que cada forma no programa de desenho seja representada por um objeto, no tempo de execução do projeto, não se sabe a forma que o usuário desenhará. Com base na entrada do jogador, o programa deve ser capaz de determinar em que classe instanciar um objeto. Se o jogador criar um retângulo na interface gráfica do utilizador (GUI, do inglês graphical user interface), nosso programa deve estar preparado para a instância de um objeto da classe Rect(). Quando o jogador decide qual objeto desenhar, o programa deve determinar em que subclasse específica deve instanciar esse objeto Zenker(2020). Os padrões de criação são os seguintes. 25 • Abstract Factory: fornece uma interface para a criação de conjuntos de objetos relacionados ou dependentes, sem precisar especificar sua classe • Builder: separa a construção de um objeto complexo da sua representação. Possibilita que seu processo de construção crie diferentes representações • Factory Method: define a interface para a criação de um objeto, mas são as subclasses que decidem qual classe deve ser instanciada. • Prototype: especifica os tipos de objetos a serem criados usando uma instância prototípica e criando objetos copiando esse protótipo • Singleton: garante que a classe tenha somente uma instância e fornece um ponto global de acesso a ela. 7.2 STRUCTURAL PATTERNS (PADRÕES ESTRUTURAIS) O objetivo desse padrão é a organização e a estruturação das classes e dos seus relacionamentos com os objetos. Ao contrário do padrão de criação, o padrão de estrutura é voltado para os objetos, descrevendo maneiras de montá-los, isto é, a maneira como as classes e os objetos serão compostos para formar estruturas maiores. A flexibilidade obtida pela composição de objetos provém da capacidade de mudar a composição em tempo de execução Zenker(2020). Segundo Ricardo (2006), do site DevMedia, um exemplo do uso do padrão estruturado é no framework Hibernate. Este faz uso do padrão proxy ao executar a técnica utilizada para acessar o banco de dados apenas quando for necessário. Em muitas buscas, o Hibernate usa o método session.load(id); nesse momento, um proxy para o objeto real é retornado. Nesse caso, o objeto ainda não está completamente preenchido, pois nenhum SQL foi realizado até esse momento. 26 Apenas quando uma propriedade desse objeto (método getX) ou um relacionamento (por exemplo, empresa.getFuncionarios()) for chamado, a consulta no banco será realizada — tudo isso de forma transparente para o cliente Zenker(2020). Os padrões de estrutura são os seguintes. • Adapter: converte a interface de uma classe em outra interface esperada pelo cliente. O Adapter permite que as classes trabalhem em conjunto para promover essa mudança. • Bridge: separa uma abstração da sua implementação, permitindo uma variação individual de cada uma. • Composite: compõe objetos em estruturas hierárquicas, como árvores. Trata objetos individuais e composições de objetos de forma uniforme. • Decorator: atribui de forma dinâmica responsabilidades adicionais a um objeto. • Façade: fornece uma interface unificada para um conjunto de interfaces em subsistema. • Flyweight: suporta grandes quantidades de objetos de forma eficiente, por meio de compartilhamento. • Proxy: fornece um objeto representante (surrogate) ou marcador de outro objeto. 7.3 BEHAVIORAL PATTERNS (PADRÕES COMPORTAMENTAIS) O objetivo é delegar responsabilidades, definindo como os objetos devem se comportar e se comunicar. Esses padrões se concentram nos algoritmos Zenker(2020). 27 São 11 os padrões comportamentais, que são descritos a seguir Zenker(2020). • Chain of Responsibility: evita o acoplamento de remetente de uma solicitação ao destinatário, dando chance ao objeto para tratar a solicitação. • Command: encapsula uma solicitação como objeto, permitindo parametrizar clientes com diferentes solicitações, enfileirando ou registrando os “logs” de solicitações. • Interpreter: define para a linguagem uma representação gramatical dentro do interpretador. • Iterator: possibilita o acesso sequencial dos elementos de uma agregação de objetos, sem a necessidade de expor sua representação subjacente • Mediator: define um objeto que encapsula a forma como um conjunto de objetos interage • Memento: captura e externaliza um estado interno do objeto sem violar seu encapsulamento, possibilitando restaurar para esse estado — por exemplo, um controle de históricos de ações. • Observer: define uma dependência um-para-muitos entre objetos; quando um objeto muda, todos os seus dependentes são notificados e atualizados automaticamente. • State: permite que o objeto altere seu comportamento quando o estado interno for modificado. • Strategy: define uma família de algoritmos, encapsulando cada um e os tornando intercambiáveis. • Template Method: define o esqueleto de um algoritmo em uma operação, postergando a definição de alguns passos para subclasses. 28 • Visitor: representa uma operação a ser executada sobre os elementos da estrutura de um objeto 8 MÉTODOS ÁGEIS Atualmente, dois cenários impactam a rotina dos desenvolvedores: o cenário da big data e o cenário da Internet das Coisas. Profissionais que estudam software e atuam na área de desenvolvimento de software, sistemas e sites são forçados a implementar tecnologias novas a todo momento. A Internet das Coisas fez com que câmeras, smartphones e relógios fiquem conectados à internet a todo momento, e as empresas veem nisso uma oportunidade para estudar comportamentos através dos logs e registros das pessoas nos sistemas. Quemquer que trabalhe na área certamente será forçado a adaptar software rapidamente às tecnologias que surgem diariamente. Por isso, a rotina dos desenvolvedores é recheada de adrenalina (Andrade, 2020). Em pouco tempo, vimos os sites que possuíam linguagens HTML 4, CSS , PHP 5 e JavaScript se tornarem HTML 5 com suas animações, CSS 3, com suas decorações extravagantes, PHP 7, com sua agilidade e códigos novos, e JavaScript React, Angular e JQuery, trazendo novas formas de controlar e manipular os elementos frontais das páginas. Além disso, você deve ter percebido que os bancos de dados SQL ganharam a companhia dos bancos NoSQL, que chegaram com agilidade para sites que contêm muita interação com pessoas. Na criação de software, percebemos que o que antes era feito através de Ionic, um framework JavaScript que cria aplicativos com plugins, hoje é feito através de React Native, que traz experiências mais satisfatórias ao usuário e usa a linguagem nativa dos aparelhos mobiles (Andrade, 2020). Neste cenário, várias formas de planejamento de criação de software disputam as decisões das equipes que desenvolvem, e disso surgem dúvidas 29 quanto ao critério de escolha do método de desenvolvimento de software mais dinâmico e condizente com o tempo em que vivemos. De acordo com Sommerville (2011), em 1980, com o surgimento das linguagens de quarta geração, e em 1990, com o surgimento de metodologias ágeis, como a Metodologia de Desenvolvimento de Sistemas Dinâmicos e a Metodologia Extreme Programming, os desenvolvedores se viram forçados a mudar a forma de trabalho. Vamos discutir um pouco sobre isso neste capítulo Andrade, (2020). 8.1 CONCEITO DE MÉTODO ÁGIL O desenvolvimento ágil envolve mudanças rápidas e prioriza-se, às vezes, agilidade frente à qualidade — alguns analistas dizem que este método é uma resposta ao cenário tecnológico. Após se fazer um planejamento de etapas de requisitos, é comum ter que se deparar com mudanças dos próprios requisitos, e isso é aceito neste método de desenvolvimento, pois não impossível antever todas as necessidades do mercado, visto que elas mudam com frequentemente; em alguns casos, os requisitos mudam depois da entrega do projeto (SOMMERVILLE, 2011). De acordo com Amaral et al. (2012, p. 21) o conceito de ágil pode ser visto como: [...] é uma abordagem fundamentada em um conjunto de princípios, cujo objetivo é tornar o processo de gerenciamento de projetos mais simples, flexível e iterativo, de forma a obter melhores resultados em desempenho (tempo, custo e qualidade), menos esforço em gerenciamento e maiores níveis de inovação e agregação de valor para o cliente. O desenvolvimento ágil é feito em equipes multidisciplinares, onde os processos são feitos em espiral e as equipes priorizam conversas em detrimento da documentação, ou seja, a rigidez é menor. Aqui, é mais importante a funcionalidade 30 do software do que a documentação; é mais importante satisfazer as necessidades do cliente do que seguir um plano. No método ágil, os colaboradores têm perfil motivado, as entregas são semanais e não mensais, o que facilita o acompanhamento da satisfação do cliente. Logo, neste modelo, as adaptações falam mais alto do que a disciplina, e o processo de criação de software é transparente, o cliente vê tudo, o que ajuda na customização, conforme Sommerville (2011). No método ágil, as equipes são pequenas, com alto nível técnico, feedbacks constantes, as reuniões são rápidas — duram geralmente 15 minutos — e muitas vezes são feitas em pé, e cada reunião gera um plano de ação, em outras palavras, um conjunto de atividades a serem realizadas em um intervalo de tempo determinado. A maioria dos métodos ágeis foi criada na década de 1990, e eles serão explicados mais adiante (Andrade, 2020). 8.2 FERRAMENTAS ELETRÔNICAS DE MÉTODOS ÁGEIS Que tal falarmos sobre ferramentas de gestão? O primeiro passo para criar um painel de metodologia ágil é escolher o seu modelo de gestão, estes modelos denominados Scrum, Kanban, entre outros, serão explicados posteriormente, mas precisamos mencionar que as empresas estão usando preferencialmente dois sites: Trello e Jira (Andrade, 2020). O Trello é uma ferramenta de gestão on-line para métodos ágeis que auxilia na organização de tarefas e possui três versões. O Quadro 1 compara as três versões desta ferramenta. A primeira é o plano básico, que é gratuito e, mesmo assim, permite vários usuários, porém não tem todas as ferramentas dos outros planos. A segunda versão do Trello, chamada Business Class, é paga e possui, por exemplo, relação com o Github. Hoje muitas empresas de tecnologia usam programas como o Github a fim de salvar versões de cada alteração do código automaticamente no servidor da plataforma. A terceira versão, o plano Enterprise, 31 também é paga e possui muitas ferramentas de gerenciamento de acessos a pastas e a convites, entre outras funcionalidades administrativas (Andrade, 2020). Fonte: Adaptado de Trello (2020) O Jira também é uma ferramenta on-line de gestão ágil muito usada em métodos ágeis. Esta ferramenta é mais complexa que o Trello e possui maior processamento e velocidade, seu suporte é muito completo, atende 24 horas por dia, e foi desenvolvido para projetos de software (Andrade, 2020). 32 O Quadro 2 mostra as utilidades do Jira e compara três planos: o básico, gratuito, e os demais, Standart e Premium, sendo este último o mais caro (Andrade, 2020). Fonte: Adaptado de Atlassian (c2020). 9 VALORES E PRINCÍPIOS ÁGEIS De acordo com o site do Manifesto Ágil (BECK et al., c2001), no ano 2001, em Utah, nos EUA, 17 profissionais que praticavam metodologias ágeis se reuniram para praticar Snowbird e conversar sobre métodos de planejamento de software. 33 Criaram, portanto, um documento, chamado de grito de guerra, o Manifesto Ágil, que possui quatro valores (contidos na coluna da esquerda na Figura 1). Estes valores priorizam os pontos a seguir. 1. Pessoas frente aos processos — Este tópico objetiva favorecer relacionamentos no ato da construção do software. 2. Funcionamento versus documentação — Este tópico objetiva favorecer a funcionalidade da criação de software, e não somente o design. 3. Colaboração do cliente, a funcionalidade da criação de software — Este tópico objetiva favorecer a interação contínua com o cliente, a fim de compreender seus desejos e anseios com mais precisão. 4. Consertar problemas e se adaptar a mudanças — Este tópico objetiva favorecer uma construção dinâmica e não engessada, onde consertar problemas é mais importante que manter a burocracia. 34 Fonte: Adaptada de Isotani e Rocha (2020). Perceba o quanto esses valores são aplicáveis. Não se trata de deixar de se organizar ou de documentar, obviamente, mas de tornar o trabalho mais funcional e orgânico. Considere que os profissionais da área de programação ficam sobrecarregados e que a área de tecnologia é estressante; há muita demanda para pouco profissional, e a tendência é aumentar a necessidade de profissionais (Andrade, 2020). Uma situação que pode acontecer durante o desenvolvimento de um software, por exemplo, é, apesar de haver um plano de ação, as pessoas perceberem, em meio ao processo, a necessidade de alterações ou de inclusão de novas funcionalidades. Outro exemplo seria, na criação de um app, constatar que os dados precisam ficar em uma tabela temporária, e então ter que parar tudo e criar um banco de dados temporário no painel do servidor (Andrade, 2020). 35 Agora, imagine que você tem que ficar documentando os detalhes de tudo que fizer. Os programadores ficariam mais sobrecarregados ainda, pois além de programar eles teriam que documentar. Neste ponto, ferramentas como o Github ajudam, pois salvam automaticamenteas versões (Andrade, 2020). Imagine agora que você não permite que seus funcionários mudem um pouco o curso? Isso acabaria atrapalhando um atributo muito importante: funcionalidade! Por isso devemos seguir, sim, as boas práticas, sem, contudo, negligenciar o tempo de execução, pois cada minuto poderia ser um recurso a mais no seu software (Andrade, 2020). Falamos dos quatro valores do Manifesto Ágil. Vejamos agora seus 12 princípios desenvolvimento de software? Eles estão presentes do próprio site oficial do Manifesto Ágil (BECK et al., 2001). • Nossa prioridade é satisfazer o cliente através da entrega contínua e adiantada de software com valor agregado. • Aceitar mudanças de requisitos, mesmo no fim do desenvolvimento. Processos ágeis se adequam a mudanças, para que o cliente possa tirar vantagens competitivas. • Entregar frequentemente software funcionando, de poucas semanas a poucos meses, com preferência à menor escala de tempo. Indivíduos e interação entre eles mais que processos e ferramentas. • Pessoas de negócio e desenvolvedores devem trabalhar diariamente em conjunto por todo o projeto. • Construir projetos em torno de indivíduos motivados, dando a eles o ambiente e o suporte necessário e confiando neles para fazer o trabalho. • O método mais eficiente e eficaz de transmitir informações para e entre uma equipe de desenvolvimento é por meio de conversa face a face. 36 • Software funcionando é a medida primária de progresso. • Os processos ágeis promovem desenvolvimento sustentável. Os patrocinadores, desenvolvedores e usuários devem ser capazes de manter um ritmo constante indefinidamente. • Contínua atenção à excelência técnica e bom design aumentam a agilidade. • Simplicidade: arte de maximizar a quantidade de trabalho não realizado é essencial. • As melhores arquiteturas, requisitos e designs emergem de times auto-organizáveis. • Em intervalos regulares, a equipe reflete sobre como se tornar mais eficaz, e então refina e ajusta seu comportamento de acordo. Como você pode perceber, é um método poderoso, para quem já atua com programação ele faz muito sentido. O sistema tradicional se mostra lento: nele, os clientes chamam a empresa de criação de software, e para fazer a elicitação são necessárias longas visitas técnicas, análises de documentos, entrevistas e análises de processos, para então dar uma data relativamente demorada com um valor relativamente alto para entregar a aplicação. Resultado? Alto custo e lentidão. No sistema ágil o processo é mais fatiado e incrementado. Falaremos agora sobre algumas metodologias famosas (Andrade, 2020). 10 PRINCIPAIS MÉTODOS ÁGEIS EXISTENTES Primeiramente, listaremos os métodos ágeis famosos. O primeiro citado é o Scrum (será explicado adiante), no Trello. Na década de 1990 vários modelos influenciaram na criação de métodos ágeis. Podemos citar como exemplo os 37 seguintes modelos: Desenvolvimento Incremental, DSDM (Metodologia de Desenvolvimento de Sistemas Dinâmicos), Crystal Clear, FDD (Desenvolvimento Direcionado a Funcionalidade), XP (Extrem Programming) e Scrum. (Andrade, 2020). O Quadro abaixo mostra a recomendação de métodos por fases do projeto de desenvolvimento de software. Fonte: Adaptado de Alegría et al. (2011) O primeiro, desenvolvimento incremental, criado pela IBM em 1990, faz a construção do sistema de pedaço em pedaço, ou de incremento em incremento. Geralmente, um pedaço é feito, e após o cliente dá alguns feedbacks. Cada pedaço é uma parte inteira: por exemplo, uma página de login link com acesso a banco de dados é um incremento (SOMMERVILLE, 2011). O segundo método cabível de explicação é a Metodologia de Desenvolvimento de Sistemas Dinâmicos, o qual é usado em situações onde o tempo e a verba são limitados. De acordo com Cruz (2018, p. 316) “[...] tem um 38 conceito mais próximo a um framework do que um método propriamente dito, sua ênfase foca a criação de protótipos que posteriormente evoluem para o sistema, e para isso é utilizada muito fortemente a colaboração próxima com o cliente”. Este método buscar entregar 80% do projeto em 20% do tempo disponível. Ele é composto por três fases: a fase do pré-projeto (onde se elabora o orçamento), a fase do ciclo de vida (onde se analisa a viabilidade) e a fase do pós-projeto (onde ocorrem as alterações), conforme a Figura abaixo: Fonte: Adaptado de Alegría et al. (2011) A Metodologia de Desenvolvimento de Sistemas Dinâmicos é recomendada quando os projetos são urgentes, quando os projetos são formados por uma nova tecnologia e precisam ser acompanhados de testes do usuário, ou quando remetem a um lançamento de produto com data marcada. Qual é a diferença entre este método e outros? Em metodologias tradicionais, como por exemplo, o famoso Project Management Institute (PMI), o cronograma e o orçamento são abertos, e o escopo é fechado; já a Metodologia de Desenvolvimento de Sistemas Dinâmicos 39 tem cronograma e orçamento fechados, mas o roteiro é aberto (SOMMERVILE, 2011). O Crystal Clear (ISOTANI; ROCHA, [20––]) é outra metodologia ágil. Ela atende vários tipos de projetos e tem como valor a comunicação com o cliente, bem como o relacionamento do desenvolvedor com o cliente, a fim de que possa captar com facilidade suas expectativas. Este método evita a criação de ferramentas complexas que não serão utilizadas, objetivando reduzir tempo e custo na entrega. Este método busca diferenciar a metodologia específica conforme a natureza de cada projeto e permite que os desenvolvedores se manifestem quando algo os incomodar. O método Crystal é um cristal em figura elaborado pela gestão e mostrado com uma escala de cores, onde as cores variam de acordo com nível de criticidade e com o tamanho da equipe: quanto mais escuro o cristal, mais crítico de fazer é o software ou projeto; já as cores claras (branco e amarelo) atestam que os software ou projetos serão simples e poucas pessoas serão necessárias; projetos alaranjados ou até vermelhos demandam mais pessoas e são mais arriscados. No Crystal da Figura 3, desenhado pela gestão, C significa confortável e D significa baixo custo, E significa alto custo, e L significa risco de vida. O números indicam o número de funcionários. 40 Fonte: Adaptada de Abrahamsson et al. (2002) A metodologia Feature-driven development (FDD), ou “Desenvolvimento Dirigido por Funcionalidades”, em português, fragmenta os projetos de gestão e de software em features (funcionalidades). Ela foi concebida na década de 1990, e tem como características os pontos, de acordo com Sommerville (2011): • elaboração de lista de tarefas de funcionalidades, ou seja, cada passo tem foco em alguma funcionalidade do software; • planejamento voltado ao método incremental por funcionalidade, ou seja, planeja-se etapas de acordo cada parte. Por exemplo: o primeiro item a ser feito é a área do cliente completa; o segundo item 41 será o carrinho de compras completo; o terceiro item será o catálogo completo, e assim por diante; • design voltado à funcionalidade, ou seja, criar um design que simplifica a navegação e promove a experiência do usuário; • teste de software orientado à funcionalidade, ou seja, testar cada função em detrimento de testar uma interface, por exemplo. Pode-se perceber que neste projeto a soma de cada etapa se faz maior do que o todo; assim, recomenda-se dividir em features curtas, a fim de agilizar a conclusão de cada função, aumentando a eficiência da construção do programa. Por exemplo: cria-se uma função de upload de imagens, cria-se uma função de adicionar produto, e assim sucessivamente. De acordo com Sommervile (2011), o quarto método a ser citado é o XP, mais conhecido como Extremming Programing. Criado em 1996, possui comoprincípios básicos: trabalho de qualidade, mudanças incrementais, feedback rápido e abraçar mudanças. A metodologia possui algumas práticas: • Jogos de planejamento: no início da semana os clientes e developers (desenvolvedores) se reúnem parar priorizar funcionalidades que serão entregues no final da semana. Cada versão deve ser pequena. • Propriedade coletiva: qualquer pessoa do time pode usar o código do programa sem precisar de permissão para alterar, assim todos têm a oportunidade de conhecer o código inteiro e se familiarizar com ele. Isso é muito importante para agilizar manutenções. • Teste de usuário: em equipes pequenas são realizados testes do software por clientes e desenvolvedores da empresa para avaliar sua qualidade. • Ritmo sustentável: as equipes devem trabalhar 40 horas, divididas em 8 horas por dia, sem sobrecarga. 42 • Equipe inteira: as reuniões devem ser em pé e devem ser rápidas. • Programação em par: um desenvolvedor mais experiente fica com um novato, o novato codifica e o mais experiente programa. O benefício deste método é que ele é revisado por duas pessoas. • Padronizações de código: a equipe de dev (developers) determina regras de codificação de salvamento, bem como as boas práticas que devem ser seguidas. Assim, parecerá que foi a mesma pessoa que editou o código, ele ficará com uma “cara” única. • Desenvolvimento orientado a teste: elaborar códigos de forma que sejam capazes de ser testados por software de teste como Imeter (um software que mede desempenho), Selenium (um software que mede erros de sites), etc. • Refatoração: é o processo de otimizar a estrutura do código sem alterar o seu comportamento externo para o usuário final. • Integração contínua: integrar alterações de forma contínua, sem esperar uma semana, por exemplo. Permite saber a real situação do software da programação • Entregas curtas: entregar pequenos pedaços para o cliente corrigir e avaliar, aumentando a probabilidade de acertar o todo. • Metáfora: entender a linguagem e as expressões do cliente. • Design simples: o código deve ter exatamente o que o cliente pediu. A quinta metodologia a ser citada é a Scrum. Ela também é citada por Sommervile (2011). Não citaremos todas que existem, obviamente. Vamos abordar alguns pontos sobre o Scrum (Andrade, 2020). • Três pessoas principais devem ser citadas: 43 • o Product Owner, que define o que comporá o Product Backlog (lista de ações do Sprint) e prioriza isso nas Sprint Planning Meetings (reuniões de planejamento do Sprint); • o Scrum Master (geralmente um gerente ou líder técnico), que verifica se todos seguem as regras e também busca impedir trabalhos excessivos; • o Scrum Team é a equipe de desenvolvimento No Scrum os projetos são divididos em etapas geralmente mensais. Pode- se dizer que são ciclos mensais. Essas etapas chamam-se Sprints. Cada Sprint é um Time Box, uma caixa no tempo com um conjunto de metas. No Scrum existem reuniões diárias, chamadas Daily Scrum. Elas são feitas no início do expediente e revisam os itens do dia anterior e determinam o que será feito no dia. As funcionalidades são agrupadas em uma lista. Product Backlog é esta lista; ela contém todas as funcionalidades necessárias para um produto, e é feita pelo Product Owner. O Sprint Planning Meeting é uma reunião feita no início de cada Sprint. Nesta reunião estarão presentes o Product Owner, o Scrum Master, o Scrum Team e interessados. Nesta reunião o Product Owner determina as prioridades, e todos juntos definirão um objetivo para aquele Sprint. Este objetivo Sprint será revisado na Sprint Review Meeting, uma reunião com o Product Owner, o Scrum Team, o Scrum Master, e algumas vezes com gerência e clientes, a fim de revisar o que foi atingido e o que não foi. O Release Burndown Chart é uma análise de metas atingidas no final de cada Sprint (iteração). 44 11 TEST-DRIVEN DEVELOPMENT Dentre as etapas genéricas presentes nos métodos de desenvolvimento de software trabalhados pela literatura, a etapa de testes sempre esteve presente. Após a popularização dos métodos ágeis, uma forma de desenvolver com foco nos testes emergiu: o test-driven development (TDD). Nesse método, o teste deixa de ser etapa e passa a ser o elemento protagonista, guiando o desenvolvimento do início ao fim do projeto e invertendo a lógica de programar e depois testar, pois o teste passa a ser a primeira ação nas iterações (Ladeira, 2020). Neste capítulo, você estudará sobre o TDD, um método de desenvolvimento dirigido por testes, conhecendo seus conceitos e sua história. Você estará apto a entender o processo de desenvolvimento de software com o TDD, bem como a identificar os seus benefícios e as suas limitações (Ladeira, 2020). 11.1 SURGIMENTO DE UM MÉTODO DE DESENVOLVIMENTO DIRIGIDO POR TESTES O TDD, também chamado de “desenvolvimento guiado por testes”, é um método ágil de desenvolvimento que concentra os esforços de desenvolvimento na capacidade de projetar testes e elaborar até que o componente esteja completamente codificado e todos os testes executem sem erro (PRESSMAN; MAXIM, 2016). Nesta seção, você aprenderá um pouco sobre a história deste método, e compreenderá os conceitos centrais por ele utilizados (Ladeira, 2020). 45 11.2 HISTÓRICO No ano de 2001, um grupo de desenvolvedores propôs mudanças nos elementos essencialmente valorizados nos processos de desenvolvimento tradicionais, o que culminou na elaboração do Manifesto para Desenvolvimento Ágil de Software (BECK et al., 2001). Esse manifesto motivou a criação de diversos métodos ditos ágeis, entre os quais estão Scrum, XP e outros. Um dos idealizadores do manifesto foi Kent Beck, um engenheiro de software estadunidense que já trabalhava em uma iniciativa de método ágil chamada extreme programming (conhecido pela sigla XP ou também por “programação extrema”, em tradução livre), que veio a se popularizar (Ladeira, 2020). O método XP sempre conteve o teste entre as suas práticas. No contexto do XP, a prática estava descrita como test-first (teste antes). Com o passar do tempo, Beck percebeu que tal prática poderia ser desmembrada do XP e utilizada de forma isolada, configurando-se em um novo método de desenvolvimento. De acordo com Prikladnicki, Willi e Milani (2014), Beck publicou um artigo em 2001 relatando a prática como sendo uma técnica de design, e não propriamente de testes, e, em 2003, publicou um livro sobre esta técnica, passando a chamá-la de test-driven development (Ladeira, 2020). O interessante é que Beck não se considera o criador do TDD, mas sim o seu redescobridor, pois alega que essa prática é muito antiga. Mas será que é mesmo? Conforme Larman e Basili (2003), sim: a IBM, na década de 60, executou um projeto para a NASA chamado Mercury. Neste projeto, foi utilizada a prática de teste-antes para cada pequeno incremento desenvolvido (Ladeira, 2020). O registro mais antigo, entretanto, é de McCracken (1957), que relata a necessidade de se trabalhar com ciclos curtos iterativos e de se elaborar o que chamava de casos de verificação antes do código a fim de evitar erros lógicos e mal-entendidos. Assim, o primeiro a mencionar as práticas do TDD é Daniel 46 McCracken, mas Beck é o responsável por redescobrir a técnica e sistematizá-la, além de ser um dos grandes responsáveis pela sua popularização (Ladeira, 2020). 11.3 CONCEITOS DO TEST-DRIVEN DEVELOPMENT De acordo com Beck (2011 apud PRIKLADNICKI; WILLI; MILANI, 2014, p. 201), “desenvolvimento dirigido por testes é um conjunto de técnicas [...] que encorajam design simples e suítes de teste que inspiram confiança”. Segundo Pressman e Maxim (2016, p. 854), “[...] o TDD não é realmente uma nova tecnologia, mas sim uma tendência que enfatiza o projeto de casos de testeantes da criação do código-fonte”. Neste modelo o elemento central é o teste, um conjunto de ações relacionadas a um diagnóstico. Um teste sempre pode passar, o que significa que o resultado retornado é o resultado esperado, ou falhar, o que significa diferença entre esses resultados (Ladeira, 2020). O tipo de teste que é realizado em todas as unidades de código criadas é o teste unitário. Neste teste todas as pequenas porções de códigos criadas são testadas por meio da citada comparação entre o valor esperado e o valor retornado. A menor unidade testável pode variar de paradigma para paradigma, podendo ser um arquivo, uma classe, um método, uma função etc (Ladeira, 2020). O modelo de desenvolvimento dirigido por testes realmente tem design simples. Ele é modelado em três estados: vermelho, verde e refatorar. A Figura 1 resume o modelo visualmente. Nela podemos ver três círculos, cada um representando um dos estados do TDD (Ladeira, 2020). 47 Fonte: Ladeira (2020) A ação inicial no TDD, antes mesmo de iniciar a implementação do componente, é a elaboração do teste automatizado. Este teste inevitavelmente falhará, pois ainda não há código. O conceito de falha está atrelado ao estado vermelho, o primeiro estado possível definido pelo método. O conceito de sucesso é modelado pelo estado verde, obtido a partir do momento em que o desenvolvedor ou a equipe de desenvolvimento implementa um código que passa pelo teste. Já o 48 conceito de refatoração é modelado pelo terceiro e último estado: refatorar. Mas o que é refatoração ? (Ladeira, 2020). A refatoração é um conjunto de ações de aprimoramento da estrutura interna do componente. Estas ações envolvem (Ladeira, 2020). • eliminação de redundâncias no código; • renomeação dos identificadores dos elementos internos, atendendo aos padrões exigidos pela equipe; • uso de estruturas de dados mais eficientes; • uso de soluções mais eficientes; • uso de bibliotecas mais adequadas; • melhoria no gerenciamento de recursos. Após a refatoração, o ciclo é reiniciado com um teste automatizado que falha. Existe ainda outro conceito importante relativo aos testes: como garantir que uma alteração em um código novo não interferiu negativamente em uma unidade já criada — e testada — anteriormente? É equivocado pensar que uma unidade já testada não pode apresentar erros em função de outra criada depois, já que é normal existir interdependência entre os componentes criados. A técnica de testes de regressão, no entanto, é a proposta de solução nestes casos. Como os testes são automatizados e escritos antes dos códigos, tem-se o histórico de todos os testes realizados anteriormente; assim, para garantir que novas unidades não tenham inserido erros em unidades já criadas, basta realizar os testes automatizados novamente (Ladeira, 2020). A ação de elaborar testes automatizados faz com que a equipe ganhe tempo. Afinal, é mais rápido realizar testes de forma automática do que exigir a ação humana para interagir com o componente, além de possibilitar que um conjunto grande de dados seja testado. Essa ação contribui para a confiabilidade do 49 componente, pois passar por um conjunto volumoso de testes automatizados minimiza as chances de problemas futuros. Prikladnicki, Willi e Milani (2014) complementam afirmando que o teste automatizado é composto por etapas de preparação, exercício e verificação, e definem como fundamental que o teste seja autocontido, isto é, não dependa de intervenção humana para decidir o resultado Como o TDD preconiza a realização de testes que sejam automáticos, por vezes é necessário simular comportamentos de outros componentes que interagem com a unidade testada mas não estão disponíveis neste formato ou não são viáveis de se utilizar por questões de, por exemplo, desempenho ou complexidade. Alguns componentes nessas situações estão relacionados à elaboração de interfaces cujo comportamento depende da forma de ação do usuário, de acesso a arquivos externos, a sistemas de gerenciamento de banco de dados ou até mesmo à transmissão de dados em uma rede de computadores. Nestes casos, para viabilizar os testes, foi proposto o conceito de mocks. Os mocks, também chamados de fake mocks, mock objects ou objetos de simulação, representam simulações dos componentes necessários. Esses objetos tentam reproduzir os objetos reais que por algum motivo não podem ser utilizados nos testes (Ladeira, 2020). Entre os motivos válidos para o uso de mocks estão, por exemplo, a indisponibilidade do componente, seja por complexidade ou por ainda não estar desenvolvido, ou até mesmo o desempenho do componente real, tal como um sistema de gerenciamento de banco de dados cujas consultas sejam demoradas. Assim, o uso de um objeto de simulação pode ser vantajoso por poupar tempo da equipe de desenvolvimento e abstrair detalhes do componente (Ladeira, 2020). A equipe de desenvolvimento pode elaborar seus próprios mocks ou utilizar frameworks de suporte a mocks disponíveis para as principais linguagens de programação do mercado (Ladeira, 2020). 50 11.4 O TEST-DRIVEN DEVELOPMENT NO CICLO DE VIDA DO SOFTWARE Nesta seção analisaremos o TDD no ciclo de desenvolvimento de software do ponto de vista de sua aplicabilidade. Discutiremos também a geração de documentação para este método (Ladeira, 2020). Pensemos agora na aplicabilidade do TDD por meio de um exemplo: uma função que recebe um número natural (inteiro positivo) n entre 0 e 50 (inclusive) e deve retornar o n-ésimo termo da Série de Fibonacci (Ladeira, 2020). A Série de Fibonacci é uma sequência numérica na qual o primeiro valor é o 0, o segundo é 1, e do terceiro valor em diante o elemento é sempre igual à soma dos dois elementos anteriores. Os 10 primeiros elementos desta série são: 0, 1, 1, 2, 3, 5, 8, 13, 21 e 34. Você consegue identificar os elementos seguintes? (Ladeira, 2020). O n-ésimo termo da Série de Fibonacci também é um número natural e, portanto, a função recebe um número natural como parâmetro e retorna outro número natural. Em uma pseudolinguagem de programação, a assinatura da função seria como segue: inteiro termoFibonacci(inteiro n) Em um teste realizado com o parâmetro n = 7, qualquer resultado retornado diferente de 8 resultará em um teste que falha, já que o valor esperado para o sétimo termo de Fibonacci é 8. Se, por algum motivo, um teste com n = 7 resultar em valor diferente de 8 e o teste não falhar, o problema estará no caso de teste projetado. Como o nosso método termoFibonacci seria testado utilizando a abordagem do TDD? Inicialmente, precisaríamos escrever um teste, utilizando nossa pseudolinguagem de programação (Ladeira, 2020). 51 testaTermoFibonacci() { inteiro n = 10 inteiro resposta inteiro respostaEsperada = 34 resposta = termoFibonacci(n) escreva verificaIgualdade(respostaEsperada, resposta) } O teste demonstrado no exemplo é o teste unitário, sendo aplicado a uma pequena porção testável (neste caso, o método termoFibonacci). O teste verifica através do método verificaIgualdade se o valor retornado pelo método termoFibonacci é igual ao valor da variável respostaEsperada (34, neste caso). Por estarmos utilizando uma pseudolinguagem, considere que verificaIgualdade é um método predefinido para comparação de valores. Os frameworks de testes unitários costumam implementar este método com um nome semelhante a assertEqual, mas há várias abordagens possíveis para escrever um teste unitário (Ladeira, 2020). As ferramentas de testes unitários criam seus métodos comparativos, mas você pode elaborar testes sem depender destas ferramentas. Podemos elaborar uma resposta diferente para o nosso exemplo da seguinte maneira: lógico testaTermoFibonacci() { inteiro n = 10 inteirorespostaEsperada = 34 retorna (respostaEsperada == termoFibonacci(n)) } Neste exemplo, o retorno de testaTermoFibonacci é um valor lógico (verdadeiro ou falso) que deve ser manipulado ou apresentado no trecho de código que invoca o método. Este retorno faz justamente a comparação de igualdade entre a resposta esperada e a resposta 52 calculada, retornando este valor. Note que aqui não foi utilizada a variável resposta, mas o resultado é igualmente válido (Ladeira, 2020). Como não há implementação de termoFibonacci, o teste falha, pois é necessário implementar o método. Suponha agora que o desenvolvedor, utilizando recursividade (chamadas sucessivas da função a ela própria), implementou o método termoFibonacci na nossa pseudolinguagem de programação: inteiro termoFibonacci(inteiro n) { se (n == 1) { retorna 0 } se (n == 2) { retorna 1 } retorna termoFibonacci(n-1) + termoFibonacci (n-2) } Agora, ao executar o nosso teste, o estado atingido é o verde, pois temos um código implementado que funciona para o nosso teste. Note que, se a entrada for 1, ou seja, deseja-se o primeiro termo, a saída será zero, pois o primeiro valor da série é zero. Se a entrada for dois, a saída será um, pois um é o segundo valor da série. Se a entrada for três, cai-se no caso da recursão, chamando termoFibonacci(3-1) e termoFibonacci(3-2), ou seja, chama-se recursivamente novamente a função duas vezes, uma com o valor 2, outra com o valor 1, e soma- se o resultado de ambas. Neste caso, 0 + 1 = 1, ou seja, o terceiro termo é também o valor 1. Cabe citar que, em uma situação real, um conjunto de dados de teste mais 53 volumoso seria utilizado e testado de forma automatizada, não apenas um teste com a entrada 10 (Ladeira, 2020). O próximo estado previsto no TDD é refatorar. Para atingir esse estado é necessário verificar a possibilidade de realizar melhorias internas no método termoFibonacci. Isso é possível? Entre várias ações possíveis, eliminar redundâncias e padronizar o código, caso a empresa de desenvolvimento utilize algum padrão, são algumas das ações necessárias. Não havendo a necessidade de realizar estas ações, precisamos pensar na sequência de instruções do método. O primeiro teste realizado é sempre a verificação se o número é um. No entanto, o comando de retorno deste teste só será executado quando o valor de n for um. Para todos os outros números aceitos na entrada, um dos testes de parada será n == 2. Por exemplo, ao chamar termoFibonacci(2), o teste n == 1 resultará falso, mas o teste n == 2 resultará verdadeiro. Assim, a simples inversão de ordem destas estruturas condicionais já configura uma comparação a menos, representando uma pequena melhoria no código: inteiro termoFibonacci(inteiro n) { se (n == 2) { retorna 1 } se (n == 1) { retorna 0 } } retorna termoFibonacci(n-1) + termoFibonacci(n-2) 54 } (Ladeira, 2020). Ainda assim, as diversas chamadas recursivas continuam sendo bastante custosas, pois a função chama a si própria diversas vezes. Há ainda a possibilidade de juntar os dois testes, economizando linhas de código: inteiro termoFibonacci(inteiro n) { se (n == 2 ou n == 1) { retorna n-1 } retorna termoFibonacci(n-1) + termoFibonacci(n-2) } O código ficou pequeno, elegante e não conta com redundâncias, mas ainda está custoso em termos de tempo e espaço (memória utilizada). Soluções recursivas costumam utilizar muita memória e, conforme entradas grandes são utilizadas (ou seja, os maiores valores válidos para n), o tempo de processamento passa a ser maior. Em uma situação como essa, é necessário pensar em melhores soluções para o consumo desses recursos. Neste caso a solução iterativa, embora gere mais código, é a mais eficiente, pois acessa menos vezes a memória e utiliza apenas a alocação de um vetor no início do código (Ladeira, 2020). inteiro termoFibonacci(inteiro n) { n = n – 1 se (n == 0 ou n == 1) { 55 retorna n } inteiro acumulador[50] acumulador [0] = 0 acumulador [1] = 1 inteiro índice para (indice = 2; indice <= n; indice++) faça { acumulador[indice] = acumulador[indice-1] + acumulador[indice-2] } return acum[n]; } } Agora, tem-se uma função de cálculo do n-ésimo termo de Fibonacci de forma iterativa. Esta implementação utiliza um vetor de 50 posições, pois este será o valor máximo para n. O índice do vetor de 50 posições inicia em zero, então, para um vetor de n posições, o vetor deverá variar de 0 a n-1, por isso a primeira instrução decrementa 1 unidade do valor de n. Com este vetor o espaço de memória determinado para resolver a função já está alocado, diferentemente da solução recursiva, que gera diversas chamadas à função de forma dinâmica. A função recursiva, se implementada em uma linguagem de programação, pode ocasionar erros de execução em função das diversas chamadas recursivas, necessitando de mais espaço de memória do que o disponível, situação altamente indesejada e que deve ser evitada no estado de refatoração previsto no TDD (Ladeira, 2020). 56 Podemos supor, ainda, que a empresa exige que existam comentários dentro da função, facilitando assim o entendimento de quem precisar manter o código. Esta ação também deve ser realizada na etapa de refatoração. Assim, o código comentado fica como segue (Ladeira, 2020). /* Função que recebe um valor n inteiro positivo e retorna um valor inteiro positivo que representa o n-ésimo termo da Série de Fibonacci */ inteiro termoFibonacci(inteiro n) { /* Decrementa n em uma unidade para trabalhar com as posições corretas do vetor, de 0 até n-1, resultando em n posições */ n = n – 1 /* Como os dois primeiros valores são conhecidos, retorna-se 0, se n for igual a zero, e 1, se n for igual a 1 */ se (n == 0 ou n == 1) { retorna n } /* Declaração do vetor de tamanho 50, a maior entrada possível neste caso */ inteiro acumulador[50] /* Atribui às duas primeiras posições do vetor os dois valores iniciais da Série de Fibonacci. O primeiro elemento recebe o valor zero e o segundo elemento recebe o valor um */ acumulador [0] = 0 57 acumulador [0] = 0 /* Declara o indice para percorrer o vetor */ inteiro índice /* Percorre o vetor a partir da posição 2 (a terceira posição), preenchendo todos os valores com a soma dos dois anteriores, conforme manda a definição da Série de Fibonacci */ para (indice = 2; indice <= n; indice++) faça { acumulador[indice] = acumulador[indice-1] + acumulador[indice-2] } /* Retorna o último valor do vetor, que será o n-ésimo termo da Série de Fibonacci */ return acum[n]; } Notou a diferença? O código, agora iterativo, está mais eficiente em consumo de recursos e dentro do padrão (hipotético) de comentário da empresa (Ladeira, 2020). Após concluída a etapa de refatoração, o componente é entregue ou incrementado com base em novos testes, que inicialmente falham, e assim inicia- se o ciclo novamente. No nosso exemplo poderia haver a necessidade de mais um incremento na unidade, exigindo também a implementação do método fibonacciAteN, para imprimir todos os números daSérie de Fibonacci até o n-ésimo termo, passado como parâmetro (Ladeira, 2020). 58 Um aspecto importante que podemos discutir é a ausência de qualquer diretriz sobre os requisitos. Assume-se que o desenvolvedor compreendeu corretamente os requisitos necessários para desenvolver a funcionalidade. Isto se materializará diretamente nos códigos dos casos de teste. No entanto, nada o impede de, antes, modelar os requisitos com outra técnica, tal como a criação de histórias de usuário ou casos de uso (Ladeira, 2020). Vimos o exemplo da função termoFibonacci em um pseudocódigo estruturado válido. Agora vejamos como isso seria feito em uma linguagem de programação, em uma situação prática real. Tomemos como exemplo a linguagem C++, utilizando-a também de forma estruturada para facilitar o seu entendimento, juntamente com o compilador g++. O código de teste seria como segue (Ladeira, 2020). #include <iostream> #include "fi bo.h" using namespace std; int main() { long long int respostaEsperada = 34; long long int n; long long int resposta; n = 10; resposta = termoFibonacci(n); if (resposta == respostaEsperada) cout << "Passou no teste!" << endl; else cout << "Falhou no teste!" << endl; 59 cout << "Entrada informada: " << resposta << endl; cout << "Resposta informada: " << resposta << endl; cout << "Resposta esperada: " << respostaEsperada << endl; return 0; } Nosso código de teste resolve o mesmo problema que o código que construímos na pseudolinguagem, mas note que ele tem algumas diferenças. Ele verifica a resposta obtida e a resposta informada. Se forem iguais, informa que passou no teste; se forem diferentes, informa que o teste falhou. Depois disso, a entrada e as respostas informada e esperada são impressas na tela. Esta é uma boa prática, pois assim o arquivo de saída registra mais informações do teste, não somente “verdadeiro” ou “falso”. Outra diferença é o uso do tipo long long int, para valores grandes. Por uma limitação de arquitetura, o tipo inteiro (int, em C++) não seria capaz de receber valores grandes, como ocorre, por exemplo, quando n é 50 (Ladeira, 2020). Agora, para poder sair do estado vermelho, nosso código precisa funcionar. Para poder desenvolvê-lo, já incluímos o arquivo fibo.h, portanto devemos criar este arquivo e acrescentar a assinatura da função desenvolvida nele (Ladeira, 2020). long long int termoFibonacci(long long int n); Agora, criamos o nosso código recursivo para passar no teste, no arquivo fibo.cpp: 60 long long int termoFibonacci(long long int n) { if (n == 2 || n == 1) return n - 1; return termoFibonacci(n-1) + termoFibonacci(n-2); } Para executar e compilar o código, você pode utilizar o comando abaixo: g++ testaTermosFibonacci.cpp fibo.cpp -o fibo; ./fibo A resposta obtida deve ser: Passou no teste! Entrada informada: 34 Resposta informada: 34 Resposta esperada: 34 Assim, como o código funciona, estamos no estado verde. No entanto, como já vimos, você pode tentar trocar o valor e executar para n = 50 para ver o tempo e a memória sendo consumidos. Para isso, devemos refatorar o código conforme realizamos no pseudocódigo. O nosso arquivo fibo.cpp deve ser refeito, tal como o código abaixo (Ladeira, 2020). /* Função que recebe um valor n inteiro positivo e retorna um valor inteiro positivo que representa o n-ésimo termo da Série de Fibonacci */ 61 long long int termoFibonacci(long long int n) { /* Decrementa n em uma unidade para trabalhar com as posições corretas do vetor, de 0 até n-1, resultando em n posições */ n--; /* Como os dois primeiros valores são conhecidos, retorna-se 0, se n for igual a zero, e 1, se n for igual a 1 */ if (n == 0 || n == 1) return n; /* Declaração do vetor de tamanho 50, a maior entrada possível neste caso */ long long int acumulador[50]; /* Atribui às duas primeiras posições do vetor os dois valores iniciais da Série de Fibonacci. O primeiro elemento recebe o valor zero e o segundo elemento recebe o valor um */ acumulador[0] = 0; acumulador[1] = 1; /* Declara o indice para percorrer o vetor*/ int indice; /* Percorre o vetor a partir da posição 2 (a terceira posição), preenchendo todos os valores com a soma dos dois anteriores, conforme manda a 62 definição da Série de Fibonacci */ for(indice = 2; indice <= n; indice++) acumulador[indice] = acumulador [indice-1] + acumulador[indice-2]; /* Retorna o último valor do vetor, que será o n-ésimo termo da Série de Fibonacci */ return acumulador[n]; } Novamente, ao compilar e executar o código, a resposta obtida deve ser: Passou no teste! Entrada informada: 34 Resposta informada: 34 Resposta esperada: 34 Desta forma, conseguimos transcrever para uma linguagem de programação o pseudocódigo inicial, realizando testes automáticos e melhorando a implementação inicial da função termoFibonacci (Ladeira, 2020). 11.5 DOCUMENTAÇÃO Até este ponto da leitura você pode estar se perguntando se (ou quando) o TDD prevê a elaboração de documentação. A resposta a esta pergunta é mais simples do que parece: a documentação já está elaborada! No TDD a documentação é todo conjunto de testes implementados, pois eles mostram a definição do comportamento esperado pelos componentes, sua interface e os tipos 63 dos dados definidos. Testes unitários, quando bem escritos, podem especificar o trabalho do código funcional (Ladeira, 2020). Por menor que seja o software desenvolvido, se a prática adotada for o TDD, os testes devem permanecer armazenados, até mesmo porque estes testes, enquanto documentos, são trabalhados em todos os estados do TDD. Além disso, os testes realizados apoiam os futuros testes de regressão. Lembre-se de que um software se diferencia de um programa por conter dados de configuração e documentação. Portanto, a documentação é parte do software (Ladeira, 2020). No caso do código apresentado, os arquivos que implementam o método de teste (testaTermoFibonacci) fazem parte da suíte de testes, bem como fariam os métodos que testam os métodos de subtração e de outras operações, se for o caso do software em desenvolvimento. Se, futuramente, por algum motivo, o método de teste precisar de correções ou alterações em virtude de mudanças de requisitos, mantém-se o arquivo anterior e cria-se o novo. Todos os arquivos de teste gerados formam a suíte de testes e ajudam a compreender o histórico de desenvolvimento do software (Ladeira, 2020). 12 VANTAGENS E DESVANTAGENS DO TEST-DRIVEN DEVELOPMENT Todo método de desenvolvimento de software tem suas especificidades. Alguns podem ser mais vantajosos em determinadas situações, outros podem apresentar limitações. O profissional que lidera as decisões da equipe de desenvolvimento de software deve estar ciente das características dos métodos, pois esta escolha é vital para o andamento do trabalho (Ladeira, 2020). 64 12.1 VANTAGENS DA UTILIZAÇÃO DO TEST-DRIVEN DEVELOPMENT Como você pode analisar na leitura do capítulo, uma das principais características do TDD é a simplicidade. Este método apresenta transições de estado bem definidas, não ambíguas e em pouca quantidade,
Compartilhar