Skip to content
lucascr91 edited this page Jul 6, 2022 · 5 revisions

Os testes do pacote em python da basedosdados foram implementados usando o módulo pytest. Neste documento, apresentamos convenções e padrões utilizados na nossa implementação dos testes:

Fixtures

No âmbito do pytest, as fixtures são valores que o usuário define com o decorador @pytest.fixture que ficam globalmente disponíveis para os outros testes definidos no módulo. É importante notar que, no caso do repositório mais, todas as fixtures devem ficar no arquivo chamado connftests.py. Dessa forma, precisamos declarar as fixtures apenas uma vez e elas ficam automaticamente disponíveis para todos os arquivos de teste, sem necessidade de importação.

Um exemplo de fixture é dado por:

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

Ocorre que, pelo nosso coding standard, evitamos sobrescrever variáveis locais com nomes globais em uso. Para manter o uso das fixtures e compatibilizar os arquivos dos módulos de testes com nosso coding standard, adotamos a convenção sugerida na documentação do pytest:

If a fixture is used in the same module in which it is defined, the function name of the fixture will be shadowed by the function arg that requests the fixture; one way to resolve this is to name the decorated function fixture_ and then use @pytest.fixture(name='').

Para o exemplo mostrado acima, temos:

@pytest.fixture(name="input_value")
def fixture_input_vallue():

Testes CLI

Os testes da CLI são um tipo específico de teste e, por essa razão, comentamos aqui sobre as limitações da implementação atual e possíveis frentes de melhoria.

Para se testar uma CLI o ideal seria rodar os comandos em uma máquina isolada com apenas a versão atual do pacote (em teste) instalada. Para isso o click, módulo utilizado na implementação da CLI da BD, disponibiliza um sub-módulo de teste chamado de click.testing. A documentação do módulo, contudo, mostra apenas o caso simples em que o teste é feito para um comando isoladamente. No nosso caso, o teste deve ser feito para grupos de comandos, com o contexto adicionado. A seguir mostramos um exemplo da implementação de um teste para um sub-comando do grupo table

'''
Tests for the CLI module.
'''

from pathlib import Path
import shutil
import os

from click.core import Context

from basedosdados.cli.cli import cli_table
from importlib_metadata import metadata
from more_itertools import bucket

import pytest
import click.testing
from basedosdados import Storage, Dataset, Table
import basedosdados as bd
from basedosdados.exceptions import BaseDosDadosException
from google.api_core.exceptions import NotFound


DATASET_ID = "pytest"
TABLE_ID = "pytest"

TABLE_FILES = ["publish.sql", "table_config.yaml"]

templates=Path(__file__).parent / "templates"
bucket_name = "basedosdados-dev"
metadata_path = Path(__file__).parent / "tmp_bases"
ctx = Context(command=cli_table.commands['create'], obj=dict(templates=templates,bucket_name=bucket_name,metadata_path=metadata_path))

def test_create_no_path_error(testdir):
    """
    Teste if error is raised when no path is provided
    """

    shutil.rmtree(testdir / DATASET_ID / TABLE_ID, ignore_errors=True)

    runner = click.testing.CliRunner()
    result = runner.invoke(cli=cli_table.get_command(ctx=ctx, cmd_name='create'),  args=f'''--if_table_exists=replace {DATASET_ID} {TABLE_ID}''')

    assert isinstance(result.exception, BaseDosDadosException)

Ocorre que o snippet acima não funciona como esperado e o comando gera um retorno diferente do que seria obtido se o comando rodasse diretamente do terminal. Não está claro qual o problema dessa implementação, mas como ela não reproduz o comportamento que obserrvamos no uso direto da CLI, esse modelo de teste foi descartado.

O modelo de teste implementado, então, foi o que faz os testes do comando usando o módullo subprocess e uma instalaçao local do python:

def _run_command(command, output="err"):
    """
    Run a command and return the output | error
    """

    current_dir = os.getcwd()

    os.chdir(cli_dir)
    proc = subprocess.Popen(
        [command],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        shell=True,
    )

    out, err = proc.communicate()

    os.chdir(current_dir)

    if output == "err":
        return err
    if output == "out":
        return out

    raise ValueError("output must be either err or out")

def test_cli_create_no_path_error(testdir, python_path):
    """
    Teste if error is raised when no path is provided
    """

    err = _run_command(
        f"{python_path} -m cli --metadata_path={testdir} table create --if_table_exists=pass {DATASET_ID} {TABLE_ID}",
        output="err",
    )

    assert b"You must provide a path to correctly create config files" in err

Esse formato de teste, contudo, não é o ideal, já que ele utiliza a versão do pacote basedosdados instalado na máquina e não a versão em desenvolvimento.

Clone this wiki locally