Testes como ferramenta de auxílio na geração de valor

Samuel Giarola
TechBlogHotmart
Published in
9 min readOct 22, 2018

Qualidade de software, stakeholders e o risco do impacto sistêmico na geração de valor

A palavra qualidade aplicada no desenvolvimento de software traz um contexto subjetivo e interpretativo que serve de guia e esperança para alguns, mas também assombra muitos outros.

Diante de tantas definições encontradas na literatura, uma de poucas palavras que para mim se destaca é a de Gerald Weinberg no livro “Quality Software Management: Systems Thinking” que diz:

“Qualidade é valor para alguma pessoa.”

Sim, eu sei. Continuou subjetivo. Porém, essa frase nos faz pensar em algumas questões, como por exemplo: o que é entrega de valor? Como aplicar isso no nosso dia-a-dia? Quem são essas pessoas com quem devo me preocupar?

Em uma breve interpretação, tais pessoas podem ser todas aquelas que possuem algum interesse na aplicação — desde usuários finais, à executivos, pessoas do financeiro e até mesmo os próprios desenvolvedores. Estes interessados são formalmente conhecidos como stakeholders.

Cada singular interesse, quando devidamente planejado, priorizado e executado, inerentemente se traduz em valor para toda cadeia de interessados. Entretanto, quando algo dá errado, o impacto negativo também é sistêmico, afetando toda esta mesma cadeia.

Robert C. Martin (popularmente conhecido como Uncle Bob) evidencia tal impacto sistêmico em um estudo de caso real divulgado em seu livro “Clean Architecture: A Craftsman’s Guide to Software Structure and Design”, onde a qualidade negligenciada perante o processo de entrega de valor acabou afetando o sucesso da aplicação. Tal estudo é elucidado nos parágrafos abaixo.

No decorrer de releases entregues em um grande projeto foram registradas as perspectivas de diferentes áreas de uma empresa. As evidências coletadas refletem o crescente impacto que uma má gestão de interesses bordada com a cultura do “entregar para ontem” e pincelada com a mentalidade que “qualidade pode ficar para depois” gera:

  • Setor de contratações: o número de contratações de engenheiros de software ao longo das releases aumentou consideravelmente. Isso em uma visão isolada e superficial pode caracterizar um cenário positivo para a empresa. O gráfico abaixo esboça a situação:
  • Marketing: o projeto parou de entregar novas funcionalidades na mesma proporção em que os engenheiros de software entravam no time. Já nas últimas releases o crescimento da aplicação estagnou independente do número de novos colaboradores, evidenciando que o cenário do setor de contratações não era tão positivo. Veja na imagem abaixo:
  • Gerência do projeto: a dimensão do problema começa a ficar mais clara quando a gerência do projeto constatou que o custo por linha de código aumentou cerca de 40 vezes, como esboça imagem abaixo:
  • Equipe de desenvolvimento: os desenvolvedores foram ficando cada vez mais frustrados com o projeto, pois seus esforços aumentavam e em contrapartida entregavam cada vez menos. Isso acontecia porque existia uma baixa manutenibilidade, pouca segurança nas modificações e muito tempo com correções de bugs. Na última release nenhuma nova funcionalidade pôde ser implementada, a equipe toda precisou dar manutenção no sistema. A rotatividade dos desenvolvedores na empresa naturalmente começou a aumentar.
  • Executivo: Já imaginou investir aproximadamente 20 milhões de dólares em uma release (no caso a última) e não ter nenhuma nova funcionalidade como retorno?! Pois é, as mesmas pessoas que na primeira release investiram a quarta parte desse valor e tiveram como retorno muitas funcionalidades, agora enxergam o investimento como desastre e estão tentando entender onde foi que erraram. Sinto cheiro de demissões e horas extras, concorda?

Como podemos então evitar/amenizar efetivamente esse cenário em nossos projetos? Muitos devem estar se questionando: “Isso depende de quem está financiando o projeto”, e não estão errados, os stakeholders que financiam e possuem um grande poder nas tomadas de decisão possuem sim sua fração de responsabilidade, porém eles normalmente não possuem o conhecimento técnico que você enquanto desenvolvedor e também interessado no sucesso da aplicação (afinal de contas você foi contratado para isso) possui para convencê-los de qual o melhor caminho seguir. Sendo assim, a palavra qualidade não pode faltar no seu vocabulário.

E dentre muitas ramificações da qualidade no processo de desenvolvimento de software, elegi uma ferramenta chave acessível a todos para começarmos desde já a assegurar a qualidade nos nossos projetos: os testes. Desde documentos formais — como a ISO/IEC 25010:2011 — até práticas ágeis bem difundidas no mercado tratam os testes como peça fundamental para auxílio ao sucesso do projeto.

Testes como ferramenta chave na garantia da qualidade de seu projeto

O conceito de teste de software foi introduzido por Glenford J. Myers no livro “The Art of Software Testing” em 1979, onde começou-se a diferenciar do conceito de debugging e cunhou-se a seguinte frase:

“Um caso de teste bem-sucedido é aquele que detecta um erro ainda não descoberto

Após isso, diferentes técnicas de testar uma aplicação surgiram, todas com o objetivo de maximizar as chances de detectar o erro ainda não descoberto.

Como o feedback constante destes testes eram importantes para garantir a qualidade e a saúde da aplicação, seu uso automatizado se difundiu e tornou-se uma boa prática.

Já em 2009, Mike Cohn no livro “Succeeding with Agile: Software Development Using Scrum” agrupou os principais tipos de testes automatizados em uma forma de pirâmide, dando origem à hoje conhecida pirâmide de testes.

A pirâmide ganhou muitas formas ao longo do tempo — agrupando, segregando e removendo diferentes tipos de testes — entretanto desde seu surgimento sempre foi uma das principais referências para construção de uma stack de testes confiáveis. Nos guiaremos então por um formato de pirâmide comumente encontrado nas referências:

Explicando e classificando as camadas da pirâmide

Teste unitário

É o melhor amigo do desenvolvedor, além de ser o mais simples se comparado aos outros. Entretanto, o que mais traz benefícios quando corretamente aplicado, pode ser considerado o “canivete suíço” dos testes.

É aplicado em pequenos trechos de código, pequenas unidades, com o objetivo de ser um teste pontual, porém por ser simples e de rápido feedback, quanto maior sua quantidade cobrindo os caminhos da complexidade ciclomática da aplicação, mais confiança seu conjunto traz para a esta.

Sua aplicabilidade vai além de testar unidades do sistema, em refatorações de código legado serve como “andaime” na garantia da não inserção de novos bugs e de que o comportamento do código alterado se mantém o mesmo.

Outra forma de utilização que Kent Beck cunhou no livro “Test Driven Development: By Example” é justamente a técnica de desenvolvimento de testes antes do desenvolvimento das classes de produção, o TDD.

Nesta técnica o teste unitário é usado para garantir o início da cobertura do código e também direcionar o desenho da aplicação em um baixo acoplamento e alta coesão.

Mas vale ressaltar que a técnica apenas inicia de forma “rasa” a cobertura daquele trecho a ser implementado, sendo necessário depois aprofundar em outros caminhos da complexidade ciclomática do trecho.

Teste de componente

É o tipo de teste menos entendido entre os desenvolvedores, muitas vezes confundido tanto com testes unitários, quanto com testes de integração.

São testes modulares de domínio que englobam as unidades de código validando o fluxo como um todo. Por isso, são mais custosos que os testes unitários.

Em contrapartida trazem maior confiança. Por exemplo, os periféricos como banco de dados e APIs são mockados, o que traz menos esforço e mais rapidez nos ciclos de feedback do que os testes integrados.

Uma boa forma de representar o domínio a ser testado é usando ferramentas que permitem manter uma documentação viva de sua aplicação.

São elas que normalmente permitem escritas discursivas da natureza do teste, as quais são inseridas em um motor de inferência para gerar código da linguagem desejada.

Dessa forma permite-se que os POs/Analistas de Negócio mantenham a documentação e consequentemente o reflexo dos testes quanto ao negócio. Uma ferramenta muito utilizada para isso é o Cucumber.

Teste integrado

São testes que englobam mais do que o domínio modular, já que acessam também periféricos contidos no domínio da aplicação, validando assim o fluxo integrado.

Isso ocorre porque as integrações com periféricos nem sempre são triviais de se testar. Elas levam em conta a configuração, a disponibilidade e o tempo de resposta.

Por exemplo em um banco de dados estes testes são mais custosos e com ciclos de feedback mais lentos do que os anteriores, porém proporcionam maior confiança em sua execução.

Um ponto importante que vale ressaltar é que os periféricos envolvidos no teste são os que estão no domínio da aplicação e não fora dele. Por exemplo, não podemos garantir que uma API que consumimos de terceiros vai estar disponível quando executarmos nossos testes, o que pode levá-los a falhar por motivos inesperados e muitas vezes desconhecidos. Por esse motivo a comunicação com periféricos fora do domínio são normalmente mockadas.

Teste de UI

São testes de interface da aplicação, envolvendo o design, distribuição de componentes e navegação.

Para tal a regra de negócio não se torna algo importante de ser executada, portanto ela é geralmente mockada para que os testes se tornem mais rápido de serem executados.

Como são testes que precisam simular a interface da aplicação e navegar nela, por mais que a regra de negócio esteja mockada, ainda são custosos de serem criados e executados.

Por isso, só ganham dos testes end-to-end em termos de custo, em contrapartida a simulação da jornada do usuário proporciona uma confiabilidade considerável.

Teste end-to-end

São testes que envolvem o fluxo ponta-a-ponta, desde uma ação disparada na interface da aplicação até a comunicação com periféricos fora do domínio da aplicação.

Demandam muito custo de serem construídos e executados e, além disso, como acessam terceiros não conseguimos garantir a estabilidade de execução.

Portanto, são executados apenas em um job diário fora de qualquer pipeline de build/deploy. Por outro lado, são obviamente os que mais trazem confiança em seu feedback.

Uma técnica de teste que vem sendo executada para quebrar um pouco essa complexidade do teste end-to-end são os testes citados por Martin Fowler como subcutâneos.

São basicamente a execução de todo fluxo, só que disparados por “debaixo” da interface, não exigindo que esta seja configurada e simulada.

Evitando assim também que seus testes complexos sejam criados, o que elimina muito do custo que traz a criação de um teste end-to-end.

Conclusão: trade-off de confiança

Muitos até hoje tentam entender os argumentos utilizados pelo criador do TDD, Kent Beck, quando uma dúvida postada no StackOverflow, questionando a qual profundidade deve-se atingir com testes unitários usando TDD, deu resposta que chocou muitos de seus seguidores na época.

Principalmente pelo início enfático: “Sou pago por código que funciona, não por testes”.

Na ocasião ele usou como argumento o trade-off de confiança. Segundo ele não é necessário atingir 100% de cobertura para garantir que a suíte dos testes unitários se tornem confiáveis o suficiente no desenvolvimento com TDD.

Isso traz um certo conforto de não precisar escrever testes desnecessários, porém aumenta e muito a responsabilidade de que os testes a serem escritos devem ser muito bem criados para transmitir a confiança necessária.

Essa mesma teoria do trade-off de confiança é utilizada também quando tratamos de pirâmide de testes.

Todos sabem que o esforço de se criar e manter cada tipo de teste em suas devidas proporções é muito grande. Apesar disso, nem sempre estamos em uma situação favorável que disponha de tempo e mão-de-obra suficientes para isso.

Portanto, ao invés de crucificar quem tem o poder de despriorizar qualidade para “ganhar” tempo em entrega. Prefiro questionar juntamente ao time o que assumir de granularidade dos testes, os tipos e suas coberturas, para que possamos alcançar o nível de confiança suficiente que nos permita deitar nossas cabeças tranquilamente no travesseiro a noite.

Essa fórmula não é exata e dependente diretamente da experiência do time e do tipo de projeto, porém nunca devemos esquecer que a “brecha” de cobertura causada pelo trade-off deve ser compensada pela qualidade e confiança nos testes escritos. A responsabilidade apenas aumenta.

A pirâmide de testes é extremamente importante como mecanismo conceitual e de direcionamento, porém trazendo para realidade, considero encará-la como apoio conceitual e não como um objetivo final de projeto.

Dessa forma mais aderente à realidade, conseguimos aplicar testes como ferramenta que contribui para a geração de valor sistêmica do projeto, minimizando impactos negativos e consequentemente as chances de decepcionar os stakeholders.

--

--

Samuel Giarola
TechBlogHotmart

Passionate to code, and code, and code a little more. Let’s talk about Software Development?