Diferenças entre os frameworks de teste automatizado Unittest e Pytest em Python

Introdução

O Python oferece diversas opções para automação de testes, com os frameworks Unittest e Pytest sendo as mais populares. Enquanto o Unittest é uma biblioteca padrão do Python que permite extensibilidade e desenvolvimento personalizado, o Pytest ganha destaque por sua sintaxe mais concisa, flexibilidade e ecossistema de plugins rico. Este artigo explora as principais diferenças entre esses dois frameworks, ajudando você a escolher o mais adequado para seus projetos.

Comparação: Unittest vs Pytest

Vamos analisar as diferenças fundamentais entre esses frameworks em vários aspectos:

Regras de Escrita de Testes

Condições Pré e Pós-teste

Asserções

Relatórios de Teste

Mecanismo de Reexecução em Falhas

Parametrização

Execução Classificada de Testes

Para uma visão consolidada, apresentamos uma tabela comparativa:

Característica Unittest Pytest
Complexidade de Código Alta Baixa
Compatibilidade Limitada Amplamente compatível
Plugins Poucos disponíveis Extensa variedade
Personalização Fácil de estender Extensível através de fixtures
Execução Paralela Não suportada nativamente Suportada via plugin xdist
Sintaxe Mais verbosa Concisa e expressiva

Em resumo, o Unittest possui uma estrutura de código mais complexa, com compatibilidade limitada e poucos plugins, mas oferece facilidade para desenvolvimento personalizado. Por outro lado, o Pytest é mais conveniente e rápido, com sintaxe simplificada, capacidade de executra testes no estilo Unittest sem modificações, e excelente compatibilidade. O ecossistema de plugins do Pytest é significativamente mais rico, incluindo plugins para Flask, reexecução em falhas e execução paralela via xdist, resultando em maior eficiência.

Demonstração Prática

Para ilustrar as diferenças práticas entre esses frameworks, apresentaremos exemplos comparativos de implementação.

Diferenças em Configurações Pré e Pós-teste

Primeiro, vamos examinar como o Unittest lida com configurações de setup e teardown:

import unittest

class TestConfiguracoes01(unittest.TestCase):
    # Executado antes de todos os testes
    def setUp(self) -> None:
        print("Início do setUp")
    
    def tearDown(self) -> None:
        print("Fim do tearDown")
    
    # Executado uma vez por classe
    @classmethod
    def setUpClass(cls) -> None:
        print("Início do setUpClass")
    
    @classmethod
    def tearDownClass(cls) -> None:
        print("Fim do tearDownClass")
    
    # Caso de teste
    def teste_001(self):
        print("Caso de teste 001")

class TestConfiguracoes02(unittest.TestCase):
    def teste_002(self):
        print("Classe de teste 2")

# Executado antes de todos os módulos
def setUpModulo():
    """
    Executado uma vez antes de todas as classes de teste,
    o nome da função é fixo e reconhecido automaticamente pelo Unittest
    """
    print('Teste de integração >>>>>>>>>>>>>>Início')

def tearDownModulo():
    print("Teste de integração >>>>>>>>>>>>>>Fim")

if __name__ == '__main__':
    unittest.main()

Agora, vejamos como o Pytest lida com essas mesmas configurações:

  1. O Pytest oferece maior flexibilidade para configurações pré e pós-teste, permitindo definição personalizada através de fixtures. A hierarquia de execução é:
  2. Nível de módulo: Global, executado uma vez para todo o módulo, antes dos casos de teste.
  3. Nível de classe: Definido dentro da classe, aplicável apenas a essa classe.
  4. Nível de função: Aplicável apenas a funções fora de classes.
  5. Nível de método: Definido dentro de classes, executado para cada método.
def setup_modulo():
    print('\nAntes de todo o módulo - executado uma vez')

def teardown_modulo():
    print('\nDepois de todo o módulo - executado uma vez')

def setup_funcao():
    print('\nAntes de cada função fora de classe')

def teardown_funcao():
    print('\nDepois de cada função fora de classe')

def teste_ab():
    valor = 2
    assert valor < 3

def teste_aba():
    valor = 2
    assert valor < 3

class TestAPI():
    def setup_classe(self):
        print('\nAntes dos testes desta classe')
    
    def teardown_classe(self):
        print('\nDepois dos testes desta classe')
    
    def setup_metodo(self):
        print('\nAntes de cada método desta classe')
    
    def teardown_metodo(self):
        print('\nDepois de cada método desta classe')
    
    def test_aa(self):
        a = 1
        print('\nCaso de teste: a')
        assert a > 0
    
    def test_b(self):
        b = 2
        assert b < 3

  1. Usando Fixtures no Pytest:

As fixtures no Pytest permitem definir configurações personalizadas. Primeiro, criamos um arquivo conftest.py:

# conftest.py
import pytest

@pytest.fixture
def login():
    print("Realizando login...")
    return "token_autenticacao"

Em seguida, usamos essa fixture nos nossos testes:

# arquivo_teste.py

def teste_um(login):
    print("Após login, executando operação 111")

def teste_tres(login):
    print("Após login, executando operação 333")

  1. Uso avançado com múltiplas fixtures:
# conftest.py
import pytest

@pytest.fixture
def login():
    print("Realizando login...")
    return "token_autenticacao"

@pytest.fixture
def abrir_pagina():
    print("Abrindo página inicial...")
    return "pagina_principal"

# arquivo_teste.py

def teste_um(login):
    print("Após login, executando operação 111")

def teste_dois(login, abrir_pagina):
    print("Executando teste 2 com login e página aberta")

def teste_tres(abrir_pagina):
    print("Executando teste 3 apenas com página aberta")

Diferenças em Parametrização

A parametrização é útil quando precisamos testar múltiplos cenários com diferentes dados. Por exemplo, ao testar uma funcionalidade de login com combinações de usuário e senha válidos e inválidos.

Parametrização no Unittest:

O Unittest utiliza a biblioteca DDT (Data-Driven Tests) para parametrização:

dados_teste = [1, 2, 3]

@ddt.ddt
class TestParametrizado(unittest.TestCase):
    @ddt.data(*dados_teste)
    def teste_001(self, dado):
        print(dado)

Parametrização no Pytest:

O Pytest possui suporte nativo para parametrização através do decorator @pytest.mark.parametrize:

@pytest.mark.parametrize('dado', [1, 2, 3])
class TestParametrizado(object):
    def teste_001(self, dado):
        print(dado)

Para parâmetros múltiplos:

@pytest.mark.parametrize("parametro1, parametro2", [
    (1, 2),
    (3, 4),
    (5, 6)
])
def teste_multiparametro(parametro1, parametro2):
    resultado = parametro1 + parametro2
    assert resultado > 0

Conclusão

Os frameworks Unittest e Pytest oferecem abordagens distintas para automação de testes em Python. Enquanto o Unittest é mais estruturado e parte da biblioteca padrão, o Pytest proporciona maior flexibilidade, sintaxe mais concisa e um ecossistema de plugins mais rico. A escolha entre eles dependerá das necessidades específicas do seu projeto, equipe e perferências de desenvolvimento.

Tags: Python unittest pytest Automação de Testes frameworks de teste

Publicado em 7-16 09:58