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

Testando testes no Python - Parte 2: fixtures parametrizadas

Posted on Oct 9 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 2º 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, contei sobre o motivo de fazermos testes de testes em projetos dos cursos da Trybe, e resumi 3 principais alternativas e seus respectivos prós e contras.Para o nosso cenário entendemos que o ideal (considerando todas as variáveis e limitações) era termos o teste de mutações customizadas.Então chegou hora da verdade: a ideia era bonita mas precisava funcionar. E aí é que entram as habilidades do Capi, que ficou responsável pela implementação.Na época, Capi era a pessoa mais sênior do time e não havia ninguém melhor para ser o pai (ou 'py'?) da criação. Ele gentilmente me concedeu a honra de escrever sobre a solução, e ainda revisar antes da publicação. 💜Então aqui vou explicar como a solução funciona mas, se você quer saber melhor como foi o processo criativo para chegar lá, já sabe quem procurar! 😉A primeira versão surgiu em um contexto que, durante um dos projetos da nossa formação, a pessoa estudante precisava implementar testes para uma função chamada sort_this_by que ordena uma lista de dicionários (dicts). Logo, precisávamos testar os testes de cada estudante contra mutações dessa função de ordenação.Os dicionários possuíam o mesmo conjunto de 6 chaves (como o exemplo a seguir) mas o critério de ordenação poderia ser apenas 3 dessas chaves. Além disso: ao selecionar a chave "min_score" como critério, a ordenação deveria ser crescente (e decrescente caso contrário); e uma exceção específica era levantada caso o parâmetro criteria não fosse um dos 3 valores válidos.Os detalhes da função sort_this_by não vão fazer diferença na explicação, mas é importante entender que havia um pouco de complexidade e casos de testes a serem cobertos. Para termos um código funcional sem complicar demais, considere a seguinte implementação no arquivo src/sorter.py:E um possível teste implementado por uma pessoa estudante em tests/test_sorter.py:Como precisamos executar os testes da pessoa estudante "trocando" a função sort_this_by por uma mutação, precisamos criar essas mutações. Posso criar quantas e quais mutações desejar, mas isso sempre vai depender da complexidade da função original. Se a função original possui 5 code-paths (ifs, raises, excepts, returns, etc), provavelmente terei aproximadamente 5 mutações.Penso em algumas mutações que forçariam a pessoa a exercitar certas habilidades de testes, e crio elas em um arquivo dedicado a isso. Em muitos casos, inclusive aproveitamos a função original para não precisar reescrever a lógica completa:Agora, preciso que o teste test_sort_this_by seja executado uma vez para cada uma dessas mutações.Fixtures são a forma que o Pytest oferece para evitarmos duplicação de código em nossos testes, principalmente pensando em setup e teardown de recursos. Além da documentação, essa live do @dunossauro também é uma ótima opção para aprofundar no tema 😉O Pytest já oferece uma forma de executar uma mesma função de teste para múltiplos valores: o marcador parametrize.Entretanto, utilizar o marcador em nosso caso não é boa alternativa por 2 motivos. O 1º deles é simples de explicar: seria necessário inserir o marcador no arquivo da pessoa estudante. Isso aumenta a chance de erros, além de expor detalhes da nossa implementação com a qual o cliente (estudante) não deveria se preocupar. O 2º motivo só fará sentido em breve, então vou me permitir adiar essa explicação. 😅Felizmente o Pytest também nos permite criar fixtures parametrizadas, que vão ser uma "mão na roda" nesse caso. Se inserirmos essa fixture no conftest.py, e ainda marcá-la como autouse para que ela seja aplicada automaticamente para todos os testes em seu contexto, conseguimos o que não tínhamos com o marcador parametrize. Então vamos à implementação do nosso tests/conftest.py:Executando os testes, teremos a seguinte saída:Uhuuul! ✅ Tudo passou! 🌟 Mas espera... 🤔 Como são mutações, os testes deveriam falhar não é mesmo? Na verdade, não estamos fazendo nada com as mutações além de um simples print. Precisamos alterar nossa fixture para que ela faça a "mágica" que precisamos. 🪄 A "mágica" é um simples patch! Assim como, durante testes de aplicações, podemos fazer o patch de uma dependência, podemos fazer isso dentro do próprio teste. Genial, Capi! 💜A alteração no tests/conftest.py então será:Se executamos o teste novamente, temos o seguinte resultado:Ou seja: o teste da "pessoa estudante" foi resistente (falhou) em uma mutação, mas não na outra. Para que o teste possa ser considerado robusto, ambas as mutações deveriam causar FAIL ❌.Ainda temos ajustes a fazer, mas aqui chegamos na nossa grande vitória: temos nosso próprio teste de mutações customizadas! 🎉E agora posso retomar ao 2º motivo que faria o marcador parametrize ser ruim para nosso caso: já imaginou como seria encaixar a funcionalidade do patch lá dentro? 😬Como estamos falando de um teste que será usado para pontuar estudantes de forma automatizada, precisamos de 2 ajustes principais:1️⃣ O teste da pessoa estudante precisa passar com a função original;2️⃣ Devemos transformar o conjunto de todos FAIL ❌ das mutações em um PASS ✅, para controlar se a pessoa estudante deve receber a pontuação ou não.O ponto 1️⃣ é resolvido adicionando a função sort_this_by aos parâmetros da fixture validate_mutations (que poderia mudar de nome):Para o item 2️⃣ usamos o marcador XFAIL 🟨 do Pytest junto com os poderes do plugin pytest-dependency: marcamos os testes das mutações como XFAIL 🟨 e como dependência para o teste da função original. Ou seja: o teste da função original só será executado se os testes de mutação falharem.Mas aqui surgiu um empecilho: o plugin pytest-dependency não entende XFAIL 🟨 como um resultado de sucesso. Então foi necessário criar um Fork do repositório com a melhoria (Capi até abriu um PR, mas até hoje não foi revisado pelo time do pytest-dependency).Para usar essa opção, basta adicionar a linha accept_xfail = true no arquivo de configurações do Pytest (pytest.ini ou pyproject.toml),Assim, nosso tests/conftest.py fica semelhante ao seguinte:Sim, eu sei. Muito complexo para ler, e ainda mais para dar manutenção. Imagine se eu quiser criar mais uma função de mutação! Ou até mesmo se quiser trocar o nome de uma mutação, ou da função original... E lembre-se que a intenção é usar isso em vários projetos diferentes.Por isso investimos um pouco mais de energia em uma refatoração, extraindo toda a construção da mutated_functions para uma função que recebe apenas 3 parâmetros:Não vale a pena detalhar essa nova função aqui porque estaria fugindo do foco, mas preciso dizer que o módulo inspect foi uma grande ajuda!Esse modelo de teste de teste funcionou bem por muito tempo, em diversos projetos! E além de funções, funciona muito bem para mutações de classes.Mas de todas as limitações, uma precisava ser resolvida com mais urgência: não queremos limitar que estudantes façam todos os testes de uma função (e principalmente de uma classe) em apenas 1 função de teste.Precisávamos de uma forma para dar essa autonomia para a pessoa estudante, estimulando-a a escrever testes mais organizados com as ferramentas que desejar. No próximo artigo vou te contar como fizemos isso, mas adoraria saber: como você abordaria esse novo problema?Nos vemos lemos em breve! 👋Templates let you quickly answer FAQs or store snippets for re-use. 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 Mary - Sep 18 Lorena GM - Oct 2 Vincent A. Cicirello - Sep 8 Fran Borges - Oct 2 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 2: fixtures parametrizadas

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×