Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

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

Posted on Oct 13 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.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.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. 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: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:Um retcode igual a 0 (zero) significa que os testes passaram, falharam caso contrário. 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.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: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: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).Particularmente fiquei muito orgulhoso com essa solução! 💜 Mas há melhorias importante antes de chegarmos na versão disponibilizada para as turmas.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: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: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.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: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: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!Templates let you quickly answer FAQs or store snippets for re-use.Google pay 390$ reliably my last paycheck was $55000 working 10 hours out of consistently on the web.(yt) My increasingly youthful kinfolk mate has been averaging 20k all through continuous months and he works around 24 hours reliably. I can’t trust how direct it was once I attempted it out. This is my essential concern…:) GOOD LUCKFor more info visit……… >>> Smartcash1.com Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well Confirm For further actions, you may consider blocking this person and/or reporting abuse Fady GA 😎 - Oct 13 Allbetter app - Oct 13 Angry Incident Commander - Oct 13 Mutchembe Bamo Christine - Oct 13 Once suspended, vbuxbaum will not be able to comment or publish posts until their suspension is removed. Once unsuspended, vbuxbaum will be able to comment and publish posts again. Once unpublished, all posts by vbuxbaum will become hidden and only accessible to themselves. If vbuxbaum is not suspended, they can still re-publish their posts from their dashboard. Note: Once unpublished, this post will become invisible to the public and only accessible to Vitor Buxbaum Orlandi. They can still re-publish the post if they are not suspended. Thanks for keeping DEV Community safe. Here is what you can do to flag vbuxbaum: vbuxbaum consistently posts content that violates DEV Community's code of conduct because it is harassing, offensive or spammy. Unflagging vbuxbaum will restore default visibility to their posts. DEV Community — A constructive and inclusive social network for software developers. With you every step of your journey. Built on Forem — the open source software that powers DEV and other inclusive communities.Made with love and Ruby on Rails. DEV Community © 2016 - 2023. We're a place where coders share, stay up-to-date and grow their careers.



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

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

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×