Testando testes no Python – Parte 3: Pytest dentro do Pytest

python-testing-tests (3 Part Series)

1 Testando testes no Python – Parte 1: motivos e alternativas
2 Testando testes no Python – Parte 2: fixtures parametrizadas
3 Testando testes no Python – Parte 3: Pytest dentro do Pytest

Pessoas geralmente começam engatinhando, depois andam, evoluem para corrida, e algumas fazem coisas mais estranhas, como Parkour.

Pessoas Devs geralmente começam codando, depois testam, evoluem para o TDD, e algumas fazem coisas mais estranhas, como testes de testes.


Boas vindas! 🤩 Esse é o 3º artigo de uma curta série, contando um pouco mais sobre “testes de testes”. 🧪

Vou discutir as motivações, alternativas, e detalhar as formas que fazemos em projetos de Python na Trybe.

Retomando de onde paramos

No artigo anterior mostrei como foi construída a primeira solução para testes de mutações customizadas, utilizando uma fixture autouse parametrizada com as mutações.

Mas havia uma limitação: nesse modelo a pessoa estudante precisa construir todos os seus testes dentro de uma função específica. É suficiente se queremos exercitar a criação de bons asserts, mas limitante quando pensamos em fazer testes mais elaborados e melhor organizados.

Trocando de “função” para “módulo”

Logo de início, a ideia era que precisávamos parar de parametrizar uma função de testes (o que a fixture faz no exemplo do artigo anterior), e fazer a parametrização para um arquivo (módulo) inteiro. Ou seja, executar todo um arquivo de testes da pessoa estudante para cada mutação.

Assim a pessoa estudante poderia fazer, por exemplo, 5 funções de teste em um arquivo e, quando uma mutação for aplicada, pelo menos 1 das 5 funções de teste deve falhar. Se todos os testes passarem para uma das mutações, consideramos que ainda não foi atingida a qualidade que esperamos.

A missão era minha, e eu não fazia ideia de como implementar. Novamente entramos naquele ponto que não havia nada pronto ou óbvio para usarmos, e precisei partir para pesquisa e experimentação.

“Como rodar um arquivo de teste no Pytest?”

Essa provavelmente foi a primeira pesquisa que fiz no Google, esperando que surgisse alguma resposta para o que precisávamos. E não, obviamente não apareceu.

Ou será que apareceu?

As respostas para essa pesquisa são de conteúdos para iniciantes, e elas citam comandos básicos da CLI do Pytest:

python <span>-m</span> pytest tests/test_file.py
python <span>-m</span> pytest tests/test_file.py
python -m pytest tests/test_file.py

Enter fullscreen mode Exit fullscreen mode

E meu primeiro pensamento foi:

“Eu já sei disso! Me mostre algo que eu não sei! “

E logo em seguida:

“Calma, realmente é bem simples solicitar ao Pytest a execução de um arquivo completo de testes. Se eu conseguir fazer isso dentro de uma execução do Pytest que já está em curso, consigo usar a parametrização! Será que é possível? 🤔”

Resposta: Sim, é possível!

O Pytest é um módulo do Python como qualquer outro. Temos o costume de acioná-lo pela CLI, mas essa é apenas uma interface para um código “chamável” do Python. Na própria documentação do Pytest há a indicação de como executá-lo sem a CLI:

<span>import</span> <span>pytest</span>
<span>retcode</span> <span>=</span> <span>pytest</span><span>.</span><span>main</span><span>()</span>
<span>import</span> <span>pytest</span>

<span>retcode</span> <span>=</span> <span>pytest</span><span>.</span><span>main</span><span>()</span>
import pytest retcode = pytest.main()

Enter fullscreen mode Exit fullscreen mode

Um retcode igual a 0 (zero) significa que os testes passaram, falharam caso contrário.

Executando o Pytest dentro do Pytest

Não parece uma ideia muito agradável, e até a documentação da ferramenta faz um alerta sobre isso:

[…] fazer multiplas chamadas a pytest.main() a partir do mesmo processo (para re-executar testes, por exemplo) não é recomendado.

Parece que escreveram isso especialmente pra mim! Mas sem ousadia nunca venceremos obstáculos, não é mesmo?

Brincadeiras a parte, seguimos entendendo que esse é um uso controlado e (até o momento) com complexidade moderada.

Ajustando o exemplo anterior

No exemplo do artigo anterior toda a configuração de mutações era feita nos arquivos tests/sorter_mutations.py (definição das mutações) e tests/conftest.py (parametrização e patch das mutações), mas este 2º não será mais necessário.

Como nosso objetivo é somente ter um PASSED caso a chamada do pytest.main() falhe para todas as mutações, podemos abandonar a complexidade da fixture parametrizada com XFAILs e seguir com uma opção mais direta: uma função de teste parametrizada que fará a chamada ao pytest.main().

Isolando essa nova função de teste em um arquivo dedicado, teremos o seguinte:

Que traduzido para código, fica assim:

<span>from</span> <span>unittest.mock</span> <span>import</span> <span>patch</span>
<span>from</span> <span>tests</span> <span>import</span> <span>sorter_mutations</span>
<span>import</span> <span>pytest</span>
<span>mutated_functions</span> <span>=</span> <span>[</span>
<span>sorter_mutations</span><span>.</span><span>no_exception_mutation</span><span>,</span>
<span>sorter_mutations</span><span>.</span><span>slice_input_mutation</span><span>,</span>
<span>]</span>
<span>@pytest.mark.parametrize</span><span>(</span><span>"</span><span>mutation</span><span>"</span><span>,</span> <span>mutated_functions</span><span>)</span>
<span>def</span> <span>test_mutations_for_test_module</span><span>(</span><span>mutation</span><span>):</span>
<span>with</span> <span>patch</span><span>(</span><span>"</span><span>tests.test_sorter.sort_this_by</span><span>"</span><span>,</span> <span>mutation</span><span>):</span>
<span>retcode</span> <span>=</span> <span>pytest</span><span>.</span><span>main</span><span>([</span><span>"</span><span>tests/test_sorter.py</span><span>"</span><span>])</span>
<span>assert</span> <span>retcode</span> <span>!=</span> <span>0</span><span>,</span> <span>"</span><span>Mutação deveria falhar</span><span>"</span>
<span>from</span> <span>unittest.mock</span> <span>import</span> <span>patch</span>
<span>from</span> <span>tests</span> <span>import</span> <span>sorter_mutations</span>
<span>import</span> <span>pytest</span>

<span>mutated_functions</span> <span>=</span> <span>[</span>
    <span>sorter_mutations</span><span>.</span><span>no_exception_mutation</span><span>,</span>
    <span>sorter_mutations</span><span>.</span><span>slice_input_mutation</span><span>,</span>
<span>]</span>


<span>@pytest.mark.parametrize</span><span>(</span><span>"</span><span>mutation</span><span>"</span><span>,</span> <span>mutated_functions</span><span>)</span>
<span>def</span> <span>test_mutations_for_test_module</span><span>(</span><span>mutation</span><span>):</span>
    <span>with</span> <span>patch</span><span>(</span><span>"</span><span>tests.test_sorter.sort_this_by</span><span>"</span><span>,</span> <span>mutation</span><span>):</span>
        <span>retcode</span> <span>=</span> <span>pytest</span><span>.</span><span>main</span><span>([</span><span>"</span><span>tests/test_sorter.py</span><span>"</span><span>])</span>

    <span>assert</span> <span>retcode</span> <span>!=</span> <span>0</span><span>,</span> <span>"</span><span>Mutação deveria falhar</span><span>"</span>
from unittest.mock import patch from tests import sorter_mutations import pytest mutated_functions = [ sorter_mutations.no_exception_mutation, sorter_mutations.slice_input_mutation, ] @pytest.mark.parametrize("mutation", mutated_functions) def test_mutations_for_test_module(mutation): with patch("tests.test_sorter.sort_this_by", mutation): retcode = pytest.main(["tests/test_sorter.py"]) assert retcode != 0, "Mutação deveria falhar"

Enter fullscreen mode Exit fullscreen mode

Dado que não melhoramos o último exemplo de “teste da pessoa estudante”, ao executar python -m pytest teremos a saída semelhante a seguinte:

Os 2 PASSED indicados na imagem são:

  1. A própria função da pessoa estudante sem a mutação aplicada
  2. O teste com a mutação slice_input_mutation, que falhou como esperado na chamada pytest.main(["tests/test_sorter.py"])

E, como imaginávamos, a chamada pytest.main com a mutação no_exception_mutation retornou 0 e por isso nosso assert acusou um problema: “Mutação deveria falhar” (mas não falhou).

Melhorando a solução

Particularmente fiquei muito orgulhoso com essa solução! Mas há melhorias importante antes de chegarmos na versão disponibilizada para as turmas.

Ocultar logs excessivos

Quando executamos o Pytest internamente, ele se comporta de fato como uma nova execução, gerando todos os logs como esperado. Em nosso exemplo o terminal ficou poluído com logs equivalentes a 3 rodadas do Pytest, e isso não é bom para a experiência, além de confundir a pessoa estudante.

O Pytest possui algumas opções para reduzir a verbosidade de logs, mas sentimos que seria melhor ocultar completamente a saída das chamadas internas. Com 4 linhas podemos fazer a saída de um comando ser redirecionada para a “lixeira” /dev/null:

<span>+import contextlib +import os </span><span>from unittest.mock import patch from tests import sorter_mutations import pytest </span><span> </span><span>mutated_functions = [ </span> sorter_mutations.no_exception_mutation,
sorter_mutations.slice_input_mutation,
]
<span> </span>@pytest.mark.parametrize("mutation", mutated_functions)
<span>def test_mutations_for_test_module(mutation): </span> with patch("tests.test_sorter.sort_this_by", mutation):
<span>+ with open(os.devnull, "w") as student_output: + with contextlib.redirect_stdout(student_output): </span> retcode = pytest.main(["tests/test_sorter.py"])
assert retcode != 0, "Mutação deveria falhar"
<span> </span>
<span>+import contextlib +import os </span><span>from unittest.mock import patch from tests import sorter_mutations import pytest </span><span> </span><span>mutated_functions = [ </span>    sorter_mutations.no_exception_mutation,
    sorter_mutations.slice_input_mutation,
]
<span> </span>@pytest.mark.parametrize("mutation", mutated_functions)
<span>def test_mutations_for_test_module(mutation): </span>    with patch("tests.test_sorter.sort_this_by", mutation):
<span>+ with open(os.devnull, "w") as student_output: + with contextlib.redirect_stdout(student_output): </span>                 retcode = pytest.main(["tests/test_sorter.py"])
    assert retcode != 0, "Mutação deveria falhar"
<span> </span>
+import contextlib +import os from unittest.mock import patch from tests import sorter_mutations import pytest mutated_functions = [ sorter_mutations.no_exception_mutation, sorter_mutations.slice_input_mutation, ] @pytest.mark.parametrize("mutation", mutated_functions) def test_mutations_for_test_module(mutation): with patch("tests.test_sorter.sort_this_by", mutation): + with open(os.devnull, "w") as student_output: + with contextlib.redirect_stdout(student_output): retcode = pytest.main(["tests/test_sorter.py"]) assert retcode != 0, "Mutação deveria falhar"

Enter fullscreen mode Exit fullscreen mode

Mutações devem falhar and Original deve passar

Tivemos um desafio semelhante na solução anterior usando a fixture: além de garantir que as mutações devem falhar, devemos garantir que o teste “normal” ou “original” deve passar.

Aqui novamente a biblioteca pytest-dependency e o módulo inspect entram como grandes amigos! Coletamos todas as funções de teste no arquivo tests/test_sorter.py e as adicionamos como dependências para o novo teste test_mutations_for_test_module.

Uma função que coleta todos os nodeid‘s de testes funcionais para um arquivo pode ser escrita assim:

<span>import</span> <span>inspect</span>
<span>from</span> <span>pathlib</span> <span>import</span> <span>Path</span>
<span>def</span> <span>get_test_functions_from</span><span>(</span><span>student_test_module</span><span>):</span>
<span>test_file_path</span> <span>=</span> <span>Path</span><span>(</span><span>student_test_module</span><span>.</span><span>__file__</span><span>).</span><span>relative_to</span><span>(</span><span>Path</span><span>.</span><span>cwd</span><span>())</span>
<span>return</span> <span>[</span>
<span>test_file_path</span> <span>+</span> <span>"</span><span>::</span><span>"</span> <span>+</span> <span>member</span><span>[</span><span>0</span><span>]</span>
<span>for</span> <span>member</span> <span>in</span> <span>inspect</span><span>.</span><span>getmembers</span><span>(</span><span>student_test_module</span><span>)</span>
<span>if</span> <span>inspect</span><span>.</span><span>isfunction</span><span>(</span><span>member</span><span>[</span><span>1</span><span>])</span> <span>and</span> <span>member</span><span>[</span><span>0</span><span>].</span><span>startswith</span><span>(</span><span>"</span><span>test_</span><span>"</span><span>)</span>
<span>]</span>
<span>import</span> <span>inspect</span>
<span>from</span> <span>pathlib</span> <span>import</span> <span>Path</span>


<span>def</span> <span>get_test_functions_from</span><span>(</span><span>student_test_module</span><span>):</span>
    <span>test_file_path</span> <span>=</span> <span>Path</span><span>(</span><span>student_test_module</span><span>.</span><span>__file__</span><span>).</span><span>relative_to</span><span>(</span><span>Path</span><span>.</span><span>cwd</span><span>())</span>
    <span>return</span> <span>[</span>
        <span>test_file_path</span> <span>+</span> <span>"</span><span>::</span><span>"</span> <span>+</span> <span>member</span><span>[</span><span>0</span><span>]</span>
        <span>for</span> <span>member</span> <span>in</span> <span>inspect</span><span>.</span><span>getmembers</span><span>(</span><span>student_test_module</span><span>)</span>
        <span>if</span> <span>inspect</span><span>.</span><span>isfunction</span><span>(</span><span>member</span><span>[</span><span>1</span><span>])</span> <span>and</span> <span>member</span><span>[</span><span>0</span><span>].</span><span>startswith</span><span>(</span><span>"</span><span>test_</span><span>"</span><span>)</span>
    <span>]</span>
import inspect from pathlib import Path def get_test_functions_from(student_test_module): test_file_path = Path(student_test_module.__file__).relative_to(Path.cwd()) return [ test_file_path + "::" + member[0] for member in inspect.getmembers(student_test_module) if inspect.isfunction(member[1]) and member[0].startswith("test_") ]

Enter fullscreen mode Exit fullscreen mode

Nodeid é o identificador de um teste no Pytest, exigido pela pytest-dependency. É o mesmo texto que aparece antes de PASSED ou FAILED quando executamos a CLI com -vv. Exemplo:

Obs: Se a pessoa estudante utilizar parametrização em seus testes, essa função de coleta de nodeid‘s não será suficiente para a pytest-dependency funcionar corretamente. Por isso alteramos nosso fork novamente, garantindo a interpretação correta das dependências.

Nosso assert, nossas regras

Já que foi necessário criar um assert para garantir que o teste falhou para uma mutação, podemos aproveitar para criar uma mensagem de erro bem específica e didática. Porque só informar "Mutação deveria falhar" se podemos chegar em algo como "Seus testes em '{arquivo}' deveriam falhar com a mutação '{mutação}' definida em '{arquivo de mutações}', mas passaram. Confira essa dica: {dica específica da mutação}"?

Poderíamos ter um map/dicionário para definir a dica de cada mutação, mas escolhemos uma forma mais “preguiçosa”: a docstring da própria mutação. Um exemplo seria:

<span># ... </span>
<span>@pytest.mark.parametrize</span><span>(</span><span>"</span><span>mutation</span><span>"</span><span>,</span> <span>mutated_functions</span><span>)</span>
<span>def</span> <span>test_mutations_for_test_module</span><span>(</span><span>mutation</span><span>):</span>
<span>with</span> <span>patch</span><span>(</span><span>"</span><span>tests.test_sorter.sort_this_by</span><span>"</span><span>,</span> <span>mutation</span><span>):</span>
<span>with</span> <span>open</span><span>(</span><span>os</span><span>.</span><span>devnull</span><span>,</span> <span>"</span><span>w</span><span>"</span><span>)</span> <span>as</span> <span>student_output</span><span>:</span>
<span>with</span> <span>contextlib</span><span>.</span><span>redirect_stdout</span><span>(</span><span>student_output</span><span>):</span>
<span>retcode</span> <span>=</span> <span>pytest</span><span>.</span><span>main</span><span>([</span><span>"</span><span>tests/test_sorter.py</span><span>"</span><span>])</span>
<span>assert</span> <span>retcode</span> <span>!=</span> <span>0</span><span>,</span> <span>(</span>
<span>"</span><span>Seus testes em </span><span>'</span><span>tests/test_sorter.py</span><span>'</span><span> deveriam falhar com a mutação,</span><span>"</span>
<span>f</span><span>"</span><span> mas passaram. Confira essa dica: </span><span>{</span><span>mutation</span><span>.</span><span>__doc__</span><span>}</span><span>"</span>
<span>)</span>
<span># ... </span>
<span>@pytest.mark.parametrize</span><span>(</span><span>"</span><span>mutation</span><span>"</span><span>,</span> <span>mutated_functions</span><span>)</span>
<span>def</span> <span>test_mutations_for_test_module</span><span>(</span><span>mutation</span><span>):</span>
    <span>with</span> <span>patch</span><span>(</span><span>"</span><span>tests.test_sorter.sort_this_by</span><span>"</span><span>,</span> <span>mutation</span><span>):</span>
        <span>with</span> <span>open</span><span>(</span><span>os</span><span>.</span><span>devnull</span><span>,</span> <span>"</span><span>w</span><span>"</span><span>)</span> <span>as</span> <span>student_output</span><span>:</span>
            <span>with</span> <span>contextlib</span><span>.</span><span>redirect_stdout</span><span>(</span><span>student_output</span><span>):</span>
                <span>retcode</span> <span>=</span> <span>pytest</span><span>.</span><span>main</span><span>([</span><span>"</span><span>tests/test_sorter.py</span><span>"</span><span>])</span>
    <span>assert</span> <span>retcode</span> <span>!=</span> <span>0</span><span>,</span> <span>(</span>
        <span>"</span><span>Seus testes em </span><span>'</span><span>tests/test_sorter.py</span><span>'</span><span> deveriam falhar com a mutação,</span><span>"</span>
        <span>f</span><span>"</span><span> mas passaram. Confira essa dica: </span><span>{</span><span>mutation</span><span>.</span><span>__doc__</span><span>}</span><span>"</span>
    <span>)</span>
# ... @pytest.mark.parametrize("mutation", mutated_functions) def test_mutations_for_test_module(mutation): with patch("tests.test_sorter.sort_this_by", mutation): with open(os.devnull, "w") as student_output: with contextlib.redirect_stdout(student_output): retcode = pytest.main(["tests/test_sorter.py"]) assert retcode != 0, ( "Seus testes em 'tests/test_sorter.py' deveriam falhar com a mutação," f" mas passaram. Confira essa dica: {mutation.__doc__}" )

Enter fullscreen mode Exit fullscreen mode

Primeiro a bagunça, depois a arrumação

Ufa… Muito código (e nem mostrei tudo) mas chegamos lá!

E nesse momento vem a dor de olhar todo aquele código criativo, mas ainda bagunçado. Só de olhar o “resultado final” já quero fugir de manutenções futuras, ainda mais pensando em múltiplos projetos e múltiplas turmas.

Por isso, aquela boa e velha refatoração sempre cai bem. Criando uma classe e algumas funções para isolar responsabilidades, temos um resultado mais palatável:

<span>from</span> <span>unittest.mock</span> <span>import</span> <span>patch</span>
<span>import</span> <span>pytest</span>
<span># Aproveitei nosso fork da pytest-dependency para # posicionar as funções de apoio </span><span>from</span> <span>pytest_dependency</span> <span>import</span> <span>(</span>
<span>assert_fails_with_mutation</span><span>,</span>
<span>get_skip_markers</span><span>,</span>
<span>get_test_assessment_configs</span><span>,</span>
<span>run_pytest_quietly</span><span>,</span>
<span>)</span>
<span>from</span> <span>src.sorter</span> <span>import</span> <span>sort_this_by</span>
<span>from</span> <span>tests</span> <span>import</span> <span>test_sorter</span><span>,</span> <span>sorter_mutations</span>
<span># TA_CFG será um objeto para guardar dados que queremos obter facilmente </span><span>TA_CFG</span> <span>=</span> <span>get_test_assessment_configs</span><span>(</span>
<span>target_asset</span><span>=</span><span>sort_this_by</span><span>,</span>
<span>mutations_module</span><span>=</span><span>sorter_mutations</span><span>,</span>
<span>student_test_module</span><span>=</span><span>test_sorter</span><span>,</span>
<span>)</span>
<span># Com essa configuração garantimos que só testaremos as mutações # caso a pessoa estudante tenha feito testes que passam. </span><span>pytestmark</span> <span>=</span> <span>get_skip_markers</span><span>(</span><span>TA_CFG</span><span>)</span>
<span>@pytest.mark.parametrize</span><span>(</span><span>"</span><span>mutation</span><span>"</span><span>,</span> <span>TA_CFG</span><span>.</span><span>MUTATED_FUNCTIONS</span><span>)</span>
<span>def</span> <span>test_mutations_for_test_module</span><span>(</span><span>mutation</span><span>):</span>
<span>with</span> <span>patch</span><span>(</span><span>TA_CFG</span><span>.</span><span>PATCH_TARGET</span><span>,</span> <span>mutation</span><span>):</span>
<span>return_code</span> <span>=</span> <span>run_pytest_quietly</span><span>([</span><span>TA_CFG</span><span>.</span><span>STUDENT_TEST_FILE_PATH</span><span>])</span>
<span>assert_fails_with_mutation</span><span>(</span><span>mutation</span><span>,</span> <span>return_code</span><span>,</span> <span>TA_CFG</span><span>)</span>
<span>from</span> <span>unittest.mock</span> <span>import</span> <span>patch</span>

<span>import</span> <span>pytest</span>

<span># Aproveitei nosso fork da pytest-dependency para # posicionar as funções de apoio </span><span>from</span> <span>pytest_dependency</span> <span>import</span> <span>(</span>
    <span>assert_fails_with_mutation</span><span>,</span>
    <span>get_skip_markers</span><span>,</span>
    <span>get_test_assessment_configs</span><span>,</span>
    <span>run_pytest_quietly</span><span>,</span>
<span>)</span>

<span>from</span> <span>src.sorter</span> <span>import</span> <span>sort_this_by</span>
<span>from</span> <span>tests</span> <span>import</span> <span>test_sorter</span><span>,</span> <span>sorter_mutations</span>

<span># TA_CFG será um objeto para guardar dados que queremos obter facilmente </span><span>TA_CFG</span> <span>=</span> <span>get_test_assessment_configs</span><span>(</span>
    <span>target_asset</span><span>=</span><span>sort_this_by</span><span>,</span>
    <span>mutations_module</span><span>=</span><span>sorter_mutations</span><span>,</span>
    <span>student_test_module</span><span>=</span><span>test_sorter</span><span>,</span>
<span>)</span>

<span># Com essa configuração garantimos que só testaremos as mutações # caso a pessoa estudante tenha feito testes que passam. </span><span>pytestmark</span> <span>=</span> <span>get_skip_markers</span><span>(</span><span>TA_CFG</span><span>)</span>


<span>@pytest.mark.parametrize</span><span>(</span><span>"</span><span>mutation</span><span>"</span><span>,</span> <span>TA_CFG</span><span>.</span><span>MUTATED_FUNCTIONS</span><span>)</span>
<span>def</span> <span>test_mutations_for_test_module</span><span>(</span><span>mutation</span><span>):</span>
    <span>with</span> <span>patch</span><span>(</span><span>TA_CFG</span><span>.</span><span>PATCH_TARGET</span><span>,</span> <span>mutation</span><span>):</span>
        <span>return_code</span> <span>=</span> <span>run_pytest_quietly</span><span>([</span><span>TA_CFG</span><span>.</span><span>STUDENT_TEST_FILE_PATH</span><span>])</span>

    <span>assert_fails_with_mutation</span><span>(</span><span>mutation</span><span>,</span> <span>return_code</span><span>,</span> <span>TA_CFG</span><span>)</span>
from unittest.mock import patch import pytest # Aproveitei nosso fork da pytest-dependency para # posicionar as funções de apoio from pytest_dependency import ( assert_fails_with_mutation, get_skip_markers, get_test_assessment_configs, run_pytest_quietly, ) from src.sorter import sort_this_by from tests import test_sorter, sorter_mutations # TA_CFG será um objeto para guardar dados que queremos obter facilmente TA_CFG = get_test_assessment_configs( target_asset=sort_this_by, mutations_module=sorter_mutations, student_test_module=test_sorter, ) # Com essa configuração garantimos que só testaremos as mutações # caso a pessoa estudante tenha feito testes que passam. pytestmark = get_skip_markers(TA_CFG) @pytest.mark.parametrize("mutation", TA_CFG.MUTATED_FUNCTIONS) def test_mutations_for_test_module(mutation): with patch(TA_CFG.PATCH_TARGET, mutation): return_code = run_pytest_quietly([TA_CFG.STUDENT_TEST_FILE_PATH]) assert_fails_with_mutation(mutation, return_code, TA_CFG)

Enter fullscreen mode Exit fullscreen mode

Muitas alegrias, até que…

Esse formato funcionou muito bem para nossos projetos sobre POO, Raspagem de Dados, Algoritmos e Estruturas de Dados, Flask… até que começamos a ensinar Django.

Django é um framework incrível, mas ele abstrai muitos detalhes de implementação. Isso acontece principalmente em relação a comunicação com o banco de dados, e é mais “agravante” quando testes acessam o banco.

Tentamos bastante até entender que não seria viável, com a solução descrita nesse artigo, testar testes que precisavam acessar o banco de dados de uma aplicação Django. Precisávamos voltar ao passo da pesquisa, com boas doses de ousadia, criatividade e paciência.

Vou ser sincero aqui: ainda não tenho a resposta final! Fizemos uma prova de conceito com hooks do Pytest que parece promissora, mas ainda não chegamos lá.

Por isso, o próximo artigo pode demorar um pouco a sair, mas já estou ansioso para esse momento!

python-testing-tests (3 Part Series)

1 Testando testes no Python – Parte 1: motivos e alternativas
2 Testando testes no Python – Parte 2: fixtures parametrizadas
3 Testando testes no Python – Parte 3: Pytest dentro do Pytest

原文链接:Testando testes no Python – Parte 3: Pytest dentro do Pytest

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
Stop cheating on your future with your past... it's over.
别再用你的过去欺骗你的未来,过去已经过去了
评论 抢沙发

请登录后发表评论

    暂无评论内容