Lançamento do meu livro de Node.js

Faz tempo desde meus primeiros estudos com Node.js, quase concomitantemente às minhas primeiras postagens sobre o assunto aqui no blog.

Assim como fiz em 2012, conforme ia iniciando meus estudos de Android, fui documentando tudo o que aprendia e testava sobre Node.js e todo o ecossistema de tecnologias ao redor dele.

Obviamente não comecei meus estudos partindo do zero, uma vez que já trabalho com desenvolvimento web há cerca de 10 anos. Todo o meu conhecimento de front-end (que se resume a JQuery e Bootstrap) e de banco de dados (que se resume a SQL Server e MongoDB), aliado às boas práticas de Engenharia de Software, Gestão de Projetos, Testes de Software, etc formaram o profissional Node.js que sou hoje.

Puramente com Node eu tenho experiência de quase um ano na data que escrevo este post e uma meia dúzia de projetos entregues e funcionando.

Pois então que há alguns meses decidi escrever um novo livro. O primeiro de 2017, justamente sobre todo esse conhecimento de programação web que possuo e que já me renderam o cargo de docente de disciplinas como Programação para Internet e Serviço para Web em faculdades do RS durante alguns anos.

Esse livro chama-se Programação Web com Node.js, e está à venda desde essa semana na Amazon.

É um livro completo. Mesmo. São 355 páginas, o maior livro que já escrevi na minha curta carreira de escritor. Uma monstruosidade que pode assustar pelo tamanho, mas que possui uma didática clara e objetiva, como todos livros de minha autoria (quem já leu os demais sabe do que estou falando), e que pega o leitor pela mão e ensina desde os conceitos mais básicos até tudo que é necessário para ter uma aplicação rodando.

Do front-end ao back-end, passando por banco de dados e com muito Node.js, Programação Web com Node.js é para quem está começando a trabalhar com web e está completamente confuso com a quantidade de frameworks, bibliotecas e tecnologias disponíveis. Nele, ensino a usar tudo o que deu certo em meus projetos nos últimos anos, não necessariamente as stacks mais badaladas da atualidade, mas o que é bom, confiável e que possui grande mercado.

Neste livro você vai aprender:

  • o básico de algoritmos e programação com JavaScript;
  • tecnologias básicas da web: HTML+CSS+JS;
  • construção de back-end de aplicações e serviços com Node.js;
  • como usar o web framework ExpressJS e diversos outros módulos famosos do Node;
  • como sistemas web funcionam e, principalmente, o protocolo HTTP;
  • como criar interfaces web atraentes com Bootstrap;
  • como criar scripts poderosos e interações real-time com Ajax em JQuery;
  • como usar o banco de dados MongoDB;
  • dezenas de boas práticas de programação com Node.js;

Novamente, devido às atualizações constantes e ao tamanho do livro, optei apenas por ter versão digital, que você pode ler no Kindle, no PC usando o ler.amazon.com e no smartphone/tablet usando o app Kindle Cloud Reader. Um livro desse tamanho impresso não custaria menos de R$100 e rapidamente ficaria desatualizado, coisa que eu detesto.

Além disso, caso assine o Kindle Unlimited (primeiro mês grátis e depois R$20/mês), você ler o meu livro e milhares de outros títulos gratuitamente.

Se ainda não é cliente da Amazon, esta é uma excelente oportunidade de começar com o pé direito. Modéstia à parte. 😉

TDD: Como criar unit tests em Node.js com Tape

Ok, que testar suas aplicações antes de enviá-las para produção é importante todo mundo já sabe. Mas será que você sabe como testar eficientemente sua aplicação escrita em Node.js?

Neste tutorial veremos como utilizar o módulo Tape para criar testes de unidade (unit tests) em uma abordagem de desenvolvimento orientado à testes, o TDD (Test Driven Development). O mais legal é que não nos restringiremos apenas a testar funções isoladas (unit test), também vamos testar APIs completas em Node.js usando o módulo Supertest para forjar requisições HTTP (integration test).

Veremos neste post:

Vamos lá!

Introdução ao TDD

O Test Driven Development/TDD ou Desenvolvimento Orientado à Testes é uma técnica criada por Kent Beck, um famoso programador e autor de livros que atualmente trabalha no Facebook. Também são de autoria de Beck a metodologia ágil Extreme Programming (XP) que inclui técnicas como o TDD e o Pair Programming, que já falei aqui no blog.

A ideia do TDD é que você deve escrever primeiro os testes da sua aplicação, para depois implementar o código que fará com que eles funcionem. Isso pode soar um tanto estranho mas é uma ideia ousada que possui vários benefícios, tais como:

  • diminuição do número de bugs, uma vez que não existem features sem testes;
  • foco nas features que importam para o projeto, pois escrevemos os testes com os requisitos em mãos;
  • permite testes de regressão, para ver se um sistema continua funcionando mesmo após várias mudanças e/ou muito tempo desde a última release;
  • aumento da confiança do time no código programado;

O TDD é executado em um ciclo chamado de Red/Green/Refactor e prega que cada incremento pequeno de software (baby step) deve ser testado, para que bugs sejam corrigidos rapidamente assim que surgem. Ele prega que primeiro você deve escrever o teste, antes mesmo da feature ser implementada. Quando executar esse teste, ele irá falhar (Red), aí você codifica a feature apenas o suficiente para ela passar no teste (Green) e então você melhora o código para que ele não apenas seja eficaz, mas eficiente (Refactor).

Recomenda-se rodar novamente os testes e se após a refatoração a feature parar de funcionar,  roda-se o ciclo novamente.

O coração do TDD são os unit tests.

Introdução aos Testes de Unidade

Existe uma pequena discussão sobre a melhor tradução de “unit test” ser “teste unitário” ou “teste de unidade”. Acredito que seja uma discussão mais conceitual do que prática (como muitas discussões “técnicas” que existem por aí), então não entrarei nela aqui e apenas chamarei de “teste de unidade”.

Um unit test é um teste, geralmente automatizado, que testa uma única unidade da sua aplicação, geralmente uma única função, em um único contexto.

Cheio de “únicos” nesse parágrafo, não?

Mesmo com essa ênfase ainda existem muitas dúvidas e discussões acerca do que pode ser considerado um unit test ou não. Me aterei mais à prática e menos aos conceitos e resumirei como: se o seu teste testa apenas uma coisa (como uma função), chamarei ele de teste de unidade aqui, mesmo essa coisa internamente seja composta de outras coisas (como outras funções internas) afinal, parto do pressuposto que não temos como garantir que algumas funções do Node.js também não chamam diversas outras internamente.

Os unit testes são o coração do TDD pois é com eles que começamos a aplicar TDD. Mesmo que você não seja um “purista” e vá aplicar TDD 100% como manda os livros, tente ao menos passar a utilizar testes de unidade na sua aplicação, pois eles realmente valem a pena.

Um exemplo prático de aplicação de testes de unidade com TDD seria a criação de um método que aplica um desconto, em R$, ao valor de um produto, também em R$. Vamos começar escrevendo a função de teste dessa funcionalidade em um arquivo index.js, comparando o retorno da função aplicarDesconto com o valor esperado, o que chamamos de asserção ou assert (em Inglês):

Se aplicarmos um desconto de R$2 sobre um produto de R$10, o valor esperado como retorno é R$8, certo? Mas o que acontece se executarmos este bloco de código com o comando “node index” no terminal?

Dará um erro porque a função aplicarDesconto ainda não existe. Ou seja, sabemos o resultado esperado, mas não programamos a função ainda. Vamos fazê-lo agora, com uma simples subtração, no mesmo arquivo:

Agora se rodarmos o arquivo index.js no terminal novamente ele deve indicar que está funcionando, pois ao testar nossa função passando 10 e 2, ela retornará 8 e a asserção será verdadeira. Sendo assim, nosso trabalho com esta função terminou, certo?

Errado. E se o valor do desconto for superior ao produto? Você conhece alguma loja que te paga para comprar alguma coisa? Eu não!

Quando uma unidade de código (nossa função nesse caso) possui casos de uso variados, devemos testá-los sob cada contexto, em asserções separadas. Sendo assim, vamos criar um novo teste considerando que, um produto jamais possa ter um valor negativo, mesmo com descontos altos (ficando de graça nesse caso):

Note que essa é uma regra que inventei para esse teste. Se o desconto for maior que o valor, o produto deve sair de graça. Se mandar rodar esse arquivo novamente, verá que esse segundo teste não irá ‘passar’, embora o teste inicial continue passando.

Vamos refatorar nossa função aplicarDesconto para que contemple este cenário que acabamos de descobrir:

Rode novamente seu arquivo de testes e verá que agora os dois testes ‘passam’!

Criar todos os testes necessários para garantir que todas as unidade do seu software realmente funcionam é o que chamamos de cobertura de código (code coverage), cuja utópica marca de 100% deve ser sempre o ideal, embora praticamente inalcançável em sistemas complexos.

E se o desconto for negativo? Precisamos de um teste pra isso, pois um desconto não pode aumentar o valor original de um produto!

E se o valor do produto for negativo? Isso é claramente um erro de integridade dos erros, pois é economicamente impossível!

O quão ‘detalhado’ os seus testes serão vai muito do grau de importância que todas essas questões possuem para o seu negócio e o quanto domina  ele.

Algumas vezes, a sua nova refatoração para fazer com que a função atenda a um novo requisito (como não permitir descontos negativos, por exemplo) pode fazer com  que algum teste antigo deixe de passar. E isso é bom. Sempre que asserções deixam de funcionar durante os testes é hora de reanalisar o seu algoritmo e melhorá-lo. Antes descobrir essa falha durante os testes do que em produção, não é mesmo!

Agora veremos como criar testes de unidade mais profissionais usando o módulo Tape.

Criando Unit Tests com Tape

Você até pode escrever testes de unidade sem módulo algum, apenas usando Node.js puro como fiz acima, sendo que o mínimo que deveria fazer é separar as funções de verdade das funções de teste, em módulos.

No entanto, facilita muito a vida usar bibliotecas como Tape, Mocha e Chai. Para este tutorial vamos usar o Tape, que é uma das mais populares e permite fazer asserções de uma maneira muito simples, prática e padronizada, dando agilidade ao processo de unit testing ou TDD, caso leve a metodologia realmente a sério.

Vamos começar criando uma pasta para um novo projeto, chamado exemplotdd. Crie um index.js e um index.test.js nessa pasta. O primeiro será o arquivo da nossa aplicação e o segundo os unit tests do primeiro.

Abra o terminal e navegue até a pasta em questão. Execute o seguinte comando:

Esse comando serve para criação de um package.json para o seu projeto. Apertando Enter em cada uma das perguntas que ele lhe fará no console usa a configuração padrão. Atente apenas ao fato de que quando chegar a pergunta ‘test command’, digite o seguinte comando:

Apenas confirme as demais perguntas pressionando Enter e quando terminar, abra o arquivo package.json e verá algo parecido com isso:

Note que tem uma seção scripts ali. Nesta seção, definimos atalhos para comandos padrões que poderemos quer executar via NPM, como o comando “test” que é executado no console com:

Mas não vamos fazer isso agora, afinal, não criamos nossos testes com Tape ainda. Então instale agora a dependência do Tape digitando o seguinte comando:

E depois abra o arquivo index.test.js para configurarmos nosso arquivo de testes. Comece o arquivo carregando o módulo tape e o módulo que deseja testar:

Depois, escreva o primeiro teste de unidade como abaixo:

A função test espera o nome do teste e uma função de teste que realizará a asserção (assert). A asserção espera a expressão que indica que o teste passou e uma mensagem de sucesso, finalizando o teste em seguida (end). Essa é uma asserção bem simples e fácil de entender, não?!

Se você rodar agora com “node index.test” ou mais elegantemente com “npm test”, dará um erro como abaixo:

Isso porque essa função ainda não existe. Vamos criá-la tal qual criamos no exemplo anterior sobre testes de unidade, mas no arquivo index.js, exportando-a via module.exports:

Agora teste novamente com ‘npm test’ e verá uma saída bem interessante:

Essa saída do Tape mostra os testes que foram executados, o resultado de cada um e um total de testes e testes bem sucedidos, finalizando com uma mensagem de ok ou não.

Existe um outro módulo que dá uma melhorada nessa saída, o tap-spec, que você instala com ‘npm install tap-spec’ e altera o script de ‘test’ no seu package.json para:

Para testar esse novo módulo, vamos adicionar mais um teste de unidade, tentando aplicar um desconto superior ao valor do produto em nosso index.test.js (já fizemos isso antes, lembra?):

Executando com ‘npm test’, terá como resultado:

tap-spec
tap-spec

Com isso, depois de construirmos testes que cubram nosso código de uma ponta a outra, temos um recurso muito poderoso para fazer testes rapidamente e ver se todas funções da nossa aplicação estão funcionando.

Juntando este conhecimento com ferramentas de Continuous Integration (CI) como CircleCI e Jenkins, podemos incluir testes de unidade automatizados em nosso build para evitar que um código suba para produção com algum bug (um dia falarei disso por aqui).

Mas e como podemos fazer testes mais amplos, como testes em APIs escritas em Node.js? Afinal, é preciso subir um servidor Express para testar uma API, certo? Caso contrário não teríamos objetos de request e responde válidos…

Introdução a Testes de Integração

Chama-se de teste de integração aqueles testes mais amplos em que um teste acaba testando internamente diversas outras funções. Por exemplo, testar se uma chamada GET a uma API está funcionando não é exatamente um teste de unidade considerando que estaremos neste caso testando a chamada/request HTTP da aplicação, o roteamento, a função em si e o retorno/resposta HTTP.

Geralmente testes de integração são mais complicados de automatizar, principalmente quando envolvem interface de usuário ou elementos complexos de infraestrutura, como bancos de dados, embora não seja impossível.

Uma técnica muito comum para abstrair questões de infraestrutura são o uso de mocking. Mocking é o ato de falsificar algum elemento externo à sua aplicação, geralmente de infraestrutura, como se ele nunca falhasse. Por exemplo, uma função que acessa o banco de dados, em um teste que não importa o acesso ao banco, poderia ser mockada para retornar sempre o valor que queremos no teste, pois o que queremos testar é o uso do valor, não o acesso ao banco.

Existem diversos módulos no Node.js que facilitam esta tarefa de mockar objetos e recursos externos, mas particularmente não gosto desta técnica. Em algumas ocasiões é a única saída (alguém aí falou de integração com bancos e financeiras?) mas geralmente prefiro ter ambientes de testes ou simulá-los de maneira mais próxima à realidade.

Da mesma forma, existem diversos módulos no Node.js que facilitam automatizar testes que necessitem de recursos mais complexos. Um deles é o módulo Supertest, que permite que você forje requisições HTTP para testar suas APIs de maneira automática usando Tape ou qualquer outro módulo de asserções.

Testando requisições com Supertest

Basicamente o Supertest é um módulo que forja requisições visando testar webservers em Node.js e verifica o retorno das mesmas para automatizar testes deste tipo de infraestrutura, principalmente web APIs.

Para usar o Supertest, devemos primeira instalar sua dependência em nossa aplicação:

Note que aproveitei e já instalei outras duas dependências que vamos precisar para criar nossa API que aplica descontos. 🙂

Como o foco do nosso tutorial não é criar APIs em Express (coisa que já ensinei em outra oportunidade), apenas copie o código abaixo em um arquivo app.js:

Resumidamente, criamos uma web API usando Express que escuta na porta 3000 esperando por requisições GET /aplicarDesconto passando no path o valor e o desconto a ser aplicado. Internamente pega-se esses dois parâmetros passados no path, converte-os para inteiro e usa-se a função que criamos anteriormente (e cujo módulo index.js carregamos no topo deste arquivo) para calcular o valor descontado, retornando-o em um JSON na resposta.

Atenção ao if que inicia o servidor somente no caso do require.main for igual a module, pois isso evita que o servidor fique ‘pendurado’ mais tarde, durante os testes.

Se você executar esse arquivo com o comando ‘node app’ verá que ele funciona perfeitamente.

Já criamos o teste de unidade da função aplicarDesconto e garantimos que ela está funcionando, o que já garante boa parte do funcionamento da nossa API. Agora vamos criar testes de integração que garantam o funcionamento completo dessa chamada.

Para isso, vamos criar um arquivo app.test.js, que conterá todos os testes do app.js. Comece esse arquivo carregando o módulo supertest, o tape e o app:

Agora, escreva um teste usando tape, que nem já fizemos antes, mas usaremos o supertest dentro dele para forjar a requisição e ler a resposta, visando uma asserção completa, que integre todo o uso da API:

Quando chamamos a função supertest devemos passar para ela o nosso app. A função get define a requisição que faremos, enquanto que as funções expect definem características que indicam que nosso supertest foi bem sucedido, analisando por exemplo o Content-Type e o HTTP status code.

Por fim, quando a requisição é finalizada, podemos usar o objeto t do Tape para analisar um possível erro na requisição e até mesmo fazer asserções em cima do body da resposta, para ver se está retornando o que é esperado.

Mas e agora que temos dois arquivos de teste separados, como fazemos para automatizar a execução dos dois usando apenas um ‘npm test’?

Você pode guardar todos seus arquivos de teste dentro de uma pasta tests e no seu script de test no package.json colocar algo como:

Apenas tome cuidado com os caminhos que referenciam os módulos app e index dentro dos seus arquivos de teste.

Ou então criar um arquivo all.test.js que é um índice de testes que apenas carrega todos os módulos de teste e ele é referenciado no package.json como sendo o arquivo a ser executado.

Em ambos os casos, o resultado é este:

Todos testes executados
Todos testes executados

Atenção: qualquer tipo de conexão que você efetue deve ser encerrada ao término dos testes, caso contrário os testes ficam ‘pendurados’ aguardam o encerramento de todos recursos.

O if que coloquei antes do listen no app.js garante que o servidor não fique rodando eternamente. No caso de um banco de dados, o último teste deve ser sempre o de encerramento da conexão. E assim por diante.

Espero que tenham gostado do tutorial e que apliquem em seus projetos!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!

Node.js: 6 dicas do módulo File System

Node.js File System
Node.js File System

Neste artigo, vou lhe mostrar algumas dicas de um dos módulos principais do Node.js que é o File System, mais conhecido como ‘fs’.

Este módulo fornece operações de I/O (Input/Output ou E/S, Entrada/Saída) através de wrappers simples ao redor de funções POSIX. Para usar o módulo fs (File System) você deve usar o comando require (‘fs’), sendo que todos os métodos possuem versões assíncronas e síncronas.

Veremos neste artigo:

  1. fs assíncrono ou síncrono?
  2. Use file streams
  3. Cópia de arquivos
  4. Não use fs.access
  5. Limitações do fs.watch
  6. Módulos adicionais ao fs

Vamos lá!

#1 – fs assíncrono ou síncrono?

Você sempre deve procurar usar a API assíncrona quando desenvolver código que vai para produção, uma vez que esta API Não bloqueia o event loop e permite que você construa aplicações com maior performance. Um exemplo de código usando ‘fs’ assíncrono pode ser visto abaixo:

Já a API síncrona pode ser usada quando estiver construindo provas de conceito ou pequenas aplicações. O código abaixo faz a mesma coisa que o anterior, mas usando a variante síncrona da mesma função:

#2 – Use File Streams

Infelizmente é muito comum ver desenvolvedores de várias linguagens negligenciando o uso de File Streams. Streams em Node.js são conceitos poderosos que lhe permitem manter um consumo baixíssimo de memória e alta performance nas aplicações que precisavam ler e escrever arquivos.

Streams são estruturas Node.js que lhe permitem manipular dados. Existem três conceitos importantes que você deve entender:

  • source – o objeto de onde seus dados vêm;
  • pipeline – por onde seus dados passam (você pode filtrar ou modificar eles aqui);
  • sink – onde seus dados vão parar (terminam);

Um guia completo sobre Streams pode ser encontrado neste link.

#3 – Cópia de arquivos

Uma vez que o módulo fs não expõe uma função para copiar arquivos, você pode facilmente fazê-lo com streams:

Uma das vantagens de se usar streams para isso é que você consegue transformar os arquivos enquanto eles estão sendo copiados, como por exemplo, fazer uma descompressão:

#4 – Não use fs.access

O propósito da função fs.access é verificar se um usuário possui permissões para um dado arquivo ou caminho, algo como isto:

Note que o segundo parâmetro espera uma ou mais constantes para verificação de permissão. São elas:

  • fs.constants.F_OK – verifica se o path é visível para esse processo;
  • fs.constants.R_OK – verifica se o path pode ser lido por esse processo;
  • fs.constants.W_OK – verifica se o path pode ser escrito por esse processo;
  • fs.constants.X_OK – verifica se o path pode ser executado por esse processo;

Entretanto, note que usar fs.access para verificar a acessibilidade de um arquivo antes de chamar fs.open, fs.readFile ou fs.writeFile não é recomendado e a razão é simples: se você o fizer, outro processo pode alterar o arquivo entre a sua verificação e a ação de usar o arquivo propriamente dita.

Ao invés de verificar, tente usar o arquivo diretamente e trate os possíveis erros que podem acontecer.

#5 – Limitações do fs.watch

A função fs.watch é usada para escutar/observar por alterações em um arquivo ou pasta. No entanto a API fs.watch não é 100% consistente entre as diferentes plataformas e em alguns sistemas nem mesmo é disponível:

  • no Linux, usa inotify;
  • no BSD, usa kqueue;
  • no OS X, usa kqueue para arquivos e FSEvents para pasras;
  • no SunOS (incluindo Solaris e SmartOS), usa event ports;
  • no Windows, esta feature depend de ReadDirectoryChangesW;

Note também que a opção recursiva somente é suportada no OS X e Windows, mas não no Linux.

Além disso, o argumento filename no callback do watch nem sempre é fornecido (além de somente ser suportado no Linux e Windows), logo, prepare-se com fallbacks em caso dele vir undefined:

#6 – Módulos adicionais ao fs

Muitos desenvolvedores não concordam exatamente como o módulo fs funciona e criaram versões alternativas e muito úteis. Outros criam extensões ao fs para expandir as funcionalidades do módulo original. Alguns módulos bem famosos com essas características são:

graceful-fs

Este módulo é um substituto ao fs com algumas melhorias:

  • chamadas de open e readdir são enfileiradas e são automaticamente retentadas em caso de erros como EMFILE;
  • ignora erros EINVAL e EPERM em chown, fchown ou lchown se o usuário não é root;
  • faz com que lchmod e lchown se tornem noops, em caso de indisponibilidade;
  • retenta a leitura do arquivo se o read retornar erro EAGAIN;

Você pode usar esse módulo assim como usaria o módulo fs ou alternativamente substituindo o módulo globalmente:

mock-fs

O módulo mock-fs permite ao módulo fs original usar a memória RAM como se fosse um disco temporário para mockar o sistema de arquivos e facilitar os testes. Ele é bem fácil de usar:

lockfile

File locking é uma maneira de restringir o acesso a um arquivo permitindo somente um processo acessar o arquivo de cada vez. Isto previne problemas de concorrência a um determinado arquivo atuando como um semáforo. Adicionar lockfiles usando o módulo lockfile é bem simples (como tudo em Node, aliás):

fs-extra

É um substituto ao fs tradicional adicionando novas funções e suporte a promises. Foi criado pois o autor afirma que estava cansado de ficar usando mkdirp, rimraf e ncp em seus projetos. Como um autêntico substituto, possui todos as funções originais do fs além de novas funções.

Estas foram algumas dicas relacionados ao módulo File System (fs) do Node.js. E aí, conhece alguma que eu não listei? Deixa aí nos comentários!

Curtiu o post? Então clica no banner abaixo e dá uma conferida no meu livro sobre programação web com Node.js!