Explore o novo chatbot do Developer Center! O MongoDB AI chatbot pode ser acessado na parte superior da sua navegação para responder a todas as suas perguntas sobre o MongoDB .

Junte-se a nós no Amazon Web Services re:Invent 2024! Saiba como usar o MongoDB para casos de uso de AI .
Desenvolvedor do MongoDB
Central de desenvolvedor do MongoDBchevron-right
Produtoschevron-right
Atlaschevron-right

Como escrever testes de unidade para MongoDB Atlas Functions

Lauren Schaefer10 min read • Published Jan 28, 2022 • Updated Sep 09, 2024
Sem servidorAtlasJavaScript
Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Alguns recursos mencionados abaixo serão descontinuados em 30, 2025 de setembro. Saiba mais.
Criei recentemente um aplicativo web para minha equipe usando oAtlas Functions. Eu queria ser capaz de iterar com rapidez e implantar minhas alterações com frequência. Para isso, precisei implementar uma infraestrutura de DevOps que incluísse uma base sólida de automação de testes. Infelizmente, eu não sabe como fazer nada disso para aplicativos construídos usando o Atlas Functions.
Nesta série, guiarei você pelo que descobrir. Mostrarei como você pode criar um conjunto de testes automatizados e um pipeline deCI/CD para aplicativos da web criados em funções sem servidor.
Hoje, explicarei como você pode escrever testes de unidade automatizados para Atlas Functions. Abaixo está um resumo do que abordaremos:
Prefere aprender por vídeo? Muitos dos conceitos que abordo nesta série estão disponíveis neste vídeo.

Sobre o aplicativo de estatísticas sociais

Antes de falar sobre como testei meu aplicativo, quero dar a você um pouco de informações sobre o que o aplicativo faz e como ele é construído.
Meu time e eu precisvamos de uma maneira de acompanhar nossas estatísticas do Twitter juntos.
O Twitter oferece uma maneira de seus usuários baixarem estatísticas do Twitter. O download é um arquivo de valores separados por vírgula (CSV) que contém uma linha de estatísticas para cada Tuíte. Se você quiser experimentar, navegue até https://analytics.facebook.com/user/insert_ your_username_here/tweets e opte por exportar seus dados por Tuíte.
Depois que meus colegas de equipe e eu baixamos nossas estatísticas de Tweets, precisávamos de uma maneira de combinar regularmente nossas estatísticas sem duplicar dados de arquivos CSV anteriores. Então, decidiu construir um aplicativo da web.
O aplicativo é muito leve e, para ser completamente verdadeiro, muito feia. Atualmente, o aplicativo consiste em duas páginas.
A primeira página permite que qualquer pessoa da nossa equipe carregue seu arquivo CSV de estatísticas do Twitter.
A segunda página é um dashboard onde podemos fatiar e cortar nossos dados. Qualquer pessoa da nossa equipe pode acessar o painel para extrair estatísticas individuais ou obter estatísticas combinadas. O dashboard é útil tanto para meus membros de equipe quanto para nossa cadeia de gerenciamento.

Arquitetura do aplicativo

Vamos dar uma olhada em como arquitetei esse aplicativo, para que possamos entender como o testei.

Arquitetura sem servidor e Atlas

O aplicativo é construído usando uma arquitetura sem servidor. O termo "sem servidor" pode ser um pouco enganoso. Sem servidor não significa que o aplicativo não usa servidores. Sem servidor significa que os desenvolvedores não precisam gerenciar os próprios servidores. (Essa é uma grande vitória no meu livro!)
Quando você usa uma arquitetura sem servidor, você escreve o código para uma função. O provedor de nuvem lida com a execução da função em seus próprios servidores sempre que a função precisa ser executada.
As arquiteturas serverless têm grandes vantagens em relação às aplicações tradicionais e monolíticas:
  • Concentre-se no que importa. Os desenvolvedores não precisam se preocupar com servidores, contêineres ou infraestrutura. Em vez disso, podemos nos concentrar no código do aplicativo, o que pode levar a um tempo de desenvolvimento reduzido e/ou mais inovação.
  • Pague apenas pelo que você usar. Em arquiteturas sem servidor, você normalmente paga pelo poder de computação que usa e pelos dados que está transferindo. Normalmente, você não paga pelos servidores quando eles estão ociosos. Isso pode resultar em grandes economias de custos.
  • Escale facilmente. O fornecedor de nuvem lida com o dimensionamento de suas funções. Se o seu aplicativo se tornar um sucesso, as equipes de desenvolvimento e operações não precisam se preocupar.
Como nunca fui fã de gerenciar infraestrutura, decidi criar o aplicativo Social Stats usando uma arquitetura sem servidor.
O MongoDB Atlas oferece vários serviços de cloud sem servidor - incluindo MongoDB Atlas Data API, Atlas GraphQL API e MongoDB Atlas Triggers - que facilitam a criação de aplicativos sem servidor.

Arquitetura de estatísticas sociais

Vamos dar uma olhada em como o aplicativo Social Stats é arquitetado. Abaixo está um diagrama de fluxo de como as partes do aplicativo funcionam juntas.
Quando um usuário deseja carregar seu arquivo CSV de estatísticas do Twitter, ele navega para index.html em seu navegador. index.html pode ser hospedado em qualquer lugar. Ops por hospedar index.html usando oStatic Hosting. Curto a simplicidade de manter meus arquivos hospedados e funções sem servidor em um projeto hospedado em uma plataforma.
Quando um usuário opta por carregar seu arquivo CSV de estatísticas do Twitter,index.html codifica o arquivo CSV e o passa para a Atlas FunctionprocessCSVdo Atlas.
A processCSV função decodifica o CSV e passa os resultados para a storeCsvInDb Atlas Function .
A funçãostoreCsvInDb chama a Atlas Function removeBreakingCharacters que remove qualquer emophyllum ou outros caracteres de quebra dos dados. Em seguida, a funçãostoreCsvInDb converte os dados limpos em documentosJSON (JavaScript Object Notation) e armazena esses documentos em um MongoDB database hospedado pelo Atlas.
Os resultados do armazenamento dos dados no banco de dados são transmitidos pela cadeia de funções.
O dashboard que exibe os gráficos com as estatísticas do Twitter é hospedado pelo MongoDB Charts. O melhor desse dashboard é que não precisei fazer nenhuma programação para criá-lo. Concedi acesso ao Charts ao meu banco de dados e, em seguida, usei a interface do usuário do Charts para criar gráficos com filtros personalizáveis.
(Nota: vincular a um painel completo do Atlas Charts funcionou bem para o meu aplicativo, mas sei que nem sempre é o ideal. O Atlas Charts também permite que você incorpore gráficos individuais em seu aplicativo por meio de um iframe ou SDK.)

Testes de unidade para funções do Atlas

Agora que expliquei o que tinha que testar, vamos explorar como testei. Hoje, falaremos sobre os testes que formam a base da Pirâmide de testes:testes de unidade.
Os testes de unidade são projetados para testar as pequenas unidades do seu aplicativo. Nesse caso, as unidades que queremos testar são funções sem servidor. Os testes de unidade devem ter uma entrada e saída claras. Eles não devem testar como as unidades interagem umas com as outras.
Os testes de unidade são valiosos porque:
  1. Normalmente são mais rápidos de escrever do que outros testes automatizados.
  2. Podem ser executados de forma rápida e independente, pois não dependem de outras integrações e sistemas.
  3. Revele bugs no início do ciclo de vida de desenvolvimento de software, quando é mais barato corrigi-los.
  4. Dê aos desenvolvedores a confiança de que não estamos introduzindo regressões à medida que atualizamos e refatoramos outras partes do código.
Existem muitas frameworks de teste JavaScript.Ops por usar oJest para criar meus testes de unidade, pois é uma escolha popular na comunidade JavaScript. Os exemplos abaixo usam o Jest, mas você pode aplicar os princípios descritos nos exemplos abaixo a qualquer framework de testes.

Modificando funções do Atlas para serem testáveis

Cada Atlas Function atribui uma função para a variável global exports. Abaixo está o código de uma função boilerplate que retorna "Hello, world!"
1exports = function() {
2 return "Hello, world!";
3};
Esse formato de função é problemático para testes de unidade: chamar essa função de outro arquivo JavaScript é impossível.
Para contornar esse problema, podemos adicionar as três linhas a seguir à parte inferior dos arquivos de origem da função:
1if (typeof module === 'object') {
2 module.exports = exports;
3}
Vamos detalhar o que está acontecer aqui. Se o tipo do módulo forobject, a função está sendo executada fora de um ambiente Atlas, portanto, precisamos atribuir nossa função (armazenada em exports) a module.exports. Se o tipo do módulo não for um object, podemos presumir com segurança que a função está sendo executada em um ambiente Atlas, portanto não precisamos fazer nada de especial.
Depois de adicionarmos essas três linhas às nossas funções sem servidor, estamos prontos para começar a escrever testes de unidade.

Funções autônomas de teste de unidade

As funções de teste de unidade são mais fáceis quando as funções são independentes, o que significa que as funções não chamam nenhuma outra função nem utilizam nenhum serviço, como um banco de dados. Então, vamos começar por aqui.
Vamos começar testando a funçãoremoveBreakingCharacters. Esta função remove emojis e outros caracteres de quebra das estatísticas do Twitter. Abaixo está o código-fonte da funçãoremoveBreakingCharacters.
1exports = function (csvTweets) {
2 csvTweets = csvTweets.replace(/[^a-zA-Z0-9\, "\/\\\n\`~!@#$%^&*()\-_—+=[\]{}|:;\'"<>,.?/']/g, '');
3 return csvTweets;
4};
5
6if (typeof module === 'object') {
7 module.exports = exports;
8}
Para testar esta função, criei um novo arquivo de teste denominado removeBreakingCharacters.test.js. Comece importando afunçãoremoveBreakingCharacters.
1const removeBreakingCharacters = require('../../../functions/removeBreakingCharacters/source.js');
Em seguida, importe várias constantes de constantes.js. Cada constante representa uma linha de dados em um arquivo CSV de estatísticas do Twitter.
1const { header, validTweetCsv, emojiTweetCsv, emojiTweetCsvClean, specialCharactersTweetCsv } = require('../../constants.js');
Então eu estava pronto para começar a testar. Comece com o caso mais simples: um único Tuíte válido.
1test('SingleValidTweet', () => {
2 const csv = header + "\n" + validTweetCsv;
3 expect(removeBreakingCharacters(csv)).toBe(csv);
4})
O teste SingleValidTweetcria uma constante chamadacsv. csv é uma combinação de um cabeçalho válido, um novo caractere de linha e um Tweet válido. Como o Tweet é válido, removeBreakingCharacters não deve remover nenhum caractere. O teste verifica se, quando csv é passado para a funçãoremoveBreakingCharacters, a função retorna uma string igual a csv.
Os Emojis eram um grande problema que estavam quebrando meu aplicativo, então decidimos criar um teste só para eles.
1test('EmojiTweet', () => {
2 const csvBefore = header + "\n" + emojiTweetCsv;
3 const csvAfter = header + "\n" + emojiTweetCsvClean;
4 expect(removeBreakingCharacters(csvBefore)).toBe(csvAfter);
5})
O testeEmojiTweet cria duas constantes:
  • csvBefore armazena um cabeçalho válido, um novo caractere de linha e estatísticas sobre um Tweet que contém três emojis.
  • csvAfter armazena o mesmo cabeçalho válido, um novo caractere de linha e estatísticas sobre o mesmo Tweet, exceto que os três emojis foram removidos.
O teste então verifica se quando passo a constante csvBeforepara a funçãoremoveBreakingCharacters, a função retorna uma String igual a csvAfter.
Criei outros testes de unidade para a funçãoremoveBreakingCharacters. Você pode encontrar o conjunto completo de testes de unidade em removeBreakingChaacters.test.js.

Testes de unidade para funções usando mocks

Infelizmente, o teste de unidade da maioria das funções sem servidor não será tão simples quanto o exemplo acima. As funções sem servidor tendem a depender de outras funções e serviços.
O objetivo do teste de unidade é testar unidades individuais, não como as unidades interagem umas com as outras.
Quando uma função depende de outra função ou serviço, podemos simular a função ou serviço com um objeto de simulação. Objetos de simulação permitem que os desenvolvedores "simulem" o que uma função ou serviço está fazendo. As simulações nos permitem testar unidades individuais.
Vamos dar uma olhada em como testei a funçãostoreCsvInDb. Abaixo está o código-fonte da função.
1exports = async function (csvTweets) {
2 const CSV = require("comma-separated-values");
3
4 csvTweets = context.functions.execute("removeBreakingCharacters", csvTweets);
5
6 // Convert the CSV Tweets to JSON Tweets
7 jsonTweets = new CSV(csvTweets, { header: true }).parse();
8
9 // Prepare the results object that we will return
10 var results = {
11 newTweets: [],
12 updatedTweets: [],
13 tweetsNotInsertedOrUpdated: []
14 }
15
16 // Clean each Tweet and store it in the DB
17 jsonTweets.forEach(async (tweet) => {
18
19 // The Tweet ID from the CSV is being rounded, so we'll manually pull it out of the Tweet link instead
20 delete tweet["Tweet id"];
21
22 // Pull the author and Tweet id out of the Tweet permalink
23 const link = tweet["Tweet permalink"];
24 const pattern = /https?:\/\/twitter.com\/([^\/]+)\/status\/(.*)/i;
25 const regexResults = pattern.exec(link);
26 tweet.author = regexResults[1];
27 tweet._id = regexResults[2]
28
29 // Generate a date from the time string
30 tweet.date = new Date(tweet.time.substring(0, 10));
31
32 // Upsert the Tweet, so we can update stats for existing Tweets
33 const result = await context.services.get("mongodb-atlas").db("TwitterStats").collection("stats").updateOne(
34 { _id: tweet._id },
35 { $set: tweet },
36 { upsert: true });
37
38 if (result.upsertedId) {
39 results.newTweets.push(tweet._id);
40 } else if (result.modifiedCount > 0) {
41 results.updatedTweets.push(tweet._id);
42 } else {
43 results.tweetsNotInsertedOrUpdated.push(tweet._id);
44 }
45 });
46 return results;
47};
48
49if (typeof module === 'object') {
50 module.exports = exports;
51}
Em um nível elevado, a funçãostoreCsvInDbestá fazendo o seguinte:
  • Chamada da funçãoremoveBreakingCharacterspara remover caracteres de quebra.
  • Convertendo os Tuítes nos documentos CSV para JSON.
  • Percorrendo os documentos JSON para limpar e armazenar cada um no banco de dados.
  • Retornar um objeto que contém uma lista de Tweets que foram inseridos, atualizados ou que não puderam ser inseridos ou atualizados.
Para testar esta função, criei um novo arquivo chamado storeCsvInDB.test.js. O topo do arquivo é muito semelhante ao topo de removeBreakingCharacters.test.js: importei a função que queria testar e importei constantes.
1const storeCsvInDb = require('../../../functions/storeCsvInDb/source.js');
2
3const { header, validTweetCsv, validTweetJson, validTweetId, validTweet2Csv, validTweet2Id, validTweet2Json, validTweetKenId, validTweetKenCsv, validTweetKenJson } = require('../../constants.js');
Em seguida, comecei a criar simulações. A função interage com o banco de dados, portanto, eu sabia que precisava criar simulações para dar suporte a essas interações. A função também chama a funçãoremoveBreakingCharacters, então criei um mock para ela também.
Adicionei o seguinte código a storeCsvInDB.test.js.
1let updateOne;
2
3beforeEach(() => {
4 // Mock functions to support context.services.get().db().collection().updateOne()
5 updateOne = jest.fn(() => {
6 return result = {
7 upsertedId: validTweetId
8 }
9 });
10
11 const collection = jest.fn().mockReturnValue({ updateOne });
12 const db = jest.fn().mockReturnValue({ collection });
13 const get = jest.fn().mockReturnValue({ db });
14
15 collection.updateOne = updateOne;
16 db.collection = collection;
17 get.db = db;
18
19 // Mock the removeBreakingCharacters function to return whatever is passed to it
20 // Setup global.context.services
21 global.context = {
22 functions: {
23 execute: jest.fn((functionName, csvTweets) => { return csvTweets; })
24 },
25 services: {
26 get
27 }
28 }
29});
Jest executa a funçãobeforeEach antes de cada teste no arquivo fornecido. Optei por colocar a instanciação das simulações dentro de beforeEach para que eu pudesse adicionar verificações de quantas vezes uma determinada simulação é chamada em um determinado caso de teste. Colocar simulações dentro de beforeEach também pode ser útil quando queremos alterar o que a simulação retorna na primeira vez que é chamada em comparação com a segunda.
Depois de criar minhas simulações, eu estava pronto para começar a testar. Eu criei um teste para o caso mais simples: um único tweet.
1test('Single tweet', async () => {
2
3 const csvTweets = header + "\n" + validTweetCsv;
4
5 expect(await storeCsvInDb(csvTweets)).toStrictEqual({
6 newTweets: [validTweetId],
7 tweetsNotInsertedOrUpdated: [],
8 updatedTweets: []
9 });
10
11 expect(context.functions.execute).toHaveBeenCalledWith("removeBreakingCharacters", csvTweets);
12 expect(context.services.get.db.collection.updateOne).toHaveBeenCalledWith(
13 { _id: validTweetId },
14 {
15 $set: validTweetJson
16 },
17 { upsert: true });
18})
Vamos ver o que esse teste está fazendo.
Assim como vimos nos testes anteriores deste post, comecei criando uma constante para representar os Tweets CSV. csvTweets consiste em um cabeçalho válido, um caractere de nova linha e um Tweet válido.
O teste então chama a funçãostoreCsvInDb, passando a constantecsvTweets . O teste afirma que a função retorna um objeto que mostra que o Tuíte que passamos foi armazenado com sucesso no banco de dados.
Em seguida, o teste verifica se a simulação da função removeBreakingCharacters foi chamada com nossa constantecsvTweets .
Por fim, o teste verifica se a funçãoupdateOnedo banco de dados foi chamada com os argumentos que esperávamos.
Depois de concluir esse teste unitário, escrevi um teste adicional que verifica se a funçãostoreCsvInDblida corretamente com vários Tweets.
Você pode encontrar o conjunto completo de testes de unidade em storeCsvInDB.test.js.

Encerrando

Os testes unitários podem ser extremamente valiosos. Eles são uma das melhores maneiras de encontrar bugs no início do ciclo de vida de desenvolvimento de software. Eles também estabelecem uma base sólida para CI/CD.
Tenha em mente as duas dicas a seguir ao escrever testes de unidade para o Atlas Functions:
  • Modifique as exportações do módulo no arquivo de origem de cada função, para que você possa chamar as funções dos seus arquivos de teste.
  • Use simulações para simular interações com outras funções, bancos de dados e outros serviços.
O código-fonte do aplicativo Social Stats e os arquivos de teste associados estão disponíveis em um repositório do GitHub: https://github.com/mongodb-developer/SocialStats. O readme do repositório contém instruções detalhadas sobre como executar os arquivos de teste.
Fique atento à próxima publicação desta série, em que mostrarei como escrever testes de integração para aplicativos sem servidor.
Confira os seguintes recursos para obter mais informações:

Ícone do FacebookÍcone do Twitterícone do linkedin
Avalie esse Tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Relacionado
Tutorial

Mostrando visualmente os destaques do Atlas Search com JavaScript e HTML


Feb 03, 2023 | 7 min read
Tutorial

Adding Autocomplete to Your Laravel Applications With MongoDB Atlas Search


Nov 26, 2024 | 9 min read
Tutorial

Criar um backend de gerenciamento de mídia escalável: integrando Node.js, Armazenamento de blobs Azure e MongoDB


Nov 05, 2024 | 10 min read
Tutorial

Construindo um Painel de Vendas Dinâmico e em Tempo Real no MongoDB


Aug 05, 2024 | 7 min read
Sumário