A adaptação de métodos ágeis dentro de uma empresa é uma tarefa desafiadora. Agilidade não é como um mero software que pode simplesmente ser instalado algum dia. Ao invés disso, agilidade precisa ser adaptada ao contexto da empresa, incluindo seus aspectos culturais, técnicos e organizacionais. Este artigo explora os desafios associados com a configuração de ambientes de desenvolvimento, testes automatizados, integração contínua e específica a definição de pronto dentro do contexto das empresas.
Configuração de Ambientes de Desenvolvimento
Todo líder técnico e gerente de desenvolvimento quer reduzir o tempo gasto na configuração de ambientes de desenvolvimento para os membros de suas equipes. Ainda, os desenvolvedores continuam a gastar algum tempo tentando manter as coisas organizadas visando produtividade em seus projetos. A falta de documentação sobre configuração de ambientes de desenvolvimento é a principal razão pela qual o tempo gasto nesta etapa é grande. A segunda maior razão é o número de passos manuais envolvidos no processo de configuração. Então, como superar estes desafios? Para a documentação há algumas orientações que eu sigo – simplicidade, atenção aos detalhes e automatização. A simplicidade se refere a manter os documentos simples de se criar, manter, visualizar e distribuir. Eu utilizo um wiki para gerenciar o conteúdo relacionado à configuração do ambiente de minha equipe. A página wiki tem um responsável e é atualizada como parte da iteração. Atenção aos detalhes se refere a manter orientações objetivas e claras ao documentar o que é necessário configurar. Isto significa documentar qualquer mínimo detalhe que um desenvolvedor for precisar para começar a escrever código e integrar com o restante do trabalho da equipe. Aqui está o que eu mantenho na página sobre os ambientes de desenvolvimento no wiki:
- Lista dos pacotes de software a serem instalados: no meu caso, esta seção deve conter o Java Developer Kit (JDK), o ambiente de desenvolvimento integrado (IDE) Eclipse, Apache Ant, Apache Axis e o SQL Server Management Express.
- Para cada pacote, incluir a localização (driver de rede/Internet/Intranet/outros) e as credenciais necessárias, se for o caso. Por exemplo, para o Apache Ant, a localização deve ser nosso repositório Subversion. O caminho relativo é especificado a partir da cópia de trabalho do Subversion - <svn-home>/Software/Apache/Ant/1.7.0.
- Ainda para cada pacote, descrever as variáveis de ambiente de sistema e locais a serem configuradas na máquina. Por exemplo, o Apache Ant necessita da variável ANT_HOME e o Apache Axis2 precisa que a variável de ambiente AXIS2_HOME esteja definida e ambas devem apontar para as respectivas estruturas de pastas na máquina desenvolvimento.
- Lista das bibliotecas adicionais a serem obtidas – isto pode incluir arquivos java (JARs), DLLs .NET ou outros. Um exemplo de biblioteca pode ser os JARs de drivers de conectividade com banco de dados em Java (JDBC) para acesso ao Microsoft SQL Server 2005 ou JARs para se trabalhar com o IBM Websphere MQ.
- Como obter acesso de usuário ao gerenciador de fila, ao servidor de banco de dados e a máquinas remotas – pessoas de contato bem como informações relevantes sobre procedimentos a serem tomados. Detalhes como as credenciais de uma aplicação no ambiente de desenvolvimento devem ser especificados aqui. Por exemplo, eu especifico um template de e-mail com nome do usuário de login, um identificador de nossa equipe e o nome da pessoa de contato a ser enviado para nosso grupo de suporte de primeiro nível para acessar um gerenciador de fila.
- Como o código fonte está organizado? Como obter acesso ao repositório de código fonte? Esta seção deve fornecer um resumo sobre organização de código. Por exemplo, eu organizo o código baseado em dados do domínio da aplicação (dados de clientes, dados de contas, dados de documentos) e também nos principais utilitários reutilizáveis (ex., registro de log, roteador, manipulador de exceções, gerenciador de notificações, etc). Esta seção também fornece a localização do tronco principal do repositório Subversion além de instruções adicionais de como se obter acesso de escrita no repositório.
- Configuração da cópia de trabalho (ou cópia local do desenvolvedor) de código a partir do controle de versão. Esta seção contém instruções sobre a localização da cópia de trabalho baseadas nas políticas de ambiente de trabalho de nossa empresa. Por exemplo, uma dada pasta acesso de escrita ainda que os usuários não possuam permissões em outras pastas.
- Localização de arquivos importantes – logs de aplicação, arquivos de erros, logs de rastreamento de servidor, dumps de processos. Exemplos do conteúdo desta seção incluem o caminho de arquivos para os logs do container de servlets do Tomcat e arquivos bindings do Websphere MQ.
- Navegando em filas e o processo de adição de filas. Esta seção irá apontar as filas salientes que um desenvolvedor deve estar atento em nosso gerenciador de filas. Também fornecerá as convenções de nomenclatura bem como informações de suporte para criação de novas filas.
- Navegação em tabelas e criação de objetos de bases de dados como tabelas, views e stored procedures. No meu caso, esta seção apresenta a documentação da base de dados gerada para nosso banco SQL Server 2005 utilizando o SchemaSpy.
- Scripts/utilitários utilizados pelos desenvolvedores – ferramentas de desenvolvimento que automatizam tarefas rotineiras. Exemplo aqui incluem scripts do Apache Ant que compilam e executam suites de testes unitários do JUnit, bem como aqueles que geram javadocs a partir do código fonte.
Talvez o aspecto mais importante da configuração do ambiente do desenvolvedor seja a automatização. Automatização possui diversas vantagens – ela pode reduzir significativamente ou até eliminar os erros de configuração oferecendo consistência a todo o procedimento. Seguem aqui alguns dos desafios à automatização:
- 1.Falta de privilégios administrativos nas máquinas: administradores de sistemas podem não dotar os desenvolvedores com as devidas permissões nas máquinas de desenvolvimento por razões de segurança e de políticas organizacionais. Isto pode dificultar instalações de software, definições de variáveis de ambiente e execução de scripts.
- 1.Necessidade de coordenação com grupos externos: grupos externos podem realizar instalações de software, dar permissões de acesso ou aprovar solicitações de instalação, etc.
- 1.Necessidade de demandar soluções a terceiros: grandes empresas compartilham infraestrutura para tarefas de suporte intermediárias (serviços de mensagens, barramento de serviço corporativo, adaptadores de aplicações) e interagem com elas quase sempre por meio de tíquetes de suporte.
Enfrentar tais desafios pode nem sempre ser possível, mas seguem aqui algumas ideias de como lidar com eles. Para todos os softwares necessários para um desenvolvedor – independentemente de de onde eles foram obtidos – adicione-o ao sistema de controle de versão. Certifique-se de organizar os pacotes de software utilizando convenções de nomenclatura e de localização que possam indicar seus números de versão e dependências de bibliotecas. Isto vai garantir que cada desenvolvedor possa ter acesso aos pacotes de software de que precise sem o inconveniente de precisar contatar diversas pessoas em sua empresa. Para pacotes de software que precisem ser instalados por equipes de suporte externas, crie scripts que gerem tíquetes, ou mesmo que já os submeta programaticamente, se possível. Uma vez que eles obtenham uma cópia dos arquivos a partir do controle de versão, você vai querer criar um script de "setup". No meu caso, isto é um script Apache Ant que define as variáveis de ambiente, criar um id/senha para o usuário em nossa base de dados de desenvolvimento, copia registros de licença do driver de rede para o perfil do usuário no Windows e gera mensagens XML de exemplo para diversos webservices com os que a equipe interage.
Testes Automatizados e Integração Contínua
Desenvolvedores não automatizam o teste por várias razões técnicas. Ouço coisas como:
- "Eu tenho uma ferramenta que eu prefiro" - pode ser uma que o próprio desenvolvedor criou ou alguma open-source ou de algum fornecedor. A ferramenta tem limitações que o desenvolvedor recusa-se a reconhecer.
- "Não tenho dados de teste" - especialmente quando coordenando dados que precisam estar correlacionados entre múltiplos sistemas/processos.
A questão da ferramenta pode ser resolvida de várias maneiras. Você pode mostrar aos desenvolvedores os benefícios de usar uma ferramenta de teste padrão como JUnit ou NUnit apontando a possibilidade de integrá-la via script e a habilidade de executar testes de forma consistente e repetida. Numa grande empresa, é improvável que todos os desenvolvedores usem uma única ferramenta. É mais realístico pelo menos um conjunto padrão de ferramentas para as equipes de desenvolvimento dentro de um departamento. O que eu faço é fornecer padrões de scripts de testes automatizados - scripts que compilam e executam casos de teste JUnit, geram relatórios e enviam os resultados por e-mail - como parte de cada ambiente de desenvolvimento. Quando o desenvolvedor baixa uma cópia de trabalho pelo sistema de controle de código fonte o script de teste já vem junto e os casos de teste individuais vão sendo adicionados. Nossa equipe tem uma estrutura de pastas padrão para colocar os casos de teste e as suites de teste para o script executar. Adicionalmente, garanto que nas revisões de código, não somente o código da aplicação, mas também o código de teste seja revisto. Como parte da revisão, os casos de teste que não estão na suite de testes automatizados são refatorados. Isso também se aplica a testar código que contém dados "hard-coded" ou dados de um banco de dados. Por exemplo, um método de teste especifica que o perfil de acesso do usuário seja BRANCH_SUPERVISOR ao invés de obtê-lo de um arquivo de configuração. Após a revisão, essa propriedade é adicionada a um arquivo de propriedades e o método de teste é refatorado para acessar o perfil do usuário a partir do arquivo. Então, essa propriedade fica disponível para testes adicionais.
Em projetos envolvendo SOA, onde serviços se integram com servidores de aplicação, sistemas legados, fontes de dados, pacotes de aplicativos, uma bateria de testes sistêmicos é essencial. Estes podem inicialmente ser feitos com objetos que simulam as respostas dos servidores integrados (mock objects). Eventualmente, você vai querer que os testes rodem contra pontos reais de integração. Neste caso, os dados de teste e os dados que atravessam múltiplos sistemas tornam-se extremamente críticos. A falta de dados de teste, especialmente aqueles que se correlacionam com múltiplos sistemas é uma grande razão para a ineficácia dos testes automatizados. Dependendo da complexidade dos dados e do número de sistemas em jogo, você pode realizar isso em fases. Você pode começar com uma cópia local dos dados onde seus desenvolvedores consultam múltiplas fontes de dados e populam a base local. Isto pode soar como uma tarefa muito manual, mas se os dados são novos e os processos de ETL para populá-los ainda não se completaram, essa pode ser a maneira mais simples de começar. Ao longo do tempo você pode construir um conjunto de classes de teste que encapsulem dados de teste de múltiplas bases de dados, garantam a validade/qualidade dos dados e também populem dados de teste como parte do script de integração contínua. Independente disso, se você desacoplar o código do seu caso de teste da obtenção dos dados de teste será mais fácil mudar a fonte de dados e não invalidar o código. Minha equipe inicialmente usou arquivos de propriedades para alimentar dados de teste e depois migrou para um conjunto de classes de acesso a dados para obter os dados de teste necessários. Por exemplo, nós usamos o design pattern DAO (Data Access Object) para criar um conjunto de classes java que encapsulam operações em dados de clientes e de contas. Essas classes proveem uma interface simples com os métodos CRUD que os métodos de teste JUNIT podem acessar. Se um método de teste necessita obter dados de cliente, ele importa a fábrica DAO e as classes DAO específicas e invoca o método getCustomer(). Uma vez obtido um objeto cliente, o teste pode prosseguir com o resto da lógica. O teste fica liberado de saber como o dados foram obtidos e empacotados em um objeto e essas interfaces tornam-se reutilizáveis por outros testes.
Definindo o que Significa Concluído
Numa empresa, especificar o critério para dizer que algo está realmente concluído pode ser muito complicado. Código que executa perfeitamente bem no ambiente de desenvolvimento pode não funcionar no ambiente de teste. Isto pode ser devido a políticas de segurança, problemas de rede e conectividade, falta de estabilidade do sistema, ou a alta exigência de qualidade por parte da equipe de QA ou do usuário final. A fim de minimizar as surpresas na hora de migrar código, seu critério deve incluir:
- Criação de um conjunto compreensivo de suites de teste que sejam repetíveis e minimizem (ou eliminem) dados "hard-coded"
- Adição das suites de teste ao processo de integração contínua. Por exemplo, na minha equipe isso significa colocar as suites de teste do JUNIT numa pasta específica onde um script Ant vai pegá-las e executá-las. Esse script por sua vez, será invocado pelo servidor de integração contínua CruiseControl.
- Casos de teste automatizados não apenas para funcionalidades, mas também para performance (ex.: usando ferramentas como JunitPerf para automatizar cenários de teste usando parâmetros de performance).
- Obtenha muitos dados de teste. No caso de testes de integração com sistemas legados, tenha as equipes desses sistemas trabalhando com você na criação e na definição de critérios de teste. Seus desenvolvedores podem não conhecer as nuances de trabalhar com os sistemas legados. Uns poucos cenários favoráveis testados no ambiente de desenvolvimento dificilmente refletirão os dados em ambiente de produção.
- Faça revisões de código não apenas com a equipe interna, mas também com as equipe de infra-estrutura (ex.: DBA’s, Administradores de Sistema).
Conclusão
Esse artigo tocou em alguns dos desafios para adoção de métodos ágeis dentro de uma empresa e forneceu estratégias para enfrentá-los. Configurar ambientes de desenvolvimento de maneira consistente usando scripts automatizados e checklists, facilitar o teste automatizado e a integração contínua usando ferramentas padronizadas e dados de teste transparentes e garantir critérios restritos para definir quando há algo está realmente concluído. Essas técnicas não englobam tudo mas ajudarão as equipes a serem mais produtivas no contexto da empresa.
Sobre o Autor
Eu sou Vijay Narayaan, um líder de equipes de desenvolvimento de software atualmente construindo serviços de dados reutilizáveis e componentes de automação de processos para uma firma de serviços financeiros. Tenho trabalhado em vários projetos desde sistemas para um único usuário até grandes plataformas de software distribuído, multiusuário com diversos serviços. Tenho um blog sobre software reutilizável em http://softwarereuse.wordpress.com/