Ir para o conteúdo

Frameworks de testes: JUnit, PyTest, Selenium, Cypress ou Robot Framework

De Wikiversidade

Testes de software são fundamentais para avaliar e garantir que um produto ou aplicação esteja funcionando conforme esperado. Realizar testes adequados traz benefícios significativos, como a prevenção de alguns bugs e a melhoria no desempenho do sistema. Eles desempenham um papel crucial na identificação precoce de falhas, no aprimoramento da qualidade do código, no aumento da confiança nas mudanças implementadas e na redução do risco de erros em produção.

Nesse contexto, os testes automatizados se tornam muito úteis, pois contribuem para um ciclo de desenvolvimento mais ágil, facilitando a manutenção e a escalabilidade do software. Dessa forma, a adoção de frameworks de testes torna o desenvolvimento mais eficiente e auxilia o software a cumprir os requisitos de qualidade, desempenho e segurança.

Frameworks como JUnit, PyTest, Selenium, Cypress e Robot Framework são ferramentas de automação que ajudam a assegurar a qualidade e confiabilidade dos softwares. Esses frameworks possibilitam a verificação contínua do comportamento das aplicações e a detecção rápida de falhas.

O JUnit, por exemplo, é amplamente utilizado para testes unitários em Java, permitindo que os desenvolvedores validem pequenas unidades de código. O PyTest, popular no ecossistema Python, oferece uma sintaxe simples e eficiente para testes unitários e de integração. Já o Selenium e o Cypress são ferramentas voltadas para testes de interfaces web, sendo o Selenium indicado para testes em múltiplos navegadores, enquanto o Cypress se destaca em testes rápidos e em tempo real. Por fim, o Robot Framework é uma ferramenta genérica para automação de testes, permitindo a execução de testes de aceitação de forma estruturada, podendo ser utilizado em diversas linguagens de programação.

Neste tutorial, iremos detalhar os conceitos relacionados ao framework PyTest.

O Pytest é um framework de teste para python criado para facilitar a escrita de testes. É uma ferramenta amplamente usada, com uma estrutura de código aberto que permite aos desenvolvedores escrever conjuntos de testes simples e compactos, ao mesmo tempo que oferece suporte a testes de unidade, testes funcionais e testes de API.

Instalação

[editar | editar código]

É necessário já ter previamente instalado o Python 3.8+ ou PyPy3.

Para instalar o Pytest, execute o seguinte comando:

pip install -U pytest

Para checar se a instalação foi concluída com sucesso, execute:

pytest --version

Arquivos de teste

[editar | editar código]

Ao executar o comando Pytest dentro do ambiente virtual Python, o framework realizará uma varredura nos diretórios e subdiretórios do repositório, buscando arquivos que sigam o padrão de nomenclatura test_*.py ou *_test.py . Por isso, é importante seguir esse padrão.

Teste de Funções

[editar | editar código]

Para que o Pytest colete o teste e o execute, as funções de teste devem ser prefixadas com test_ . Confira a seguir um exemplo de um teste contido no arquivo test_sample.py:

def func(x):
    return x + 1

def test_answer():
    assert func(3) == 5

Classes de teste e métodos de teste

[editar | editar código]

As classes e os métodos de teste também possuem convenções para nomenclatura:

  • As classes de teste têm o prefixo Test
  • Os métodos de teste têm o prefixo test_

Veja abaixo um exemplo de teste válido:

class TestClass:
    def test_one(self):
        x = "this"
        assert "h" in x

    def test_two(self):
        x = "hello"
        assert hasattr(x, "check")

Execução de teste

[editar | editar código]

O Pytest oferece várias opções para executar e selecionar testes, seja pela linha de comando ou por meio de um arquivo.

Executar testes em um módulo

pytest test_mod.py

Executar testes em um diretório

pytest testing/

Executar testes por argumentos

Para isso, é preciso informar o nome do arquivo do módulo relativo ao diretório de trabalho, seguido por especificadores como o nome da classe e da função, separados por ::, e parâmetros entre [].

Para executar um teste específico dentro de um módulo:

pytest tests/test_mod.py::test_func

Para executar todos os testes em uma classe:

pytest tests/test_mod.py::TestClass

Especificando um método de teste específico:

pytest tests/test_mod.py::TestClass::test_method

Especificando uma parametrização específica de um teste:

pytest tests/test_mod.py::test_func[x1,y2]

Utilização do import

[editar | editar código]

É possível manter os scripts e testes em arquivos diferentes. Para isso, basta adicionar a instrução import. No exemplo a seguir, temos dois arquivos: calcualte_age.py, que calcula a idade do usuário, e test_calculate_age.py:

# calcualte_age.py
import datetime

def get_age(yyyy:int, mm:int, dd:int) -> int:
    dob = datetime.date(yyyy, mm, dd)
    today = datetime.date.today()
    age = round((today - dob).days / 365.25)
    return age
# test_calculate_age.py
from calculate_age import get_age

def test_get_age():
    # Given.
    yyyy, mm, dd = map(int, "2001/07/11".split("/"))   
    # When.
    age = get_age(yyyy, mm, dd)
    # Then.
    assert age == 23

Instrução assert

[editar | editar código]

O Pytest permite o uso do assert para verificar expectativas e valores em testes Python. Confira o seguinte exemplo:

# content of test_assert1.py
def f():
    return 3


def test_function():
    assert f() == 4

Execução:

pytest test_assert1.py

Saída:

=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item

test_assert1.py F                                                    [100%]

================================= FAILURES =================================
______________________________ test_function _______________________________

    def test_function():
>       assert f() == 4
E       assert 3 == 4
E        +  where 3 = f()

test_assert1.py:6: AssertionError
========================= short test summary info ==========================
FAILED test_assert1.py::test_function - assert 3 == 4
============================ 1 failed in 0.12s =============================

Na saída, algumas métricas são apresentadas e é indicado se o teste falhou ou obteve sucesso.

  • . sinaliza que o teste foi aprovado;
  • F sinaliza que houve falha;

Além disso, com o uso do assert, é possível obter uma descrição melhor sobre a divergência encontrada.

Instrução pytest.raises

[editar | editar código]

Atua como um gerenciador de contexto, que capturará a exceção do tipo fornecido para afirmar que a exceção acontece. Exemplo:

def test_div_zero_exception():
    """
    pytest.raises can assert that exceptions are raised (catching them)
    """
    with pytest.raises(ZeroDivisionError):
        x = 1 / 0

Neste caso, o teste terá sucesso. Se realizarmos uma modificação para x = 1 / 1 , o teste irá falhar pois a exceção esperada não acontecerá.

Parametrização e a instrução @pytest.mark.parametrize

[editar | editar código]

Vamos analisar o código abaixo:

def test_eval_addition():
    assert eval("2 + 2") == 4

def test_eval_subtraction():
    assert eval("2 - 2") == 0

def test_eval_multiplication():
    assert eval("2 * 2") == 4

def test_eval_division():
    assert eval("2 / 2") == 1.0

Embora essa solução funcione, ela não é a mais eficiente, pois envolve um grande volume de código repetitivo. Uma abordagem melhor para resolver esse problema é utilizar o @pytest.mark.parametrize, que permite a parametrização de argumentos em uma função de teste. Com isso, podemos definir uma única função de teste e deixar o PyTest testar automaticamente os diferentes parâmetros que especificarmos. Assim, é possível reescrever o código acima como:

import pytest

@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
    assert eval(test_input) == expected

Instrução @pytest.fixture

[editar | editar código]

As fixtures são funções que criam um contexto confiável, consistente e bem definido para os testes, fornecendo dados ou estados necessários para a execução dos mesmos, o que permite que os testes sejam executados de forma isolada e controlada. Além disso, é muito útil para sinalizar quais funções devem ser executadas primeiro.

Os serviços, estado ou outros ambientes operacionais configurados por fixtures são acessados ​​por funções de teste por meio de argumentos. Para cada fixture usada por uma função de teste, normalmente há um parâmetro (nomeado após o fixture) na definição da função de teste. Podemos sinalizar ao PyTest que uma função específica é um fixture utilizando @pytest.fixture. Confira o exemplo abaixo:

import pytest

class Fruit:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return self.name == other.name


@pytest.fixture
def my_fruit():
    return Fruit("apple")


@pytest.fixture
def fruit_basket(my_fruit):
    return [Fruit("banana"), my_fruit]


def test_my_fruit_in_basket(my_fruit, fruit_basket):
    assert my_fruit in fruit_basket

A partir do exemplo, temos o seguinte fluxo:

  1. O Pytest identifica as dependências do teste (my_fruit e fruit_basket) e executa as fixtures correspondentes para criar os objetos necessários.
  2. A fixture my_fruit retorna uma instância de Fruit com o nome "apple".
  3. A fixture fruit_basket cria uma lista contendo uma banana e a maçã (referenciada como my_fruit).
  4. O teste verifica se a instância retornada por my_fruit está na lista fruit_basket.
  5. O método __eq__ da classe Fruit é usado para comparar os objetos durante a execução do assert.

Após a execução do código, temos que o teste foi aprovado pois a ordem de execução ocorreu corretamente:

=================================================================== test session starts ===================================================================
platform win32 -- Python 3.9.5, pytest-8.3.3, pluggy-1.5.0
rootdir: /tutorial
collected 1 item

test_fruits.py .                                                                                                                                     [100%] 

==================================================================== 1 passed in 0.08s ====================================================================

Referências

[editar | editar código]

DataCamp. Pytest Tutorial: A Hands-On Guide to Unit Testing. Disponível em: https://www.datacamp.com/pt/tutorial/pytest-tutorial-a-hands-on-guide-to-unit-testing. Acesso em: 29 nov. 2024.

IBM. Software Testing. Disponível em: https://www.ibm.com/br-pt/topics/software-testing. Acesso em: 29 nov. 2024.

MICROSOFT. Testando Python com o Pytest. Disponível em: https://learn.microsoft.com/pt-br/training/modules/test-python-with-pytest/. Acesso em: 29 nov. 2024.

PYTEST. Getting Started. Disponível em: https://docs.pytest.org/en/stable/getting-started.html. Acesso em: 28 nov. 2024.