<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Vitor Buxbaum Orlandi</title>
    <description>The latest articles on Forem by Vitor Buxbaum Orlandi (@vbuxbaum).</description>
    <link>https://forem.com/vbuxbaum</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F570281%2Fa666f67e-92e4-41c7-ba3d-95bad7c5fc66.png</url>
      <title>Forem: Vitor Buxbaum Orlandi</title>
      <link>https://forem.com/vbuxbaum</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vbuxbaum"/>
    <language>en</language>
    <item>
      <title>Entendendo @decorators no Python em 6 passos</title>
      <dc:creator>Vitor Buxbaum Orlandi</dc:creator>
      <pubDate>Tue, 14 Nov 2023 21:58:02 +0000</pubDate>
      <link>https://forem.com/vbuxbaum/entendendo-decorators-no-python-em-6-passos-4g5k</link>
      <guid>https://forem.com/vbuxbaum/entendendo-decorators-no-python-em-6-passos-4g5k</guid>
      <description>&lt;h2&gt;
  
  
  Decorators: um resumo
&lt;/h2&gt;

&lt;p&gt;Quando falamos de &lt;em&gt;decorators&lt;/em&gt; no Python, nos referimos à seguinte sintaxe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@meu_decorator&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;minha_funcao&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja: eu tenho uma função (mas poderia ser um método ou uma classe) chamada &lt;code&gt;minha_funcao&lt;/code&gt; que está 'decorada' pelo &lt;em&gt;decorator&lt;/em&gt; &lt;code&gt;meu_decorator&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;É comum ver essa sintaxe em diversas bibliotecas, como Flask e FastAPI (ao criar rotas), Pytest (ao criar &lt;em&gt;fixtures&lt;/em&gt;), Dataclass (para definir uma &lt;em&gt;dataclass&lt;/em&gt;) etc. Mas também há &lt;em&gt;decorators&lt;/em&gt; "nativos", como &lt;code&gt;@classmethod&lt;/code&gt; e &lt;code&gt;@staticmethod&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Esse artigo é para você que já &lt;strong&gt;usou&lt;/strong&gt; &lt;em&gt;decorators&lt;/em&gt; em algum cenário, mas não sabe como poderia &lt;strong&gt;criar&lt;/strong&gt; um por conta própria. &lt;/p&gt;

&lt;p&gt;Entender a estrutura de um &lt;em&gt;decorator&lt;/em&gt; pode ser uma tarefa complexa, mas irei dividir em passos mais simples de entender. Vou começar pegando leve e depois pode ficar mais pesado, mas você consegue! Vamos lá? 💜&lt;/p&gt;

&lt;h2&gt;
  
  
  Passo-a-passo
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Passo 0: Crie uma função, e execute-a
&lt;/h3&gt;

&lt;p&gt;Bem simples né? Vou adicionar alguns &lt;code&gt;prints&lt;/code&gt; especiais que vão facilitar o entendimento do fluxo 😉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Iniciando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Finalizando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Começando tudo]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meu querido parâmetro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Passo 1: atribuir a função a outro nome/variável
&lt;/h3&gt;

&lt;p&gt;Caso você não saiba, funções também são objetos no Python! Elas possuem tipo (&lt;code&gt;&amp;lt;class 'function'&amp;gt;&lt;/code&gt;) e atributos, e podemos "guardá-las" em outras variáveis. &lt;/p&gt;

&lt;p&gt;Para esse passo, ficamos assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Iniciando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Finalizando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Começando tudo]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;other_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;my_function&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 1
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Troca de nome realizada]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 1
&lt;/span&gt;&lt;span class="nf"&gt;other_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meu querido parâmetro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja: &lt;code&gt;other_name&lt;/code&gt; guarda &lt;code&gt;my_function&lt;/code&gt;, então se eu chamar &lt;code&gt;other_name&lt;/code&gt; na verdade estarei executando &lt;code&gt;my_function&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passo 2: criar uma função que retorna outra
&lt;/h3&gt;

&lt;p&gt;Como funções são objetos, eu posso usar uma função para retornar outra:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Iniciando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Finalizando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Passo 2
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; Iniciando get_function()&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; Finalizando get_function()&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;my_function&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Começando tudo]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;other_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 2
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Troca de nome realizada]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;other_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meu querido parâmetro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repare que &lt;code&gt;get_function&lt;/code&gt; não retorna o &lt;em&gt;resultado de &lt;code&gt;my_function&lt;/code&gt;&lt;/em&gt;, mas &lt;strong&gt;a função &lt;code&gt;my_function&lt;/code&gt; em si&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Executando o código que temos até agora, a saída será:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Começando tudo]
&amp;gt; Iniciando get_function()
&amp;gt; Finalizando get_function()
[Troca de nome realizada]
&amp;gt;&amp;gt; Iniciando my_function(meu querido parâmetro)
&amp;gt;&amp;gt; Finalizando my_function(meu querido parâmetro)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Passo 3: declarar uma função dentro da outra (função "mãe")
&lt;/h3&gt;

&lt;p&gt;Aqui é uma refatoração relativamente simples, mas é essencial para garantirmos o comportamento do &lt;em&gt;decorator&lt;/em&gt;: vamos deslocar a declaração de &lt;code&gt;my_function&lt;/code&gt; para &lt;em&gt;dentro&lt;/em&gt; de &lt;code&gt;get_function&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; Iniciando get_function()&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 3
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Iniciando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 3
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Finalizando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 3
&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; Finalizando get_function()&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;my_function&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Começando tudo]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;other_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Troca de nome realizada]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;other_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meu querido parâmetro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Em termos de comportamento, nada vai mudar e a saída no terminal continuará a mesma. A diferença é que agora &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;não é mais possível acessar &lt;code&gt;my_function&lt;/code&gt; diretamente pelo escopo global&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;my_function&lt;/code&gt; compartilha do escopo de &lt;code&gt;get_function&lt;/code&gt; (como veremos a seguir)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Passo 4: Compartilhar parâmetro da "mãe" com execução da "filha"
&lt;/h3&gt;

&lt;p&gt;Se seus neurônios ainda não tinham fritado, provavelmente chegou a sua hora 😅&lt;/p&gt;

&lt;p&gt;Eis o que vamos fazer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;renomear &lt;code&gt;get_function&lt;/code&gt; para &lt;code&gt;mother_function&lt;/code&gt; (só pra facilitar as coisas)&lt;/li&gt;
&lt;li&gt;adicionar um parâmetro em &lt;code&gt;mother_function&lt;/code&gt; chamado &lt;code&gt;mother_param&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;fazer &lt;code&gt;my_function&lt;/code&gt; acessar &lt;code&gt;mother_param&lt;/code&gt; (ilustrando com um &lt;em&gt;print&lt;/em&gt;)
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mother_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mother_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 4
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; Iniciando mother_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mother_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 4
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Iniciando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Tenho acesso a (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mother_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 4
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Finalizando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; Finalizando mother_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mother_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 4
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;my_function&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Começando tudo]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;other_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mother_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parâmetro materno&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 4
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Troca de nome realizada]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;other_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meu querido parâmetro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja: &lt;code&gt;my_function&lt;/code&gt; consegue acessar variáveis no escopo de &lt;code&gt;mother_function&lt;/code&gt;! Incrível, né??&lt;/p&gt;

&lt;p&gt;Executando o código que temos até agora, a saída será:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Começando tudo]
&amp;gt; Iniciando mother_function(parâmetro materno)
&amp;gt; Finalizando mother_function(parâmetro materno)
[Troca de nome realizada]
&amp;gt;&amp;gt; Iniciando my_function(meu querido parâmetro)
&amp;gt;&amp;gt; Tenho acesso a (parâmetro materno)!
&amp;gt;&amp;gt; Finalizando my_function(meu querido parâmetro)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Passo 5: passar uma nova função como parâmetro para a "mãe"
&lt;/h3&gt;

&lt;p&gt;Agora vem o "pulo-do-gato": vamos tirar proveito do fato que funções são objetos, e passar uma &lt;strong&gt;função&lt;/strong&gt; (ao invés de uma simples &lt;em&gt;string&lt;/em&gt;) como parâmetro para &lt;code&gt;mother_function&lt;/code&gt;. Dentro de &lt;code&gt;my_function&lt;/code&gt; então poderei chamar essa nova função, manipulando (&lt;em&gt;decorando&lt;/em&gt; 👀) como eu desejar.&lt;/p&gt;

&lt;p&gt;Então agora vou: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;criar uma nova função &lt;code&gt;final_function&lt;/code&gt; e passá-la como &lt;code&gt;mother_param&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;fazer &lt;code&gt;my_function&lt;/code&gt; &lt;strong&gt;chamar&lt;/strong&gt; &lt;code&gt;mother_param&lt;/code&gt; (que será &lt;code&gt;final_function&lt;/code&gt;) passando &lt;code&gt;my_param&lt;/code&gt; como parâmetro&lt;/li&gt;
&lt;li&gt;e por fim, exibir o retorno da chamada de &lt;code&gt;other_function&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mother_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mother_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; Iniciando mother_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mother_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Iniciando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mother_param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 5
&lt;/span&gt;        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt; Finalizando my_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;my_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 5
&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt; Finalizando mother_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mother_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;my_function&lt;/span&gt;


&lt;span class="c1"&gt;# Passo 5
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;final_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt;&amp;gt; Executando final_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RESULTADO FINAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Começando tudo]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;other_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mother_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_function&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Troca de nome realizada]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;other_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meu querido parâmetro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt; &lt;span class="c1"&gt;# Passo 5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executando esse código, a saída fica assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Começando tudo]
&amp;gt; Iniciando mother_function(&amp;lt;function 'final_function'&amp;gt;)
&amp;gt; Finalizando mother_function(&amp;lt;function 'final_function'&amp;gt;)
[Troca de nome realizada]
&amp;gt;&amp;gt; Iniciando my_function(meu querido parâmetro)
&amp;gt;&amp;gt;&amp;gt; Executando final_function(meu querido parâmetro)
&amp;gt;&amp;gt; Finalizando my_function(meu querido parâmetro)
RESULTADO FINAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Antes de seguir para o último passo
&lt;/h4&gt;

&lt;p&gt;Nesse momento &lt;strong&gt;já temos o comportamento "cru" do &lt;em&gt;decorator&lt;/em&gt;&lt;/strong&gt;: uma função está sendo decorada por outra. 💅&lt;/p&gt;

&lt;p&gt;Podemos afirmar isso porque quando fazemos &lt;code&gt;other_name = mother_function(final_function)&lt;/code&gt;, estamos usando &lt;code&gt;mother_function&lt;/code&gt; para decorar &lt;code&gt;final_function&lt;/code&gt;! Podemos dizer que &lt;code&gt;other_function&lt;/code&gt; é a versão &lt;strong&gt;decorada&lt;/strong&gt; de &lt;code&gt;final_function&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nesse caso é uma decoração simples (&lt;em&gt;prints&lt;/em&gt; informando que a execução está iniciando/finalizando), mas ao final mostrarei um exemplo mais aplicável 😉&lt;/p&gt;

&lt;h3&gt;
  
  
  Passo 6: simplificando para a sintaxe com &lt;code&gt;@&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Agora que temos nosso &lt;em&gt;decorator&lt;/em&gt; funcionando, só precisamos usar a famosa sintaxe com &lt;code&gt;@&lt;/code&gt;. Nosso código vai ficar assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mother_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mother_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Nada muda aqui, escondi apenas para ajudar na leitura ;)
&lt;/span&gt;    &lt;span class="p"&gt;...&lt;/span&gt;  


&lt;span class="nd"&gt;@mother_function&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 6
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;final_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final_param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt;&amp;gt; Executando final_function(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;final_param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RESULTADO FINAL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[Começando tudo]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;final_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meu querido parâmetro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Passo 6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Removi o &lt;em&gt;print&lt;/em&gt; &lt;code&gt;[Troca de nome realizada]&lt;/code&gt; porque esse passo é feito implicitamente. Antes usávamos &lt;code&gt;other_name&lt;/code&gt; para chamar a função decorada, mas agora &lt;code&gt;final_function&lt;/code&gt; já guarda a versão decorada da função. &lt;/p&gt;

&lt;p&gt;Isso significa que uma chamada para &lt;code&gt;final_function&lt;/code&gt; na verdade executará &lt;code&gt;my_function&lt;/code&gt;, e teremos a seguinte saída:&lt;/p&gt;

&lt;p&gt;Executando o novo código, a saída fica assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; Iniciando mother_function(&amp;lt;function 'final_function'&amp;gt;)
&amp;gt; Finalizando mother_function(&amp;lt;function 'final_function'&amp;gt;)
[Começando tudo]
&amp;gt;&amp;gt; Iniciando my_function(meu querido parâmetro)
&amp;gt;&amp;gt;&amp;gt; Executando final_function(meu querido parâmetro)
&amp;gt;&amp;gt; Finalizando my_function(meu querido parâmetro)
RESULTADO FINAL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma diferença interessante aqui: &lt;code&gt;[Começando tudo]&lt;/code&gt; agora aparece depois do print de &lt;code&gt;mother_function&lt;/code&gt;, já que ela foi processada antes (quando usamos &lt;code&gt;@mother_function&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Um exemplo mais divertido (e útil)
&lt;/h2&gt;

&lt;p&gt;Para finalizar, vamos ver mais um exemplo para garantir que ficou nítido?! 🤩&lt;/p&gt;

&lt;p&gt;Vamos fazer um &lt;em&gt;decorator&lt;/em&gt; chamado &lt;code&gt;shhh&lt;/code&gt; para suprimir a saída padrão (os "&lt;em&gt;prints&lt;/em&gt;") de uma função. Fica mais ou menos assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shhh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;devnull&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;student_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect_stdout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;

&lt;span class="nd"&gt;@shhh&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say_goodbye_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Goodbye, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;say_hello_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;say_goodbye_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jair&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;say_hello_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Luiz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executando esse exemplo, a saída será somente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello, Luiz!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E aí, como você pensa em explorar o poder dos decorators? &lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>python</category>
    </item>
    <item>
      <title>Testando testes no Python - Parte 3: Pytest dentro do Pytest</title>
      <dc:creator>Vitor Buxbaum Orlandi</dc:creator>
      <pubDate>Fri, 13 Oct 2023 21:43:20 +0000</pubDate>
      <link>https://forem.com/vbuxbaum/testando-testes-no-python-parte-3-pytest-dentro-do-pytest-493</link>
      <guid>https://forem.com/vbuxbaum/testando-testes-no-python-parte-3-pytest-dentro-do-pytest-493</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Pessoas geralmente começam engatinhando, depois andam, evoluem para corrida, e algumas fazem coisas mais estranhas, como &lt;a href="https://www.google.com/search?q=parkour"&gt;Parkour&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pessoas Devs geralmente começam codando, depois testam, evoluem para o TDD, e algumas fazem coisas mais estranhas, como &lt;strong&gt;testes de testes&lt;/strong&gt;. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Boas vindas! 🤩 Esse é o 3º artigo de uma &lt;a href="https://dev.to/vbuxbaum/series/24810"&gt;curta série&lt;/a&gt;, contando um pouco mais sobre "testes de testes". 🧪&lt;/p&gt;

&lt;p&gt;Vou discutir as motivações, alternativas, e detalhar as formas que fazemos em projetos de Python na &lt;a href="https://www.betrybe.com/"&gt;Trybe&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retomando de onde paramos
&lt;/h2&gt;

&lt;p&gt;No artigo anterior mostrei como foi construída a primeira solução para testes de mutações customizadas, utilizando uma &lt;em&gt;fixture&lt;/em&gt; &lt;code&gt;autouse&lt;/code&gt; parametrizada com as mutações. &lt;/p&gt;

&lt;p&gt;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 &lt;code&gt;assert&lt;/code&gt;s, mas limitante quando pensamos em fazer testes mais elaborados e melhor organizados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trocando de "função" para "módulo"
&lt;/h2&gt;

&lt;p&gt;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, &lt;strong&gt;executar todo um arquivo de testes da pessoa estudante para cada mutação.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;&lt;em&gt;falhar&lt;/em&gt;&lt;/strong&gt;. Se &lt;strong&gt;&lt;em&gt;todos os testes passarem para uma das mutações&lt;/em&gt;&lt;/strong&gt;, consideramos que ainda não foi atingida a qualidade que esperamos.&lt;/p&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;h2&gt;
  
  
  "Como rodar um arquivo de teste no Pytest?"
&lt;/h2&gt;

&lt;p&gt;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. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ou será que apareceu? 👀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;As respostas para essa pesquisa são de conteúdos para iniciantes, e elas citam comandos básicos da CLI do Pytest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest tests/test_file.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E meu primeiro pensamento foi:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Eu já sei disso! Me mostre algo que eu não sei! 😫"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;E logo em seguida:&lt;/p&gt;

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

&lt;p&gt;Resposta: &lt;strong&gt;Sim, é possível!&lt;/strong&gt; 🎉&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://docs.pytest.org/en/7.1.x/how-to/usage.html#calling-pytest-from-python-code"&gt;como executá-lo sem a CLI&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="n"&gt;retcode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Um &lt;code&gt;retcode&lt;/code&gt; igual a &lt;code&gt;0&lt;/code&gt; (zero) significa que os testes passaram, falharam caso contrário. &lt;/p&gt;

&lt;h2&gt;
  
  
  Executando o Pytest &lt;em&gt;dentro&lt;/em&gt; do Pytest
&lt;/h2&gt;

&lt;p&gt;Não parece uma ideia muito agradável, e até &lt;a href="https://docs.pytest.org/en/7.1.x/how-to/usage.html#calling-pytest-from-python-code:~:text=Calling%20pytest.main,is%20not%20recommended."&gt;a documentação da ferramenta faz um alerta sobre isso&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[...] fazer multiplas chamadas a &lt;code&gt;pytest.main()&lt;/code&gt; a partir do mesmo processo (para re-executar testes, por exemplo) não é recomendado.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Parece que escreveram isso especialmente pra mim! 😂 Mas sem ousadia nunca venceremos obstáculos, não é mesmo?&lt;/p&gt;

&lt;p&gt;Brincadeiras a parte, seguimos entendendo que esse é um uso controlado e (&lt;em&gt;até o momento&lt;/em&gt;) com complexidade moderada.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ajustando o exemplo anterior
&lt;/h2&gt;

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

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

&lt;p&gt;Isolando essa nova função de teste em um arquivo dedicado, teremos o seguinte:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwo9frfibfbx5wlqzrojh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwo9frfibfbx5wlqzrojh.png" alt="Nova estratégia para aplicar mutações" width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Que traduzido para código, fica assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="n"&gt;mutated_functions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_exception_mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slice_input_mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mutation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mutated_functions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_mutations_for_test_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests.test_sorter.sort_this_by&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;retcode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/test_sorter.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;retcode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mutação deveria falhar&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dado que não melhoramos o &lt;a href="https://dev.to/vbuxbaum/testando-testes-no-python-parte-2-fixtures-parametrizadas-270g#:~:text=E%20um%20poss%C3%ADvel%20teste%20implementado%20por%20uma%20pessoa%20estudante%20em%20tests/test_sorter.py%3A"&gt;último exemplo de "teste da pessoa estudante"&lt;/a&gt;, ao executar &lt;code&gt;python -m pytest&lt;/code&gt; teremos a saída semelhante a seguinte:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffclrwr36loqfwnm5whim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffclrwr36loqfwnm5whim.png" alt="Resultado do teste com pytest chamado internamente" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Os 2 &lt;code&gt;PASSED&lt;/code&gt; 🟢 indicados na imagem são:&lt;/p&gt;

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

&lt;p&gt;E, como imaginávamos, a chamada &lt;code&gt;pytest.main&lt;/code&gt; com a mutação &lt;code&gt;no_exception_mutation&lt;/code&gt; retornou &lt;code&gt;0&lt;/code&gt; e por isso nosso &lt;code&gt;assert&lt;/code&gt; acusou um problema: "Mutação deveria falhar" (mas não falhou).&lt;/p&gt;

&lt;h2&gt;
  
  
  Melhorando a solução
&lt;/h2&gt;

&lt;p&gt;Particularmente fiquei muito orgulhoso com essa solução! 💜 Mas há melhorias importante antes de chegarmos na versão disponibilizada para as turmas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ocultar logs excessivos
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://pt.stackoverflow.com/questions/118603/qual-%C3%A9-a-finalidade-do-caminho-dev-null-em-alguns-comandos"&gt;"lixeira" &lt;code&gt;/dev/null&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+import contextlib
+import os
&lt;/span&gt;&lt;span class="p"&gt;from unittest.mock import patch
from tests import sorter_mutations
import pytest
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;mutated_functions = [
&lt;/span&gt;    sorter_mutations.no_exception_mutation,
    sorter_mutations.slice_input_mutation,
]
&lt;span class="err"&gt;

&lt;/span&gt;@pytest.mark.parametrize("mutation", mutated_functions)
&lt;span class="p"&gt;def test_mutations_for_test_module(mutation):
&lt;/span&gt;    with patch("tests.test_sorter.sort_this_by", mutation):
&lt;span class="gi"&gt;+        with open(os.devnull, "w") as student_output:
+            with contextlib.redirect_stdout(student_output):
&lt;/span&gt;                 retcode = pytest.main(["tests/test_sorter.py"])
    assert retcode != 0, "Mutação deveria falhar"
&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Mutações devem falhar &lt;code&gt;and&lt;/code&gt; Original deve passar
&lt;/h3&gt;

&lt;p&gt;Tivemos um desafio semelhante na solução anterior usando a &lt;em&gt;fixture&lt;/em&gt;: além de garantir que as mutações &lt;strong&gt;devem falhar&lt;/strong&gt;, devemos garantir que o teste "normal" ou "original" &lt;strong&gt;deve passar&lt;/strong&gt;.&lt;/p&gt;

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

&lt;p&gt;Uma função que coleta todos os &lt;em&gt;nodeid&lt;/em&gt;'s de testes funcionais para um arquivo pode ser escrita assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_test_functions_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_test_module&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;test_file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_test_module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;relative_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;test_file_path&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;::&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getmembers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_test_module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isfunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;member&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqixau04pmhtkl8ct73v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxqixau04pmhtkl8ct73v.png" alt="Exemplo de nodeid" width="800" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Obs: Se a pessoa estudante utilizar parametrização em seus testes, essa função de coleta de &lt;em&gt;nodeid&lt;/em&gt;'s não será suficiente para a &lt;code&gt;pytest-dependency&lt;/code&gt; funcionar corretamente. Por isso &lt;a href="https://github.com/betrybe/pytest-dependency/commit/a5b5a21255ddb62727fafb5f9caa923e3a8cc985"&gt;alteramos nosso &lt;em&gt;fork&lt;/em&gt; novamente&lt;/a&gt;, garantindo a interpretação correta das dependências.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Nosso &lt;code&gt;assert&lt;/code&gt;, nossas regras
&lt;/h3&gt;

&lt;p&gt;Já que foi necessário criar um &lt;code&gt;assert&lt;/code&gt; 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 &lt;code&gt;"Mutação deveria falhar"&lt;/code&gt; se podemos chegar em algo como &lt;code&gt;"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}"&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Poderíamos ter um map/dicionário para definir a dica de cada mutação, mas escolhemos uma forma mais "preguiçosa": a &lt;em&gt;docstring&lt;/em&gt; da própria mutação. Um exemplo seria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...
&lt;/span&gt;
&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mutation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mutated_functions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_mutations_for_test_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests.test_sorter.sort_this_by&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;devnull&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;student_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect_stdout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;student_output&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;retcode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests/test_sorter.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;retcode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Seus testes em &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tests/test_sorter.py&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; deveriam falhar com a mutação,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; mas passaram. Confira essa dica: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__doc__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Primeiro a bagunça, depois a arrumação
&lt;/h2&gt;

&lt;p&gt;Ufa... Muito código (&lt;em&gt;e nem mostrei tudo&lt;/em&gt;) mas chegamos lá! 🎉&lt;/p&gt;

&lt;p&gt;E nesse momento vem a dor de olhar todo aquele código &lt;em&gt;criativo&lt;/em&gt;, 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.&lt;/p&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="c1"&gt;# Aproveitei nosso fork da pytest-dependency para
# posicionar as funções de apoio
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pytest_dependency&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;assert_fails_with_mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;get_skip_markers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;get_test_assessment_configs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;run_pytest_quietly&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.sorter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sort_this_by&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;test_sorter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;

&lt;span class="c1"&gt;# TA_CFG será um objeto para guardar dados que queremos obter facilmente
&lt;/span&gt;&lt;span class="n"&gt;TA_CFG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_test_assessment_configs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;target_asset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sort_this_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;mutations_module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sorter_mutations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;student_test_module&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;test_sorter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Com essa configuração garantimos que só testaremos as mutações
# caso a pessoa estudante tenha feito testes que passam.
&lt;/span&gt;&lt;span class="n"&gt;pytestmark&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_skip_markers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TA_CFG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mutation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TA_CFG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MUTATED_FUNCTIONS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_mutations_for_test_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TA_CFG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PATCH_TARGET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;return_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_pytest_quietly&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;TA_CFG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;STUDENT_TEST_FILE_PATH&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="nf"&gt;assert_fails_with_mutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TA_CFG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Muitas alegrias, até que...
&lt;/h2&gt;

&lt;p&gt;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 &lt;strong&gt;Django&lt;/strong&gt;. 😅&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; é um framework incrível, mas ele abstrai &lt;strong&gt;&lt;em&gt;muitos&lt;/em&gt;&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Vou ser sincero aqui: ainda não tenho a resposta final! Fizemos uma prova de conceito com &lt;a href="https://docs.pytest.org/en/7.1.x/how-to/writing_hook_functions.html"&gt;hooks do Pytest&lt;/a&gt; que parece promissora, mas ainda não chegamos lá.&lt;/p&gt;

&lt;p&gt;Por isso, o próximo artigo pode demorar um pouco a sair, mas já estou ansioso para esse momento!&lt;/p&gt;

</description>
      <category>python</category>
      <category>braziliandevs</category>
      <category>testing</category>
    </item>
    <item>
      <title>Testing tests in Python - Part 1: reasons and alternatives</title>
      <dc:creator>Vitor Buxbaum Orlandi</dc:creator>
      <pubDate>Fri, 13 Oct 2023 17:00:05 +0000</pubDate>
      <link>https://forem.com/vbuxbaum/testing-tests-in-python-part-1-reasons-and-alternatives-42bn</link>
      <guid>https://forem.com/vbuxbaum/testing-tests-in-python-part-1-reasons-and-alternatives-42bn</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;People usually start by crawling, then walk, progress to running, and some do stranger things, like &lt;a href="https://www.google.com/search?q=parkour"&gt;Parkour&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Software Developers usually start by coding, then testing, progress to TDD, and some do stranger things, like &lt;strong&gt;testing tests&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Welcome! 🤩 This is the first article in a short series, providing more information about "tests of tests." 🧪&lt;/p&gt;

&lt;p&gt;I will discuss the motivations, alternatives, and detail the ways we do it in Python projects at &lt;a href="https://www.betrybe.com/"&gt;Trybe&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I came to know "tests of tests"
&lt;/h3&gt;

&lt;p&gt;Nice to meet you, I'm &lt;a href="https://www.linkedin.com/in/vitorbuxbaum/"&gt;Bux&lt;/a&gt;! At the moment I'm writing this text, I am an Instruction Specialist (i.e., a teacher and content producer) at Trybe, a Brazilian technology school, where I have been working for nearly 3 years.&lt;/p&gt;

&lt;p&gt;In our Web Development course, students work on &lt;strong&gt;projects&lt;/strong&gt; in which we assess if they have learned the content. These projects have automated tests, and the student must implement the necessary code to pass the tests in order to "pass the exam".&lt;/p&gt;

&lt;p&gt;For example: when teaching Flask, we can have a project that requires the implementation of a CRUD for songs, and we create tests that validate the requirements of this CRUD. If the student's implementation passes our tests, they are approved. ✅&lt;/p&gt;

&lt;p&gt;But what happens when we teach students how to create automated tests? &lt;strong&gt;How do we evaluate if they have created adequate tests?&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What are tests of tests
&lt;/h3&gt;

&lt;p&gt;In summary, "tests of tests" are the code we write to answer the last question, which is: have software tests been created that meet the specified requirements?&lt;/p&gt;

&lt;p&gt;This is a question that any team of developers (or quality analysts) deeply concerned with the quality of the developed test can come across. But in our team's case, the intention was to "just" assess if the class can create good software tests.&lt;/p&gt;

&lt;p&gt;Imagine, for example, a student needing to create tests for a function that searches for books, based on a string in their title, in the database. This search may have more details, such as being case-insensitive, returning paginated content, etc. I need to have an (automated) way to ensure that the student has created tests for this function.&lt;/p&gt;

&lt;h3&gt;
  
  
  What would &lt;em&gt;you&lt;/em&gt; do?
&lt;/h3&gt;

&lt;p&gt;Before moving on in the reading, take a moment to reflect: how would you do this? 🤔&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/a5viI92PAF89q/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/a5viI92PAF89q/giphy.gif" alt="Batman thinking" width="500" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Test Coverage Tool
&lt;/h3&gt;

&lt;p&gt;This is one of the simplest ways to validate whether a code section is being tested. Most modern languages have ways to check how many and which lines of the source code are being tested when running a test.&lt;/p&gt;

&lt;p&gt;In Python, we can use the Pytest plugin called &lt;a href="https://pytest-cov.readthedocs.io/en/latest/"&gt;pytest-cov&lt;/a&gt; (which, under the hood, uses &lt;a href="https://coverage.readthedocs.io/"&gt;coverage.py&lt;/a&gt;). With a few parameters, we can find out which lines of a specific file are "uncovered" by a specific test.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W9XgrM0B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mufrvzxzcrxhu5ortgy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9XgrM0B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mufrvzxzcrxhu5ortgy3.png" alt="Example usage of  raw `pytest-cov` endraw " width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can then run the student's tests and approve them if they have 100% coverage! 🎉&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🟢 Advantages: It is simple to build and maintain, and it can be applied to virtually any context. Furthermore, the tool provides direct and explicit feedback (essential in an educational context).&lt;/p&gt;

&lt;p&gt;🔴 Disadvantages: Test coverage is not a foolproof metric of test quality, as we can achieve 100% coverage without making a single &lt;code&gt;assert&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Mutation Testing Tool
&lt;/h3&gt;

&lt;p&gt;🌟 Mutation tests work on a simple but brilliant idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Unit tests should pass with the correct implementation of that unit, and &lt;strong&gt;should fail with incorrect implementations of that unit&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Libraries like &lt;a href="https://mutmut.readthedocs.io/en/latest/"&gt;&lt;code&gt;mutmut&lt;/code&gt;&lt;/a&gt; can do this for us: the tool generates mutations in the source code and runs the tests again. Mutations are made at the level of the &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree"&gt;Abstract Syntax Tree (AST)&lt;/a&gt;, such as changing comparators (&lt;code&gt;&amp;lt;&lt;/code&gt; to &lt;code&gt;&amp;gt;=&lt;/code&gt;) or booleans (&lt;code&gt;True&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;A good test is one that &lt;strong&gt;&lt;em&gt;fails&lt;/em&gt;&lt;/strong&gt; for all mutations. If your test continues to pass for some of the mutations, it means that it can be improved by validating more use cases.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🟢 Advantages: We can have more confidence that &lt;strong&gt;good tests&lt;/strong&gt; are being created by the student, not just that the function/unit is being executed.&lt;/p&gt;

&lt;p&gt;🔴 Disadvantages: Mutation testing tools add complexity to the testing process, which can impact the learning experience (remember, we want to use them in didactic projects). Additionally, the mutations offered by these tools are limited, generic, and have no 'strategy,' which can lead to a slowdown in the process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Customized Mutation Tests
&lt;/h3&gt;

&lt;p&gt;The idea behind the previous alternative is, as I mentioned, brilliant! 🌟&lt;/p&gt;

&lt;p&gt;Customized mutation (a concept I'm probably creating now, with the help of ChatGPT) uses the same idea but with a different implementation: instead of automatically generating various random mutations in a file, we choose exactly the mutations we want using test doubles.&lt;/p&gt;

&lt;p&gt;Picture this scenario: we ask students to create tests for the following &lt;code&gt;Queue&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__len__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IndexError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;LookupError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Queue is empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we will do, then, is to create "broken" versions of the &lt;code&gt;Queue&lt;/code&gt; class (with strategic mutations) and run the tests again. If the tests are well-written, they should &lt;strong&gt;&lt;em&gt;fail&lt;/em&gt;&lt;/strong&gt; with the mutations.&lt;/p&gt;

&lt;p&gt;A possible mutated class for this case is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.queue&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;

&lt;span class="c1"&gt;# Notice that I'm using inheritance to reuse the methods 
# of the original class and perform a mutation only 
# in the 'dequeue' method.
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WrongExceptionQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IndexError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Queue is empty&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words, if the student didn't create a test that validates the &lt;em&gt;type of exception&lt;/em&gt; thrown by the &lt;code&gt;dequeue&lt;/code&gt; method, they will not be approved.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🟢 Advantages: We continue to have confidence that &lt;strong&gt;good tests&lt;/strong&gt; are being created by the student, not just that the function/unit is being executed. Moreover, we have more freedom to create complex and strategic mutations for each requirement, and we don't have to run unnecessary mutations (which don't add to learning), saving time and computational resources. As a bonus, we can provide extremely customized feedback (essential in the learning process), such as: "Did you remember to validate which exception is raised in the &lt;code&gt;dequeue&lt;/code&gt; method?"&lt;/p&gt;

&lt;p&gt;🔴 Disadvantages: Effort is required to think and code the mutations, as they will not be generated automatically. Additionally, &lt;strong&gt;there is no library (or at least we didn't find it) that facilitates the configuration of this type of test&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  If it doesn't exist, we can create it! 🚀
&lt;/h2&gt;

&lt;p&gt;And that's what we did: we created the necessary code to apply customized mutations in the tests. 🤓&lt;/p&gt;

&lt;p&gt;In the next article in this series, I will detail how the 1st version (we are already moving towards the 3rd) of our "tests of tests" with customized mutations works. Until then, I'd like to know: how would &lt;strong&gt;you&lt;/strong&gt; implement this functionality?&lt;/p&gt;

&lt;p&gt;See you soon! 👋&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>Testando testes no Python - Parte 2: fixtures parametrizadas</title>
      <dc:creator>Vitor Buxbaum Orlandi</dc:creator>
      <pubDate>Mon, 09 Oct 2023 18:34:31 +0000</pubDate>
      <link>https://forem.com/vbuxbaum/testando-testes-no-python-parte-2-fixtures-parametrizadas-270g</link>
      <guid>https://forem.com/vbuxbaum/testando-testes-no-python-parte-2-fixtures-parametrizadas-270g</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Pessoas geralmente começam engatinhando, depois andam, evoluem para corrida, e algumas fazem coisas mais estranhas, como &lt;a href="https://www.google.com/search?q=parkour" rel="noopener noreferrer"&gt;Parkour&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pessoas Devs geralmente começam codando, depois testam, evoluem para o TDD, e algumas fazem coisas mais estranhas, como &lt;strong&gt;testes de testes&lt;/strong&gt;. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Boas vindas! 🤩 Esse é o 2º artigo de uma &lt;a href="https://dev.to/vbuxbaum/series/24810"&gt;curta série&lt;/a&gt;, contando um pouco mais sobre "testes de testes". 🧪&lt;/p&gt;

&lt;p&gt;Vou discutir as motivações, alternativas, e detalhar as formas que fazemos em projetos de Python na &lt;a href="https://www.betrybe.com/" rel="noopener noreferrer"&gt;Trybe&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retomando de onde paramos
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Para o nosso cenário entendemos que o ideal (considerando todas as variáveis e limitações) era termos o &lt;strong&gt;teste de mutações customizadas&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Então chegou hora da verdade: a ideia era bonita mas precisava funcionar. E aí é que entram as habilidades do &lt;a href="https://github.com/barraponto" rel="noopener noreferrer"&gt;Capi&lt;/a&gt;, que ficou responsável pela implementação.&lt;/p&gt;

&lt;p&gt;Na época, Capi era a pessoa mais sênior do time e não havia ninguém melhor para ser o pai (&lt;em&gt;ou 'py'?&lt;/em&gt;) da criação. Ele gentilmente me concedeu a honra de escrever sobre a solução, e ainda revisar antes da publicação. 💜&lt;/p&gt;

&lt;p&gt;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! 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Entenda o caso: testar ordenação por critérios
&lt;/h2&gt;

&lt;p&gt;A primeira versão surgiu em um contexto que, durante um dos projetos da nossa formação, a pessoa estudante precisava implementar &lt;strong&gt;testes para uma função chamada &lt;code&gt;sort_this_by&lt;/code&gt; que ordena uma lista de dicionários (&lt;code&gt;dict&lt;/code&gt;s)&lt;/strong&gt;. Logo, precisávamos &lt;em&gt;testar os testes&lt;/em&gt; de cada estudante contra mutações dessa função de ordenação.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ctcbrl87sl5t692x5iv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ctcbrl87sl5t692x5iv.png" alt="Relação do arquivo com função original, e arquivo de testes da pessoa estudante"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;crescente&lt;/em&gt; (e &lt;em&gt;decrescente&lt;/em&gt; caso contrário); e uma exceção específica era levantada caso o parâmetro &lt;code&gt;criteria&lt;/code&gt; não fosse um dos 3 valores válidos.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# ordenação descrescente
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# ordenação descrescente
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;min_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# ordenação crescente
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hobby&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;short_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Os detalhes da função &lt;code&gt;sort_this_by&lt;/code&gt; 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 &lt;code&gt;src/sorter.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort_this_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unordered_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E um possível teste implementado por uma pessoa estudante em &lt;code&gt;tests/test_sorter.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.sorter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sort_this_by&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_sort_this_by&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;John&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jane&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;criteria&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Jane&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;John&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sort_this_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expect&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Criando as mutações
&lt;/h2&gt;

&lt;p&gt;Como precisamos executar os testes da pessoa estudante "trocando" a função &lt;code&gt;sort_this_by&lt;/code&gt; 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 &lt;em&gt;code-paths&lt;/em&gt; (ifs, raises, excepts, returns, etc), provavelmente terei aproximadamente 5 mutações.&lt;/p&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.sorter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sort_this_by&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;no_exception_mutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Mutação que nunca levanta exceção&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sort_this_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;elements&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;slice_input_mutation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Mutação que altera lista inicial&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sort_this_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:],&lt;/span&gt; &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# def other_mutation(elements, criteria):
#     ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora, preciso que o teste &lt;code&gt;test_sort_this_by&lt;/code&gt; seja executado uma vez para cada uma dessas mutações.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parametrizando o teste com uso de uma &lt;em&gt;fixture&lt;/em&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://docs.pytest.org/fixtures.html" rel="noopener noreferrer"&gt;&lt;em&gt;Fixtures&lt;/em&gt;&lt;/a&gt; são a forma que o Pytest oferece para evitarmos duplicação de código em nossos testes, principalmente pensando em &lt;em&gt;setup&lt;/em&gt; e &lt;em&gt;teardown&lt;/em&gt; de recursos. Além da documentação, &lt;a href="https://www.youtube.com/watch?v=sidi9Z_IkLU" rel="noopener noreferrer"&gt;essa live&lt;/a&gt; do &lt;a class="mentioned-user" href="https://dev.to/dunossauro"&gt;@dunossauro&lt;/a&gt; também é uma ótima opção para aprofundar no tema 😉&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;O Pytest já oferece uma forma de executar uma mesma função de teste para múltiplos valores: &lt;a href="https://docs.pytest.org/en/7.3.x/how-to/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions" rel="noopener noreferrer"&gt;o marcador &lt;em&gt;parametrize&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Entretanto, utilizar o marcador em nosso caso &lt;strong&gt;não é boa alternativa&lt;/strong&gt; 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. 😅&lt;/p&gt;

&lt;p&gt;Felizmente o Pytest também nos permite criar &lt;em&gt;fixtures&lt;/em&gt; parametrizadas, que vão ser uma "mão na roda" nesse caso. Se inserirmos essa &lt;em&gt;fixture&lt;/em&gt; no &lt;code&gt;conftest.py&lt;/code&gt;, e ainda marcá-la como &lt;code&gt;autouse&lt;/code&gt; para que ela seja aplicada automaticamente para todos os testes em seu contexto, conseguimos o que não tínhamos com o marcador &lt;em&gt;parametrize&lt;/em&gt;. Então vamos à implementação do nosso &lt;code&gt;tests/conftest.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;

&lt;span class="n"&gt;mutated_functions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_exception_mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slice_input_mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;autouse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mutated_functions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_mutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt;&amp;gt; Parâmetro mutação: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Executando os testes, teremos a seguinte saída:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7e1pcxt44v34iuihf76q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7e1pcxt44v34iuihf76q.png" alt="Resultado após fixture com print"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Uhuuul! ✅ Tudo passou! 🌟 Mas espera... 🤔 Como são mutações, os testes deveriam falhar não é mesmo? &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Na verdade, não estamos fazendo nada com as mutações além de um simples &lt;code&gt;print&lt;/code&gt;. Precisamos alterar nossa &lt;em&gt;fixture&lt;/em&gt; para que ela faça a "mágica" que precisamos. &lt;/p&gt;

&lt;h2&gt;
  
  
  A "mágica"
&lt;/h2&gt;

&lt;p&gt;🪄 A "mágica" é um simples &lt;code&gt;patch&lt;/code&gt;! Assim como, durante testes de aplicações, podemos fazer o &lt;code&gt;patch&lt;/code&gt; de uma dependência, podemos fazer isso dentro do próprio teste. Genial, Capi! 💜&lt;/p&gt;

&lt;p&gt;A alteração no &lt;code&gt;tests/conftest.py&lt;/code&gt; então será:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;import pytest
&lt;/span&gt;&lt;span class="gi"&gt;+from unittest.mock import patch
&lt;/span&gt;&lt;span class="p"&gt;from tests import sorter_mutations
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;mutated_functions = (
&lt;/span&gt;    sorter_mutations.no_exception_mutation,
    sorter_mutations.slice_input_mutation,
)
&lt;span class="err"&gt;

&lt;/span&gt;@pytest.fixture(autouse=True, params=mutated_functions)
&lt;span class="p"&gt;def validate_mutations(request):
&lt;/span&gt;&lt;span class="gd"&gt;-    print(f"\n&amp;gt;&amp;gt;&amp;gt; Parâmetro mutação: {request.param}", end=' ')
&lt;/span&gt;&lt;span class="gi"&gt;+    with patch("tests.test_sorter.sort_this_by", request.param):
+        yield
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Se executamos o teste novamente, temos o seguinte resultado:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdb1xuqkbwo12rrsq6pg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdb1xuqkbwo12rrsq6pg1.png" alt="Resultado após fixture com patch"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ou seja: &lt;strong&gt;o teste da "pessoa estudante" foi resistente (falhou) em uma mutação, mas não na outra&lt;/strong&gt;. Para que o teste possa ser considerado robusto, ambas as mutações deveriam causar &lt;code&gt;FAIL&lt;/code&gt; ❌.&lt;/p&gt;

&lt;p&gt;Ainda temos ajustes a fazer, mas aqui chegamos na nossa grande vitória: temos nosso próprio teste de mutações customizadas! 🎉&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;E agora posso retomar ao 2º motivo que faria o marcador &lt;em&gt;parametrize&lt;/em&gt; ser ruim para nosso caso: já imaginou como seria encaixar a funcionalidade do &lt;code&gt;patch&lt;/code&gt; lá dentro? 😬&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ajustes para tudo rodar bem
&lt;/h2&gt;

&lt;p&gt;Como estamos falando de um teste que será usado para pontuar estudantes de forma automatizada, precisamos de 2 ajustes principais:&lt;/p&gt;

&lt;p&gt;1️⃣ O teste da pessoa estudante &lt;strong&gt;&lt;em&gt;precisa passar&lt;/em&gt;&lt;/strong&gt; com a função original;&lt;br&gt;
2️⃣ Devemos transformar o conjunto de todos &lt;code&gt;FAIL&lt;/code&gt; ❌ das mutações em um &lt;code&gt;PASS&lt;/code&gt; ✅, para controlar se a pessoa estudante deve receber a pontuação ou não.&lt;/p&gt;

&lt;p&gt;O ponto 1️⃣ é resolvido adicionando a função &lt;code&gt;sort_this_by&lt;/code&gt; aos parâmetros da fixture &lt;code&gt;validate_mutations&lt;/code&gt; (que poderia mudar de nome):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;import pytest
from unittest.mock import patch
&lt;/span&gt;&lt;span class="gi"&gt;+from src.sorter import sort_this_by
&lt;/span&gt;&lt;span class="p"&gt;from tests import sorter_mutations
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;mutated_functions = (
&lt;/span&gt;    sorter_mutations.no_exception_mutation,
    sorter_mutations.slice_input_mutation,
&lt;span class="gi"&gt;+    sort_this_by
&lt;/span&gt;)
&lt;span class="err"&gt;

&lt;/span&gt;@pytest.fixture(autouse=True, params=mutated_functions)
&lt;span class="p"&gt;def validate_mutations(request):
&lt;/span&gt;    with patch("tests.test_sorter.sort_this_by", request.param):
        yield
&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para o item 2️⃣ usamos o &lt;a href="https://docs.pytest.org/en/7.1.x/how-to/skipping.html" rel="noopener noreferrer"&gt;marcador &lt;code&gt;XFAIL&lt;/code&gt; 🟨 do Pytest&lt;/a&gt; junto com os poderes do plugin &lt;a href="https://pytest-dependency.readthedocs.io/" rel="noopener noreferrer"&gt;&lt;code&gt;pytest-dependency&lt;/code&gt;&lt;/a&gt;: marcamos os testes das mutações como &lt;code&gt;XFAIL&lt;/code&gt; 🟨 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.&lt;/p&gt;

&lt;p&gt;Mas aqui surgiu um empecilho: o plugin &lt;code&gt;pytest-dependency&lt;/code&gt; não entende &lt;code&gt;XFAIL&lt;/code&gt; 🟨 como um resultado de sucesso. Então foi necessário criar um &lt;a href="https://github.com/betrybe/pytest-dependency" rel="noopener noreferrer"&gt;Fork do repositório&lt;/a&gt; com a melhoria (&lt;em&gt;Capi até &lt;a href="https://github.com/RKrahl/pytest-dependency/pull/67" rel="noopener noreferrer"&gt;abriu um PR&lt;/a&gt;, mas até hoje não foi revisado pelo time do &lt;code&gt;pytest-dependency&lt;/code&gt;&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Para usar essa opção, basta adicionar a linha &lt;code&gt;accept_xfail = true&lt;/code&gt; no arquivo de configurações do Pytest (&lt;code&gt;pytest.ini&lt;/code&gt; ou &lt;code&gt;pyproject.toml&lt;/code&gt;),&lt;/p&gt;

&lt;p&gt;Assim, nosso &lt;code&gt;tests/conftest.py&lt;/code&gt; fica semelhante ao seguinte:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.sorter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sort_this_by&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;

&lt;span class="n"&gt;mutated_functions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_exception_mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;marks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xfail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;sorter_mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slice_input_mutation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;marks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;xfail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;sort_this_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;marks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;depends&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_sort_this_by[no_exception_mutation]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test_sort_this_by[slice_input_mutation]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;autouse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mutated_functions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate_mutations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tests.test_sorter.sort_this_by&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Refatorando a solução final
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Por isso investimos um pouco mais de energia em uma refatoração, extraindo toda a construção da &lt;code&gt;mutated_functions&lt;/code&gt; para uma função que recebe apenas 3 parâmetros:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;O módulo com todas as mutações;&lt;/li&gt;
&lt;li&gt;A função original a ser testada;&lt;/li&gt;
&lt;li&gt;A função de teste da pessoa estudante.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Não vale a pena detalhar essa nova função aqui porque estaria fugindo do foco, mas preciso dizer que o módulo &lt;a href="https://docs.python.org/3/library/inspect.html" rel="noopener noreferrer"&gt;&lt;code&gt;inspect&lt;/code&gt;&lt;/a&gt; foi uma grande ajuda!&lt;/p&gt;

&lt;h2&gt;
  
  
  Próximos passos
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Mas de todas as limitações, uma precisava ser resolvida com mais urgência: &lt;strong&gt;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&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;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 &lt;strong&gt;&lt;em&gt;você&lt;/em&gt;&lt;/strong&gt; abordaria esse novo problema?&lt;/p&gt;

&lt;p&gt;Nos &lt;del&gt;vemos&lt;/del&gt; lemos em breve! 👋&lt;/p&gt;

</description>
      <category>python</category>
      <category>braziliandevs</category>
      <category>testing</category>
    </item>
    <item>
      <title>Testando testes no Python - Parte 1: motivos e alternativas</title>
      <dc:creator>Vitor Buxbaum Orlandi</dc:creator>
      <pubDate>Wed, 27 Sep 2023 13:30:32 +0000</pubDate>
      <link>https://forem.com/vbuxbaum/testando-testes-no-python-parte-1-motivos-e-alternativas-6oj</link>
      <guid>https://forem.com/vbuxbaum/testando-testes-no-python-parte-1-motivos-e-alternativas-6oj</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Pessoas geralmente começam engatinhando, depois andam, evoluem para corrida, e algumas fazem coisas mais estranhas, como &lt;a href="https://www.google.com/search?q=parkour"&gt;Parkour&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Pessoas Devs geralmente começam codando, depois testam, evoluem para o TDD, e algumas fazem coisas mais estranhas, como &lt;strong&gt;testes de testes&lt;/strong&gt;. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Boas vindas! 🤩 Esse é o primeiro artigo de uma curta série, contando um pouco mais sobre "testes de testes". 🧪&lt;/p&gt;

&lt;p&gt;Vou discutir as motivações, alternativas, e detalhar as formas que fazemos em projetos de Python na &lt;a href="https://www.betrybe.com/"&gt;Trybe&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Como conheci "testes de testes"
&lt;/h2&gt;

&lt;p&gt;Muito prazer, eu sou o &lt;a href="https://www.linkedin.com/in/vitorbuxbaum/"&gt;Bux&lt;/a&gt;! No momento em que escrevo esse texto, sou Especialista em Instrução (i.e., professor e produtor de conteúdo) na Trybe, uma escola de tecnologia brasileira, onde trabalho há quase 3 anos.&lt;/p&gt;

&lt;p&gt;No nosso curso de Desenvolvimento Web, estudantes realizam &lt;strong&gt;projetos&lt;/strong&gt; em que avaliamos se aprenderam o conteúdo. Nesses projetos temos testes automatizados, e a pessoa estudante deve implementar o código necessário para passar nos testes para conseguir a aprovação.&lt;/p&gt;

&lt;p&gt;Por exemplo: ao ensinarmos Flask podemos ter um projeto que exige a implementação de um CRUD de músicas, e criamos testes que validam os requisitos desse CRUD. Se a implementação da pessoa estudante passar em nossos testes, ela está aprovada. ✅&lt;/p&gt;

&lt;p&gt;Mas o que acontece quando ensinamos a pessoa a criar testes automatizados? &lt;strong&gt;Como vamos avaliar se ela criou testes adequados?&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  O que são os testes de testes
&lt;/h2&gt;

&lt;p&gt;Em resumo, "teste de testes" é o código que fazemos para responder a última pergunta, ou seja: foram criados testes de software que atendem aos requisitos informados?&lt;/p&gt;

&lt;p&gt;Essa é uma pergunta que pode ser reflexão de qualquer time de pessoas desenvolvedoras (&lt;em&gt;ou analistas de qualidade&lt;/em&gt;) profundamente preocupadas com a qualidade do teste desenvolvido. Mas, no caso do nosso time, a intenção era "apenas" avaliar se a turma consegue criar bons testes de software.&lt;/p&gt;

&lt;p&gt;Imagine que, por exemplo, a pessoa estudante precise criar testes para uma função que busca por livros no banco de dados a partir de uma string em seu título. Essa busca pode ter mais detalhes, como ser case-insensitive, retornar conteúdo paginado, etc. Eu preciso ter uma forma (automatizada) de garantir que a pessoa criou testes para essa função.&lt;/p&gt;

&lt;h2&gt;
  
  
  O que &lt;em&gt;você&lt;/em&gt; faria?
&lt;/h2&gt;

&lt;p&gt;Antes de avançar na leitura, pare para refletir um pouco: como você faria isso? 🤔&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/a5viI92PAF89q/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/a5viI92PAF89q/giphy.gif" alt="Batman pensativo" width="500" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Algumas alternativas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ferramenta de cobertura de testes
&lt;/h3&gt;

&lt;p&gt;Essa é uma das formas mais simples para validar se um trecho de código está sendo testado. A maioria das linguagens modernas possuem formas de averiguar, quando rodamos um teste, quantas e quais linhas do código fonte estão sendo testadas.&lt;/p&gt;

&lt;p&gt;No Python podemos usar o plugin do Pytest chamado &lt;a href="https://pytest-cov.readthedocs.io/en/latest/"&gt;pytest-cov&lt;/a&gt; (que por baixo dos panos utiliza a &lt;a href="https://coverage.readthedocs.io/"&gt;coverage.py&lt;/a&gt;). Com alguns parâmetros, podemos saber quais linhas de um arquivo específico estão "descobertas" por um teste específico. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W9XgrM0B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mufrvzxzcrxhu5ortgy3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W9XgrM0B--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mufrvzxzcrxhu5ortgy3.png" alt="Exemplo de uso do  raw `pytest-cov` endraw " width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eu posso então executar os testes da pessoa estudante, e aprová-la caso tenha 100% de cobertura! 🎉&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🟢 Vantagens: é simples de construir e dar manutenção, e podemos aplicar a praticamente qualquer contexto. Além disso, a ferramenta tem um feedback direto e explícito (essencial num contexto de educação)&lt;/p&gt;

&lt;p&gt;🔴 Desvantagens: cobertura de testes não é uma métrica de qualidade de testes "infalível", já que podemos conseguir 100% de cobertura sem fazer um único &lt;code&gt;assert&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Ferramenta de testes de mutação
&lt;/h3&gt;

&lt;p&gt;🌟 Testes de mutação funcionam a partir de uma ideia simples mas brilhante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Os testes de uma unidade devem passar com a implementação correta dessa unidade, e &lt;strong&gt;devem falhar com implementações incorretas dessa unidade&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Bibliotecas como o &lt;a href="https://mutmut.readthedocs.io/en/latest/"&gt;&lt;code&gt;mutmut&lt;/code&gt;&lt;/a&gt; podem fazer isso por nós: a ferramenta gera mutações ("sujeiras") no código fonte e roda os testes novamente. As mutações são realizadas a nível da &lt;a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree"&gt;AST (árvore sintática abstrata)&lt;/a&gt;, como trocar comparadores (&lt;code&gt;&amp;lt;&lt;/code&gt; por &lt;code&gt;&amp;gt;=&lt;/code&gt;) ou booleanos (&lt;code&gt;True&lt;/code&gt; por &lt;code&gt;False&lt;/code&gt;). &lt;/p&gt;

&lt;p&gt;Um bom teste é aquele que &lt;strong&gt;&lt;em&gt;falha&lt;/em&gt;&lt;/strong&gt; para todas as mutações. Se o seu teste continua passando para alguma das mutações, significa que ele pode melhorar validando mais casos de uso.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🟢 Vantagens: podemos ter mais confiança de que &lt;strong&gt;bons testes&lt;/strong&gt; estão sendo feitos pela pessoa estudante, e não apenas que a função/unidade está sendo executada.&lt;/p&gt;

&lt;p&gt;🔴 Desvantagens: ferramentas de mutação adicionam complexidade na execução do teste, o que pode impactar a experiência de aprendizado (lembre-se, queremos utilizar em projetos didáticos). Além disso, as mutações oferecidas por essas ferramentas são limitadas, genéricas e não possuem nenhuma 'estratégia', o que pode levar a uma lentidão no processo.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Testes de mutações customizadas
&lt;/h3&gt;

&lt;p&gt;A ideia por trás da alternativa anterior é, como já disse, brilhante! 🌟&lt;/p&gt;

&lt;p&gt;A mutação customizada (um conceito que provavelmente estou criando agora, com ajuda do ChatGPT) utiliza essa mesma ideia mas com uma implementação diferente: ao invés de gerarmos automaticamente diversas mutações aleatórias em um arquivo, escolhemos exatamente as mutações que desejamos com uso de dublês.&lt;/p&gt;

&lt;p&gt;Imagine o seguinte cenário: pedimos para que estudantes criem testes para a seguinte classe &lt;code&gt;Queue&lt;/code&gt; (&lt;em&gt;Queue = Fila, uma estrutura de dados sequencial em que inserimos elementos no fim, e removemos do início&lt;/em&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__len__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IndexError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;LookupError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A fila está vazia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O que faremos, então, é criar versões "quebradas" da classe &lt;code&gt;Queue&lt;/code&gt; (com mutações estratégicas) e rodar os testes novamente. Se os testes estiverem bem escritos, eles devem &lt;strong&gt;&lt;em&gt;falhar&lt;/em&gt;&lt;/strong&gt; com as mutações. &lt;/p&gt;

&lt;p&gt;Uma possível classe com mutação para esse caso é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;src.queue&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Queue&lt;/span&gt;

&lt;span class="c1"&gt;# Repare que estou usando herança para reaproveitar
# os métodos da classe original e realizar mutação 
# apenas no método 'dequeue'
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WrongExceptionQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Queue&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dequeue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;IndexError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A fila está vazia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja: se a pessoa estudante não fez um teste que valida o &lt;em&gt;tipo da exceção&lt;/em&gt; levantada pelo método &lt;code&gt;dequeue&lt;/code&gt;, ela não será aprovada.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🟢 Vantagens: continuamos tendo confiança de que &lt;strong&gt;bons testes&lt;/strong&gt; estão sendo feitos pela pessoa estudante, e não apenas que a função/unidade está sendo executada. Mas além disso, temos mais liberdade para criar mutações complexas e estratégicas para cada requisito, e não precisamos executar os testes com mutações desnecessárias (que não agregam ao aprendizado) poupando tempo e recursos computacionais. E como bônus, podemos dar feedbacks (essenciais no processo de aprendizado) extremamente customizados como, por exemplo: "Você lembrou de validar qual exceção é levantada no método &lt;code&gt;dequeue&lt;/code&gt;?"&lt;/p&gt;

&lt;p&gt;🔴 Desvantagens: é necessário esforço para pensar e codificar as mutações, já que não serão geradas automaticamente. E, além disso, &lt;strong&gt;não existe (&lt;em&gt;ou pelo menos não conseguimos encontrar&lt;/em&gt;) uma biblioteca que facilite a configuração desse tipo de teste&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Se não existe, a gente cria! 🚀
&lt;/h2&gt;

&lt;p&gt;E foi isso que fizemos: criamos o código necessário para aplicar as mutações customizadas nos testes. 🤓&lt;/p&gt;

&lt;p&gt;No próximo artigo dessa série vou detalhar como funciona 1ª versão (já estamos caminhando para a 3ª) dos nossos "testes de testes" com mutações customizadas. Até lá, queria saber: como &lt;strong&gt;você&lt;/strong&gt; implementaria essa funcionalidade?&lt;/p&gt;

&lt;p&gt;Nos &lt;del&gt;vemos&lt;/del&gt; lemos em breve! 👋&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>braziliandevs</category>
    </item>
    <item>
      <title>Python's "TypeError: unhashable type"</title>
      <dc:creator>Vitor Buxbaum Orlandi</dc:creator>
      <pubDate>Tue, 14 Mar 2023 15:02:00 +0000</pubDate>
      <link>https://forem.com/vbuxbaum/pythons-typeerror-unhashable-type-4kf0</link>
      <guid>https://forem.com/vbuxbaum/pythons-typeerror-unhashable-type-4kf0</guid>
      <description>&lt;p&gt;Have you ever encountered a message like &lt;code&gt;TypeError: unhashable type: 'set'&lt;/code&gt;, and couldn't figure out why exactly this exception was raised? Well, I'm here to help you!&lt;/p&gt;

&lt;h2&gt;
  
  
  When does it usually happen
&lt;/h2&gt;

&lt;p&gt;In addition to other situations, Python will raise this exception as soon as you try to store &lt;strong&gt;something that can not be hashed&lt;/strong&gt; (AKA unhashable) in a structure implemented as a HashMap. The most common Python types that are implemented as HashMaps are &lt;code&gt;dict&lt;/code&gt; and &lt;code&gt;set&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;dict&lt;/code&gt;s, the &lt;code&gt;TypeError: unhashable&lt;/code&gt; exception is raised when one tries to place an unhashable object as a &lt;strong&gt;key&lt;/strong&gt; of the dictionary. &lt;/p&gt;

&lt;p&gt;For &lt;code&gt;set&lt;/code&gt;s, the &lt;code&gt;TypeError: unhashable&lt;/code&gt; exception is raised when one tries to add an unhashable object to the &lt;code&gt;set&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  What does it mean &lt;em&gt;"can (not) be hashed"&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;In Python, we say that something &lt;strong&gt;can be hashed&lt;/strong&gt; if it is possible to calculate it's hash with the built-in &lt;code&gt;hash(obj)&lt;/code&gt; function. This function returns an integer for a given object, respecting the following rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Two objects that compare equal must also have the same hash value, but the reverse is not necessarily true.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In computer science hash functions are used in multiple contexts (&lt;em&gt;like cryptography and compression&lt;/em&gt;), but for HashMaps (as &lt;code&gt;set&lt;/code&gt; and &lt;code&gt;dict&lt;/code&gt;) it is used to compute a kind of &lt;em&gt;ID&lt;/em&gt; for each object. This ID is used to organize data in such a way that we can do fast insertions and lookups.&lt;/p&gt;

&lt;p&gt;It means that for every key in a &lt;code&gt;dict&lt;/code&gt; (and for every object in a &lt;code&gt;set&lt;/code&gt;) Python runs the function &lt;code&gt;hash(obj)&lt;/code&gt;, and that is when it actually raises the &lt;code&gt;TypeError: unhashable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pbt62n42p2o1tgoxd7u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pbt62n42p2o1tgoxd7u.png" alt="Comparing 'TypeError: unhashable' exceptions raised"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Hashable and unhashable types in Python
&lt;/h2&gt;

&lt;p&gt;As a general rule, &lt;strong&gt;Python immutable objects are hashable&lt;/strong&gt; and mutable objects are unhashable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29diz0foyh18q422gbyz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29diz0foyh18q422gbyz.png" alt="Examples of Python built-in hashable and unhashable types"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Actually, since what is really happening is a call to the object's &lt;code&gt;__hash__(self)&lt;/code&gt; magic method, anything can be hashable in python (as long as we implement it)! &lt;/p&gt;

&lt;p&gt;So, to summarize it all, let's see a custom class with a custom &lt;code&gt;__hash__(self)&lt;/code&gt; method&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# I'm using dataclass here to avoid coding 
# __init__ and __repr__ methods
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__hash__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Here we define how we should compute
&lt;/span&gt;        &lt;span class="c1"&gt;# the hash for a given User. In this example
&lt;/span&gt;        &lt;span class="c1"&gt;# we use the hash of the '__email' attribute
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__eq__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# This method is necessary if you want to
&lt;/span&gt;        &lt;span class="c1"&gt;# guarantee that User objects with same
&lt;/span&gt;        &lt;span class="c1"&gt;# email will not duplicate in a set or
&lt;/span&gt;        &lt;span class="c1"&gt;# as a dict key, for example
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;

&lt;span class="n"&gt;main_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Vitor Buxbaum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vitor.buxbaum@gmail.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gmail_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Vitor Buxbaum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vitor.buxbaum@gmail.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;work_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Vitor Buxbaum&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vitor.buxbaum@betrybe.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;my_accounts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;main_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gmail_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;work_account&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# my_accounts =
# {
#     User(name='Vitor Buxbaum', email='vitor.buxbaum@gmail.com'),
#     User(name='Vitor Buxbaum', email='vitor.buxbaum@betrybe.com')
# }
&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
  </channel>
</rss>
