Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Node.js: implementando testes em uma API

Node.js: implementando testes em uma API

Por que precisamos de testes? - Apresentação

Apresentando o curso e o instrutor

Seja bem-vinde ao curso Node.js: Implementando testes em uma API. Meu nome é Thiago Martins, sou instrutor aqui na Alura.

Audiodescrição: Thiago é um homem branco, de cabelo castanho, que usa óculos. Veste uma camiseta cor vinho e, atrás dele, há duas poltronas e uma estante de livros.

Apresentando objetivos e perguntas iniciais

Este curso é para quem deseja aprofundar-se em testes automatizados, entender por que precisamos deles, quais são seus benefícios, as diferenças, as diversas estratégias de teste e até as diferentes escolas de testes que interpretam como devemos construí-los.

Neste curso, o que vamos aprender? Basicamente, nós vamos começar respondendo a esta pergunta: por que precisamos testar.

Explicando a importância dos testes e a especificação formal

Consideramos que é muito importante, antes de passar à prática, que você entenda, sobretudo se estiver começando a abordar este tema, por que os testes são importantes e qual problema eles resolvem. Veremos que isso está relacionado a sistemas de software complexos e ciclos de feedback (retorno). Em seguida, faremos a conexão de por que os testes automatizados são importantes para esses ciclos de feedback (retorno).

Depois, falaremos sobre especificação formal, que é basicamente de onde podemos derivar e escrever testes para, em seguida, implementá-los.

Estruturando os tipos de testes e dublês de teste

A segunda parte do curso será mais teórica, e então começaremos a escrever, de fato, testes end-to-end (fim a fim) de uma API, depois testes unitários e, por fim, testes de integração. Cobriremos todos os principais tipos de testes da pirâmide de testes.

Ao final, falaremos sobre os quatro pilares dos testes automatizados, que são características que podemos usar para avaliar o valor de um teste. Além disso, trataremos um tema muito importante, que é sobre mocks (objetos simulados).

Começaremos abordando o termo guarda-chuva test doubles (dublês de teste). Em seguida, veremos o que são mocks (objetos simulados) e até que ponto os implementamos em nosso sistema. Também exploraremos a diferença entre mocks (objetos simulados) e stubs (substitutos simplificados), dois dos principais tipos de dublês, e como são úteis nos testes de uma API.

Contextualizando o projeto e pré-requisitos

Para o contexto deste curso, vamos utilizar o projeto de API de uma livraria, que também está presente em outros cursos da trilha de Node.js. Se você já o viu antes, estará familiar com os casos de uso e com as rotas que temos aqui.

Quais são os pré-requisitos para aproveitar bem este curso, com tranquilidade? São poucos, mas relevantes. Começando por conhecimento prévio de Node.js. Também é importante conhecer a biblioteca Express, que utilizamos para criar o servidor HTTP aqui. Além disso, é importante ter familiaridade com Docker e Docker Compose, porque usaremos essas duas ferramentas para subir a base de dados. Por fim, noções de PostgreSQL são essenciais, pois é o banco de dados que utilizaremos.

Recomendando leitura e encerrando a introdução

Como pré-requisito e apoio teórico fundamental, recomendamos fortemente o livro Unit Testing Principles, Practices and Patterns (Princípios, Práticas e Padrões de Testes de Unidade), de Vladimir Khorikov. Ele serve como principal fundamento teórico para este curso, especialmente a parte dos quatro pilares dos testes automatizados. Vale muito a pena.

Espero que você esteja animade para começar este curso comigo. É um tema de que eu gosto muito — gosto de falar, escrever artigos e ensinar. Estou com muita vontade de compartilhar este conhecimento com você. Nos vemos no próximo vídeo.

Por que precisamos de testes? - Sistemas complexos e leis de Lehman

Apresentando a pergunta central do curso

Nesta primeira aula do nosso curso de testes de APIs em Node.js, vamos desenvolver a teoria por trás das provas antes de passar à prática. Embora pudesse ser mais fácil introduzir o tema mostrando como testamos uma API que já desenvolvemos com Jest ou outras ferramentas de teste, queremos construir um repertório para responder com clareza à pergunta que está na tela: por que precisamos testar?

Pode parecer simples responder que precisamos testar para garantir que o sistema funcione. Porém, na Alura, queremos que haja uma base mais sólida e teórica, para explicar por que o que fazemos na nossa profissão é importante e por que fazemos dessa forma. Para isso, pedimos atenção, pois vamos construir esse raciocínio gradualmente. Em cada vídeo, neste e nos próximos, vamos construir uma escada de conhecimento para responder a essa pergunta, tornando muito mais claro por que dedicamos tempo e esforço à escrita de testes para nossos sistemas.

Definindo objetivos do software e caracterizando sistemas complexos

Para começar, vamos abordar quais são os objetivos do software em geral. É algo básico, talvez até óbvio, mas destacamos quatro pontos para iniciar a linha de raciocínio:

A partir desses exemplos, vamos separar visualmente dois grupos para entender melhor uma distinção importante. Em um lado, temos um software bancário e uma rede social. O software bancário pode se encaixar tanto em resolução de problemas quanto em automação de processos, enquanto a rede social se aproxima do entretenimento. No outro lado, temos um software de simulação física e um software de logística, que resolve problemas como: se precisamos visitar cinco cidades, qual é a rota mais rápida para visitar todas, possivelmente gastando menos combustível.

Existe uma distinção entre esses dois grupos. Os softwares do primeiro grupo estão mais relacionados à mecanização de atividades humanas. O fator humano está muito presente, pois automatizamos e sistematizamos interações humanas: no sistema bancário, com pessoas no caixa emitindo cheques ou realizando saques; e, ainda mais, na rede social, com interações totalmente entre pessoas.

No segundo grupo, os sistemas não têm, necessariamente, interação humana direta. O valor de um software de simulação física, por exemplo, está associado a simular corretamente fenômenos como aceleração e gravidade. Em logística, a abordagem tende a ser mais heurística: tenta-se resolver um problema de forma eficiente, ainda que não exata, modelando o mundo real com menor influência do fator humano e maior ênfase no componente matemático.

Vamos focar nos sistemas com os quais trabalhamos com mais frequência, tipicamente os do primeiro grupo. Quando você está desenvolvendo em Node.js e evoluindo na carreira, costumamos atuar mais com sistemas desse lado, como os bancários e as redes sociais, que possuem um componente humano mais forte.

Esses sistemas têm uma característica especial: são constantemente influenciados pelo ambiente em que operam. No caso de uma rede social, por exemplo, seu valor está mais ligado à percepção das pessoas usuárias sobre ela do que a um valor intrínseco, como “modelar bem como duas pessoas se tornam amigas na realidade”. Uma rede social é influenciada por como as pessoas a utilizam, pelo contexto cultural e geográfico onde opera (por exemplo, uma rede social no Brasil pode ter comportamentos distintos de uma na Ásia), por nichos de interesse diferentes, por regulações que mudam ao longo do tempo e pelo próprio passar do tempo, com gerações que passam a usar (ou deixam de usar) determinada plataforma, enquanto outra se torna mais relevante.

O ponto central é que esses são sistemas que operam em ambientes fortemente influenciados pelo contexto em que estão inseridos. Chamamos esses sistemas de sistemas complexos porque estão em constante evolução, sofrem influência e também influenciam os atores que interagem com eles.

Ao longo do tempo (no eixo x, o transcurso do tempo), para que sistemas complexos continuem relevantes, precisam mudar conforme certos eventos ocorrem. Por exemplo, surge uma nova legislação no país onde o sistema opera: o sistema precisa ser modificado para continuar em uso. Mais adiante, pode surgir uma nova geração de pessoas usuárias; para atendê-la, novas funcionalidades se fazem necessárias. Assim, esses sistemas estão sempre mudando; precisam mudar para permanecer em operação.

Um professor, o doutor Lima, do Imperial College de Londres, postulou um conjunto de leis que descrevem precisamente o comportamento de sistemas complexos. Há várias leis em seu trabalho, mas vamos nos concentrar em três que são muito relevantes para o nosso contexto:

Esses fundamentos orientarão por que investimos tempo em testes e como estruturamos nossa prática ao testar APIs em Node.js com ferramentas como Jest. Ao longo das próximas aulas, vamos aprofundar cada um desses pontos e conectar a teoria com a prática, passo a passo.

Leis de Lehman e evolução de sistemas complexos

Os nomes talvez pareçam autoexplicativos; vamos revisá-los rapidamente para enfatizar os seguintes pontos:

A partir disso, começamos a entender por que estamos falando de testes e de sua importância.

Como lidar com sistemas complexos em evolução

Dado o contexto do que são sistemas complexos e das leis de Lehman que operam sobre eles, o que fazemos para começar a lidar com esse cenário e garantir que construímos sistemas resilientes, capazes de suportar mudanças e complexidade crescente?

Precisamos nos concentrar em dois pontos:

  1. Como resolvemos os problemas corretos do sistema.
  2. Como resolvemos o problema corretamente.

Essa diferenciação pode parecer sutil, então vamos à prática.

Exemplo: sistema de livraria

Vamos utilizar o mesmo exemplo que já vimos em outros cursos: um sistema de livraria. Se alguém não viu os outros cursos, vamos recapitular rapidamente: nosso sistema de livraria é voltado à venda de livros. Registramos livros, que têm um autor e uma editora associados. Temos clientes cadastrados e, quando realizam compras, tratamos essa operação na parte de vendas do sistema.

Dado esse contexto, avaliemos primeiro o caso de “resolver o problema correto”. Considere três funcionalidades durante a fase de concepção do sistema, quando estamos definindo o que implementar:

Quais dessas funcionalidades fazem sentido no sistema? Comprar e buscar um livro fazem sentido; apostar um livro não faz sentido, e incluir uma seção de apostas seria inadequado.

Esse exemplo é propositalmente óbvio. Porém, em sistemas reais, isso ocorre com frequência: equipes de pessoas desenvolvedoras, Product Owners (responsáveis pelo produto), pessoas de produto e demais stakeholders (partes interessadas) reúnem-se para avaliar funcionalidades e, por vezes, identificam equivocadamente um problema que acreditam que o cliente final tem.

Um exemplo mais sutil: imaginemos que pensamos que o cliente final tem o problema de possuir muitos livros em casa e queira vender seus livros usados em nosso sistema. Acabamos implementando isso e, depois, descobrimos que o cliente final não quer essa funcionalidade; frequentemente são colecionadores que usam nosso sistema e desejam manter até mesmo livros antigos.

Esse é o primeiro ponto em sistemas complexos e em evolução: precisamos nos concentrar em resolver os problemas corretos, aqueles que nossos clientes finais realmente têm.

Como resumimos isso de forma prática? Para verificar se estamos resolvendo o problema correto, fazemos duas perguntas:

Isso também é essencial. Muitas vezes, um sistema começa simples, resolvendo uma dor muito específica do cliente final. Com o tempo, tenta abarcar diversos outros problemas que até podem existir, mas não são o foco da empresa. O software (programa) não nasceu com essa proposta; não faz parte da especialidade da empresa resolver aquele problema. No sistema de livraria, por exemplo, poderia ser oferecer crédito para compra de vários livros ao mesmo tempo, algo que talvez não faça parte da nossa especialidade. Portanto, além de ser o problema correto, precisa fazer sentido para a empresa.

Resolver um problema corretamente

Agora, vamos à segunda parte: resolver um problema corretamente. Isso é mais direto.

Foquemos no exemplo de buscar um livro, mais especificamente por título. Precisamos aprofundar o nível de detalhe dessa funcionalidade, isto é, definir sua especificação. O que esperamos ao buscar um livro por título?

Vamos considerar três comportamentos:

Isso é a especificação do sistema, pois define como ele deve se comportar.

Agora, imaginemos que, ao construir um protótipo, verificamos o seguinte: nos dois primeiros casos, o comportamento está conforme o esperado. No terceiro caso, no entanto, onde esperávamos que um título vazio gerasse um erro, o sistema retorna uma lista vazia. Esse não era o comportamento definido; queríamos um erro.

Portanto, especificamos que o sistema deveria funcionar de certa maneira, mas o comportamento observado foi diferente. Resolver o problema corretamente significa garantir o seguinte: o sistema se comporta como estabelecemos que deveria se comportar? Se sim, estamos resolvendo corretamente. Se não, não estamos resolvendo corretamente.

Encerramento e próximos passos

Essa é a primeira parte do nosso desenvolvimento teórico: entendemos o que é um sistema complexo, as leis de Lehman que o regem e em que precisamos nos concentrar.

No próximo vídeo, vamos aprofundar e construir o próximo degrau do nosso conhecimento: o que precisamos fazer daqui para frente? Já entendemos o problema e que precisamos focar em resolver os problemas corretos e resolvê-los corretamente. Agora, como fazemos isso? Veremos no próximo vídeo.

Por que precisamos de testes? - Ciclo de feedback

Contextualizando o tema e retomando Lehman

Neste segundo vídeo da nossa primeira aula sobre testes, vamos avançar mais um degrau no nosso conhecimento, nessa linha de raciocínio que vamos construir, para discutir por que precisamos testar.

No vídeo anterior, basicamente apresentamos as leis de Lehman, lembrando que as leis de Lehman regem o software (programa de computador) considerado complexo: a lei da mudança contínua, a da complexidade crescente e a da qualidade em declínio. Também mencionamos que, a partir dessas leis, a consequência é que temos dois principais desafios ao construir esse tipo de solução: como resolver o problema certo e como resolver esses problemas corretamente.

Contrastando o planejamento excessivo e o aprendizado contínuo

Queremos mostrar uma divergência entre como a engenharia de software via, no passado, a forma de resolver esses problemas e como, hoje, a engenharia de software moderna enxerga isso. No passado, acreditava-se que nossa área tinha muitas semelhanças com outras engenharias, como a civil ou a mecânica, nas quais a precisão importa muito. Assim, planejar, elaborar planos e definir requisitos técnicos antes de implementar, antes de construir algo, ajudava bastante a controlar o resultado final.

Entretanto, na engenharia de software, percebemos que o planejamento excessivo antes de colocar o software em produção não ajuda tanto. Por que isso ocorre? Precisamente por causa da característica já mencionada dos sistemas complexos. Por mais que possamos planejar todos os requisitos, funcionalidades e características que o sistema deve ter, uma vez que o colocamos em produção e as pessoas usuárias começam a interagir com ele, ou mudanças na legislação passam a afetá-lo, precisamos modificá-lo. Essa característica de mudança contínua do software impacta o quanto o planejamento prévio será útil.

Hoje, o que sabemos? Sabemos que o mais importante é focar no aprendizado. E o que é aprendizado? É, basicamente, reunir a informação mínima para termos o que chamamos de um Produto Mínimo Viável (PMV), colocá-lo diante das pessoas usuárias para que utilizem, observar como interagem com o sistema e aprender a partir dessa interação para identificar o que o sistema precisa, quais outras funcionalidades são necessárias ou quais funcionalidades que pensávamos ser necessárias, mas, na realidade, não são.

Mas como otimizamos esse aprendizado? Existem diferentes formas de aprender. De certo modo, a maneira anterior — o planejamento excessivo — gerava algum aprendizado, porque, ao começar a planejar um sistema, surgem ideias de aspectos que não havíamos considerado, e então os planejamos. No entanto, isso não é tão eficaz quanto colocar o sistema diante das pessoas usuárias.

Explicando a analogia do alvo e o ciclo de retroalimentação

Para falar sobre como aprendemos — tanto nós, enquanto seres humanos, quanto sobre como aprendemos a respeito dos sistemas —, trouxemos uma analogia e é importante prestar muita atenção a ela. Pode parecer simples no início, mas faz muito sentido e acreditamos que ajudará a entender como otimizamos o aprendizado ao construir software.

Pensemos em um problema do mundo real: queremos acertar um alvo, como um alvo de dardos ou de arco e flecha. Como tentamos atingir o centro desse alvo? Como começamos? Se nunca tivermos experiência nesse esporte, vamos pegar o dardo, começar a lançá-lo e, provavelmente, acertaremos nas posições mais próximas da borda. Por quê? Porque não temos prática nem experiência. Assim, vamos errar bastante, possivelmente. Talvez acertemos um ou outro arremesso no centro, mas por acaso.

Como melhoramos com o tempo? Ao lançarmos mais dardos e treinarmos, aperfeiçoamos a pontaria e acertamos cada vez mais próximo do centro graças ao aprendizado — não apenas visual, mas também motor.

Quando lançamos um dardo — considerando uma vista lateral do alvo — e ele atinge a borda, observamos onde ocorreu o impacto e, no próximo lançamento, fazemos um ajuste. Nosso cérebro absorve a informação sobre a força aplicada e o ponto de impacto e, a partir disso, ajustamos a força ou o ângulo para nos aproximarmos daquele ponto desejado. Com o tempo e a repetição do movimento, nosso cérebro entende a força e o ângulo necessários para acertarmos cada vez mais perto do centro.

Esse processo de realizar uma tentativa, observar o resultado e ajustar a tentativa seguinte descreve, de forma geral, como funciona um processo de aprendizado.

Podemos representar esse objetivo de forma esquemática: no início, com o objetivo de acertar o alvo, realizamos uma ação — lançar o dardo — e verificamos o resultado dessa ação: se acertamos o alvo, se ficamos longe do centro ou não. Se não acertamos o centro, tentamos novamente, absorvemos essa informação e ela impacta nossa próxima ação. Ajustamos a força ou o ângulo, tentamos de novo, verificamos novamente se acertamos; se não acertamos, repetimos até atingir o centro do alvo.

Esse processo, em que buscamos nos aproximar do objetivo por meio de ajustes, é o que chamamos de ciclo de retroalimentação. Executamos uma ação, absorvemos o resultado dessa ação para ajustar a próxima; a retroalimentação é a informação extraída do resultado de nossa ação, e é um ciclo porque essa informação alimenta a ação seguinte rumo ao objetivo final.

Aplicando o ciclo ao software e destacando a velocidade da retroalimentação

Isso se aplica diretamente ao software. Sempre que definimos um objetivo como “precisamos implementar a funcionalidade X” ou “temos que satisfazer o requisito Y”, o objetivo é essa funcionalidade. O que fazemos em sistemas de software? Precisamos realizar uma mudança, implementar algum código e avaliar se essa mudança satisfaz o que especificamos (conforme discutimos anteriormente sobre especificação). Se não satisfaz, ajustamos e avaliamos novamente.

Esse ciclo de retroalimentação existe em vários níveis. No nível mais baixo, quando implementamos uma funcionalidade específica, verificamos se a estamos implementando corretamente — esse foi o caso mencionado antes, sobre qual é o comportamento esperado do sistema quando buscamos um livro por título. Em um nível mais alto, implementamos, por exemplo, uma funcionalidade de empréstimo de livros, colocamos em produção e avaliamos se as pessoas usuárias estão utilizando essa funcionalidade. Se estiverem, objetivo alcançado; se não, ou apenas parcialmente, precisamos realizar mudanças no software e reavaliar.

Essa é a forma de aprendermos com mais eficácia e de desenvolvermos software útil. Um aspecto essencial desse ciclo de aprendizado é a velocidade com que coletamos a retroalimentação. Não basta coletá-la; a velocidade importa muito. Esse será exatamente o tema do próximo vídeo, no qual aprofundaremos esse processo.

Nos vemos no próximo vídeo.

Sobre o curso Node.js: implementando testes em uma API

O curso Node.js: implementando testes em uma API possui 389 minutos de vídeos, em um total de 69 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Bônus PM3 Summit 2026

Alavanque sua carreira com até 44% off + 2 meses grátis!

Conheça os Planos para Empresas