No campo de teste automático, você pode encontrar ferramentas diferentes, por exemplo, py.test é uma das soluções mais populares para escrever testes automáticos em Python.
Depois de passar por muitos recursos relacionados ao pytest e ter estudado a documentação no site oficial do projeto, não consegui encontrar uma descrição direta da solução para uma das principais tarefas - executar testes com dados de teste armazenados em um arquivo separado. Caso contrário, pode-se dizer, o carregamento de parâmetros nas funções de teste do (s) arquivo (s) ou a parametrização do arquivo diretamente. Esse procedimento não é descrito em nenhum lugar nos meandros e a única menção desse recurso está em apenas uma linha da documentação do pytest.
Neste artigo, falarei sobre minha solução para esse problema.
Desafio
A tarefa principal é gerar casos de teste na forma dos parâmetros test_input
em cada função de teste individual a partir dos nomes de funções de arquivo correspondentes.
Tarefas adicionais:
- escolha formatação legível por humanos de arquivos com casos de teste;
- deixe a capacidade de suportar casos de teste codificados;
- exibir identificadores claros para cada caso.
Toolkit
No artigo, eu uso o Python 3 (2.7 também é adequado), pyyaml e pytest
(versões 5+ para Python 3 ou 4.6 para Python 2.7) sem usar plug-ins de terceiros. Além disso, a biblioteca padrão do sistema os
será usada.
O próprio arquivo a partir do qual realizaremos os casos de teste precisa ser estruturado usando uma linguagem de marcação que seja conveniente para uma pessoa entender. No meu caso, o YAML foi escolhido (porque resolve a tarefa adicional de escolher um formato legível por humanos) . De fato, que tipo de linguagem de marcação para arquivos com conjuntos de dados que você precisa depende dos requisitos apresentados no projeto.
Implementação
Como o principal pilar do universo em programação é o acordo, teremos que introduzir vários deles para nossa solução.
Interceptação
Para começar, esta solução usa a função de interceptação pytest_generate_tests
( wiki ), que começa no estágio de geração de casos de teste, e seu argumento metafunc
, que nos permite parametrizar a função. Neste ponto, o pytest itera sobre cada função de teste e executa o código de geração subsequente para ela.
Argumentos
Você deve definir uma lista exaustiva de parâmetros para funções de teste. No meu caso, o dicionário é test_input
e qualquer tipo de dado (na maioria das vezes uma string ou um número inteiro) no resultado expected_result
. Precisamos desses parâmetros para uso em metafunc.parametrize(...)
.
Parametrização
Essa função repete completamente a operação do parâmetro @pytest.mark.parametrize
, que usa como primeiro argumento uma string que lista os argumentos da função de teste (no nosso caso "test_input, expected_result"
) e uma lista de dados pelos quais iterará para criar nossos casos de teste (por exemplo, [(1, 2), (2, 4), (3, 6)]
).
Na batalha, ficará assim:
@pytest.mark.parametrize("test_input, expected_result", [(1, 2), (2, 4), (3, 6)]) def test_multiplication(test_input, expected_result): assert test_input * 2 == expected_result
E no nosso caso, indicaremos isso com antecedência:
Filtragem
A partir daqui também segue a alocação dessas funções de teste onde os dados de um arquivo são necessários, daqueles que usam dados estáticos / dinâmicos. Aplicaremos essa filtragem antes de analisar as informações do arquivo.
Os próprios filtros podem ser quaisquer, por exemplo:
- Marcador de função chamado
yaml
:
Caso contrário, o mesmo filtro pode ser implementado assim:
- O argumento para a função
test_input
:
Esta opção me convinha mais.
Resultado
Precisamos adicionar apenas a parte em que analisamos os dados do arquivo. Isso não será difícil no caso do yaml (assim como no json, xml etc.) , portanto, coletamos tudo no heap.
Nós escrevemos um script de teste como este:
Um arquivo de dados:
# test_multiplication.yaml - !!python/tuple [1,2] - !!python/tuple [1,3] - !!python/tuple [1,5] - !!python/tuple [2,4] - !!python/tuple [3,4] - !!python/tuple [5,4]
Temos a seguinte lista de casos de teste:
pytest /test_script.py --collect-only ======================== test session starts ======================== platform linux -- Python 3.7.4, pytest-5.2.1, py-1.8.0, pluggy-0.13.0 rootdir: /pytest_habr collected 6 items <Module test_script.py> <Function test_multiplication[1-2]> <Function test_multiplication[1-3]> <Function test_multiplication[1-5]> <Function test_multiplication[2-4]> <Function test_multiplication[3-4]> <Function test_multiplication[5-4]> ======================== no tests ran in 0.04s ========================
E executando o script, este resultado: 4 failed, 2 passed, 1 warnings in 0.11s
Adicionar. atribuições
Isso pode terminar o artigo, mas, por uma questão de complexidade, adicionarei identificadores mais convenientes à nossa função, outra análise de dados e marcação de cada caso de teste individual.
Então, imediatamente, o código:
Assim, alteramos a aparência do nosso arquivo YAML:
# test_multiplication.yaml - test_data: [1, 2] id: 'one_two' - test_data: [1,3] marks: ['xfail'] - test_data: [1,5] marks: ['skip'] - test_data: [2,4] id: "it's good" marks: ['xfail'] - test_data: [3,4] marks: ['negative'] - test_data: [5,4] marks: ['more_than']
A descrição mudará para:
<Module test_script.py> <Function test_multiplication[one_two]> <Function test_multiplication[1_3]> <Function test_multiplication[1_5]> <Function test_multiplication[it's good]> <Function test_multiplication[3_4]> <Function test_multiplication[5_4]>
E o lançamento será: 2 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 2 warnings in 0.12s
PS: avisos - porque marcadores auto-escritos não são registrados no pytest.ini
No desenvolvimento do tópico
Pronto para discutir nos comentários perguntas sobre o tipo:
- qual é a melhor maneira de escrever um arquivo yaml?
- Em que formato é mais conveniente armazenar dados de teste?
- Que caso de teste adicional é necessário no estágio de geração?
- Preciso de identificadores para cada caso?