Showing posts with label programming language. Show all posts
Showing posts with label programming language. Show all posts

Wednesday, January 8, 2020

Logs em formato JSON com Python

Python possui um módulo de logging bastante robusto já embutido em sua biblioteca padrão. Esse módulo disponibiliza muita coisa pronta para que qualquer pessoa comece a incluir logs em sua aplicação sem grande esforço. Não obstante, o módulo é também altamente configurável, permitindo personalizar a estrutura de handling dos logs, o formato das mensagens, entre outras coisas.

Em algumas situações, pode ser interessante que a mensagem de log gerada seja um JSON bem formatado, contendo informações sobre o fluxo da aplicação em formato chave-valor. Isto é, em vez de emitir logs esparsos, que teriam que ser agregados para dar visão geral do fluxo, poderíamos ter um único log em formato JSON impresso ao final da execução de determinado fluxo (independentemente desse fluxo ter terminado de forma bem sucedida ou não). Essa abordagem facilita não somente a busca por mensagens específicas (usando ferramentas como o CloudWatch Logs Insights) mas também o próprio processo de depuração de problemas, concentrando as informações necessárias num formato bem estruturado.

Em Python, podemos gerar strings JSON a partir de dicionários. Usando as cláusulas try/except/finally, consequimos também garantir a impressão dos logs qualquer que seja o resultado da execução do fluxo contido no bloco de tratamento de exceções. A maior dificuldade é repetir todo esse boilerplate nos pontos em que é necessário aplicar o log JSON. Podemos então lançar mão de mais um recurso da linguagem a fim de evitar essa repetição: decoradores. O Gist a seguir mostra uma maneira de conectar todas essas ideias para construir um decorador que, ao ser aplicado a um método/função, permite construir facilmente um log JSON que será impresso ao final da execução desse método/função, ainda que um erro aconteça durante a execução.

Tuesday, December 24, 2019

Evitando chamadas custosas em testes automáticos

Ao escrever testes automáticos, é comum utilizarmos Test Doubles para substituir dependências diretas do código sob teste. Além de ter impacto direto na forma de pensar e construir tanto os casos de teste quanto o próprio código sob teste, essa prática também permite eliminar chamadas custosas e/ou não confiáveis durante a execução dos testes, garantindo assim mais eficiência e confiabilidade para a suíte de testes como um todo.

Por exemplo, suponha que precisemos testar uma função ou objeto que efetue uma requisição HTTP para uma API externa à nossa aplicação. Podemos criar um mock para o trecho de código que faz a requisição, de forma a simular o retorno esperado e, assim, não ter que fazer a chamada real de rede. Ganhamos em eficiência, pois, sem a requisição, não é necessário lidar com a latência de rede durante a execução dos testes. Ganhamos também em confiabilidade, pois, como chamadas HTTP podem falhar, qualquer requisição externa tem o potencial de gerar um falso positivo na suíte de testes; removendo a chamada, removemos essa possibilidade.

Pensando nisso, é de vital importância que numa base de código grande — com uma suíte de testes, em geral, igualmente grande — consigamos automaticamente garantir que chamadas custosas sejam apropriadamente mockadas. No caso de requisições de rede, podemos simplesmente bloquear ou restringir chamadas de socket durante a execução dos testes. É o que o pytest-socket, por exemplo, faz. Para quem usa o pytest, basta instalar o plugin e executar pytest --disable-socket. Qualquer teste em que uma chamada de socket for feita (mesmo por pacotes terceiros importados) irá falhar com SocketBlockedError.

Para códigos com custo de computação alto (ex: uma função com um pesado cálculo recursivo) que façam parte da aplicação, uma forma de evitar seu uso sem mock em testes é torna esse requisito uma parte do próprio código, isto é, permitir que a pessoa que o desenvolveu adicione alguma marcação que bloqueie a chamada em ambiente de teste. Não conhecendo uma solução já existente para isso em Python, me aventurei em criar a minha própria e daí surgiu o decorador enforce_mock_in_tests. Esse decorador pode ser aplicado a classes ou funções. A cada chamada do código decorado, ele avalia se o ambiente é de teste ou não e, se for, uma exceção é lançada, dando uma clara indicação ao desenvolvedor de que aquele código não deve ser chamado diretamente nos casos de teste, mas sim mockado. Além do código do decorador em si, o Gist abaixo contém também um arquivo com casos de teste para ele, permitindo ver como o decorador pode ser usado no código da aplicação.