Testes unitários em Python: como utilizar o Unittest e executá-los na AWS

Itaú Tech
ItauTech
Published in
7 min readDec 6, 2022

Por Erik Fernando Mendes Silva, Team Member na Comunidade Integrada de Investment Services no Itaú

Imagem com fundo laranja, do lado esquerdo o seguinte texto: Testes unitários em Python, como integrar testes à AWS aplicando o framework UnitTest na prática. Do lado direito a foto de um homem negro, usando óculos, sentado e digitando, com três telas de computador.

Em setembro, publicamos aqui no Medium um artigo sobre o método de desenvolvimento orientado a testes, em que falamos sobre a importância desse conceito para a validação de códigos, aumento de produtividade e prevenção de problemas futuros com suas aplicações. Ao longo do conteúdo, ainda falamos sobre testes unitários e compartilhamos um tutorial de como você pode executá-los em Python, por meio do framework PyTest.

No entanto, existem diversas alternativas para a execução de testes que podem te ajudar nas mais diferentes situações.

Por isso, no artigo dessa semana, vamos detalhar e mostrar como aplicar na prática o framework UnitTest, além de oferecer uma introdução às bibliotecas Boto3 e Moto, que podem te ajudar com a integração dos testes à AWS. Dessa forma, você poderá avaliar qual delas melhor se encaixa em seu dia a dia.

Uma introdução ao framework Unittest

Assim como o PyTest, o UnitTest é um framework de testes. As duas opções são bastante semelhantes: ambas oferecem soluções para a execução de testes e são nativos em Python. É muito comum que times utilizem o PyTest como framework principal, enquanto aproveitam de algumas funcionalidades de desenvolvimento e gerenciamento específicas do UnitTest, como a unittest.mock, por exemplo.

unittest.mock é uma classe desse framework que nos permite substituir partes do nosso sistema de testes por objetos simulados, o que nos dá a oportunidade de gerenciar e controlar como esses objetos vão se comportar, bem como o que irão nos retornar. Além disso, o mock nos fornece um recurso patch(), que oferece a possibilidade de manipulação dos atributos classe e módulo — que estão sendo testados ou que possuem integração com a funcionalidade a ser testada — dentro do escopo de um teste, o que transforma essas parcelas temporariamente em um objeto mock.

Essa funcionalidade é muito útil para a testagem de partes do código que interajam com serviços externos, como bancos de dados, conexões e até mesmo AWS, já que torna possível a criação de simulações dessas interações para controlarmos o seu comportamento como se fosse o código real em ação. Veja como isso acontece na prática no exemplo abaixo:

Def soma(a,b):return a+b
 
 Def prepara_soma():
 
 Res = soma(1,1)
 
 Return res
Def test_prepara_soma():
 
 With patch(“test_arquivo.soma”) as mock_soma:
 
 Mock_soma.return_value=4
 
 Response = prepara_soma()
 
 Assert response == 4

Neste primeiro exemplo de como desenvolver objetos simulados, criamos o método soma, com o método prepara_soma acoplado a ele. Nos testes, usamos o recurso “with”, em adição à funcionalidade “patch” disponibilizada pela classe mock. A partir daí, definimos o nome desse objeto como “mock_soma”.

Repare que o método prepara_soma passa os valores “1,1” para o método soma, o que resultaria na resposta “2”. No entanto, como criamos o objeto mock dentro desse teste, conseguirmos definir um outro retorno desse objeto: dessa forma, conseguimos alterar a resposta original para que ele nos retorne o resultado “4”.

@patch(“test_arquivo.soma”)
 
 Def test_prepara_soma(mock_soma):
 
 Mock_soma.return_value = 4
 
 Response = prepara_soma()
 
 Assert response == 4

Já neste segundo exemplo, utilizamos um decorator patch: ele realiza a mesma função do bloco “with”, que utilizamos no exemplo anterior, mas também pode incluir na assinatura do método o nome do objeto mock que criamos.

A vantagem da utilização do mock é justamente a minimização dos impactos causados por funcionalidades das quais temos pouco controle para testar, e que muitas vezes se tratam de serviços externos ao nosso código. Ela ainda possui outras diversas funcionalidades que auxiliam nossa rotina de testes — caso queira se aprofundar mais no tema, confira a sua documentação oficial.

Testes unitários em Python — e na AWS

Para começarmos a falar sobre esse tema, vamos entender primeiro como funciona a integração do Python com serviços externos. Para isso, você precisa conhecer a biblioteca Boto3.

Na AWS, cada API de serviço obtém um cliente que a fornece uma interface. Com a Boto3, conseguimos criar uma sessão que armazena o seu estado de configuração e permite a criação de clientes e recursos de serviços, a partir da classe boto3.Session.

Ela representa a configuração de uma identidade do usuário IAM ou função assumida, além da região da AWS. Veja no exemplo abaixo como criar uma conexão usando a Boto3:

Import boto3
 
 Session = boto3.session.Session(region_name=””)
 
 Client = session.client(service_name=””,region_name=session.region_name)
mport boto3
 
 
 Instancia = boto3.resource(“”)

No primeiro exemplo, criamos a sessão do Boto3 na região desejada, e a partir dela, criamos um cliente na mesma região, que receberá o seu service_name. Nessa lista, você pode conferir quais serviços a biblioteca suporta. Já no segundo exemplo, criamos a instância a partir da funcionalidade “resource”, que também possibilita a criação da conexão de maneira simplificada, onde você passa como argumento do método o recurso que deseja conectar (S3, Lambda, SQS etc.).

De forma resumida, em comparação com os clientes, os resources são abstrações de alto nível dos serviços da AWS. Eles são o padrão recomendado para a utilização da Boto3, pois abstraem a preocupação com detalhes subjacentes na interação com os serviços desejados e resultam numa escrita de código mais simples. No entanto, os resources não estão disponíveis para todos os serviços da AWS — o que significa que, em alguns casos, não há outra escolha a não ser usar um cliente.

Uma vez que entendemos que podemos criar clientes para diversos serviços, precisamos saber que cada serviço tem seus próprios métodos. Vamos exemplificar usando o cliente do Bucket S3:

imagem lista opções disponíveis de métodos: abort_multipart_upload( ) can_paginate( ), close( ), complete_multipart_upload( ), copy( ), copy_object( ), create_bucket( ), create_multipart_upload( ), delete_bucket( ), delete_bucket_analytics_configuration( ), delete_bucket_cors( ), delete_bucket_encryption( ), delete_bucket_intelligent_tiering_configuration( ), delete_bucket_inventory_configutation( )

Além dos métodos acima, existem diversos outros disponíveis para clientes S3, cada um com suas propriedades de requisição e resposta, argumentos e estilos etc. É por essa razão que a documentação da Boto3 é bastante rica em detalhes: praticamente não há como desenvolver sem que ela esteja disponível para consulta.

No exemplo, vamos aplicar o método “create_bucket” seguindo as recomendações da documentação. A requisição desse método acontece da seguinte maneira:

imagem mostra o código de sintax

Nesse caso, nem todas as propriedades listadas na documentação são obrigatórias. Veja que apenas a propriedade Bucket é utilizada abaixo:

Import boto3
 
 
 Session = boto3.session.Session(region_name=”s a-east-1")
 
 S3_client = session.client(service_name=”s3", region_name=session.region_name)
 
 
 S3_client.create_bucket(Bucket=”examplebucket”)

Com isso, criamos a sessão Boto3 e um cliente do serviço S3 em uma região especifica, realizando então a chamada do método “create_bucket” para a criação de um Bucket.

Deu para entender um pouco sobre como funciona a Boto3 e de como a utilizamos para interagir com os serviços da AWS? Então agora vamos explorar como testar tudo isso em um serviço externo do qual não temos controle.

Uma introdução à biblioteca Moto

Se você leu os últimos parágrafos com atenção, muito provavelmente já deve ter pensado que estes testes deverão ser executados com a utilização da classe mock, que simula serviços dos quais não temos controle. Mas como podemos utilizá-la para simular algo que não faz parte do nosso código?

É para isso que utilizamos a biblioteca Moto. Quando ela entra na jogada, conseguimos simular serviços da AWS e aliviar a necessidade de escrevermos simulações por conta própria: ela já nos fornece os ambientes de cada serviço simulado, permitindo que todas as operações dos clientes apresentados sejam feitas em um ambiente simulado, sem impacto real nos ambientes da AWS.

O conceito é relativamente simples: quando a Moto é invocada para um serviço, ela aplica o mock à todas as chamadas realizadas para ele via Boto3, e controla a execução dessas requisições como objetos simulados. Veja como ela funciona na prática:

No exemplo, importamos da Moto o módulo “mock_s3” e utilizamos nele um decorator. Dessa forma, invocamos a Moto para que ela possa mockar todas as chamadas que forem feitas para o serviço S3.

Com isso, criamos uma conexão com um cliente S3, realizamos a operação de criação de um bucket e armazenamos um objeto com o nome “obj_teste”, com o conteúdo sendo “Teste”. Por fim, realizamos a chamada do método “get_object” para pegar o objeto no bucket desejado — com a condição de “assert” aplicada, o que para garante que o Body do objeto capturado seja realmente o esperado.

A documentação da Moto também é muito rica em informações — e será importante para que você consiga tirar dúvidas e realizar testes mais completos e assertivos no dia a dia.

Com esse artigo, encerramos a nossa série sobre testes unitários em Python com uma boa base de conhecimento para que você os aplique na sua rotina de trabalho. Como da última vez, compartilhamos algumas documentações e links úteis que podem te ajudar!

Tem alguma dica, informação complementar ou dúvida sobre esse tema para compartilhar? Escreva nos comentários que vamos ficar felizes em bater um papo com você! 😉

--

--