Uso básico de pytest e suas fixtures

Em primeiro lugar, o que diabos é pytest e por que eu me importo com isso?

A biblioteca pytest é um pacote de testes em Python. Para que serve? Como eu sei que tenho muitos leitores na área acadêmica, acho que vou me fazer entender: você escreve seus projetos de programação em Python, para simulação numérica de algum problema físico, para processamento de dados, para automatizar alguma tarefa, para gerar gráficos etc. Você vai aumentando o seu programa até que não tem mais certeza de como uma parte conecta com outra. Você adiciona uma função inofensiva, e então percebe que outra, “aparentemente” não relacionada, parou de funcionar. Os arquivos que eram para ser gerados não o são mais. Os gráficos saem esquisitos. Os resultados numéricos não fazem sentido.

Testes, nesse contexto de programação, são funções adicionais que asseguram o funcionamento correto do código. Se uma determinada função, quando executada, deve sempre gerar um arquivo results.txt, então você escreve um teste que executa a função e assegura que esse arquivo existe. A cada modificação no seu programa, você executa seu teste; se ele falha, é sinal de que é preciso parar, avaliar e corrigir erros.

Neste ambiente acadêmico em que estou, não vejo muita ênfase sendo colocada em testar o seu código, o que é um contrassenso. Testes têm um sentido científico: se eles passam, então a hipótese de que o você programou representa a realidade se torna mais forte. Eles não são “perda de tempo”; é bem fácil escrever rapidamente um programa que não funciona.

Tudo o que vou falar aqui se baseia nessa minha experiência: um pesquisador que usa de programas em Python como uma ferramenta para produzir conhecimento científico.

Instalação e uso básico

Para aplicações acadêmicas, especialmente em Windows, a melhor maneira de usar Python é através da distribuição Anaconda. A biblioteca pytest já está incluida.

No espírito acadêmico de transparência, vou compartilhar exatamente como tenho usado pytest para melhorar minhas práticas de programação e, com isso, produzir resultados científicos mais confiáveis.

Tenho um projeto chamado magnet3Dpolomag. Não se preocupem sobre o que é, apenas entendam como minha plataforma de testes está organizada:

Lista de diretórios de um projeto usando pytest
A propósito, esse screenshot é do Visual Studio Code

No meu diretório raiz, além de vários arquivos, existem essas duas pastas importantes: uma pasta com o pacote em Python, com seus vários módulos, e uma pasta de testes. O diretório do pacote Python é declarado como “importável” ao conter o arquivo de inicialização __init__.py. Assim, quando esse pacote é instalado, eu posso digitar simplesmente:

import magnet3Dpolomag

e tudo que estiver no arquivo __init__.py dentro da pasta magnet3Dpolomag vai ser carregado.

A convenção de testes em Python é usar nomes de arquivos que comecem com test_, e no momento eu só tenho um arquivo geral: dentro dele existem funções que testam o correto funcionamento de magnet3Dpolomag quando parâmetros válidos são fornecidos (testar se os parâmetros passados ao programa são válidos ou não é outra história).

Como um exemplo de teste, aqui está um pedaço do meu pacote: uma classe Magnet3DModel, com uma função que faz uma transformação de dados de simulações e retorna dois vetores:

import numpy as np

class Magnet3DModel:

    def get_profile_data(self, ):
        """Return phi, B vectors at the central plane"""
        
        middle_plane = self.z_profile_1q == 0
        phi_middle_plane_1q = self.phi_profile_1q[middle_plane]
        B_middle_plane_1q = self.B_profile_1q[middle_plane]
    
        phi_vector = np.concatenate((phi_middle_plane_1q,
                                 phi_middle_plane_1q + 90,
                                 phi_middle_plane_1q + 180,
                                 phi_middle_plane_1q + 270))

        B_profile = np.concatenate((B_middle_plane_1q,
                                B_middle_plane_1q[::-1],
                                B_middle_plane_1q,
                                B_middle_plane_1q[::-1]))

        return phi_vector, B_profile

Uma breve explicação: esse “modelo”, quando é executado, armazena uma nuvem de dados nos vetores z_profile_1q, phi_profile_1q e B_profile_1q. Para esse método em particular, estou apenas interessado nos dados que correspondem ao “plano” z_profile_1q == 0. O sufixo 1q indica que se trata de dados “no primeiro quadrante”; como os dados são periódicos, eu retorno os dados “expandidos”.

Eu quero me assegurar que, quando esse modelo é executado, essa função vai retornar esses vetores conforme esperado, com valores apenas numéricos:

import numpy as np
import magnet3Dpolomag

def test_can_access_magnetic_profile():

    m = magnet3Dpolomag.Magnet3DModel()
    # executa mais instruções para simular o modelo

    phi_profile, B_profile = m.get_profile_data()

    assert (
        np.isfinite(phi_profile).all() and 
        np.isfinite(B_profile).all()
    )

Repare na semântica do teste: ao escrever esse código, eu não tenho ideia de como get_profile_data() funciona; eu apenas sei que, quando eu executar o modelo, quero poder chamar uma função que me retorne esses vetores e que estes não tenham valores não-numéricos (que poderia surgir de algum erro). Para esse fim, eu uso a função isfinite() da biblioteca NumPy, que retorna um vetor booleano que checa exatamente isso; então, eu uso o método all() de vetores da biblioteca NumPy, que vai retornar True se todos os elementos são True.

A chave de uma função de teste é o comando assert; se o seu argumento é falso, então o programa para (ou, no caso, a função de teste falha). A biblioteca pytest tem como grande característica usar esse comando, que faz parte da linguagem Python “pura”, e reportar exatamente o que deu errado, no caso de uma falha.

Testes são executadas invocando pytest em um terminal. A partir do diretório onde é executado e varrendo os subdiretórios em todos os níveis, esse comando primeiro “descobre” os testes, arquivo que começam com test e, dentro deles, as funções e classes que começam com test_. Como quase tudo em se tratando de pytest, esse processo de descoberta de testes é configurável. A biblioteca então executa todos as funções e métodos de teste e reporta, de maneira muito inteligente, os testes que passaram e os que falharam.

Este exemplo pode parecer meio tolo, então quero enfatizar o grande propósito dessa minha discussão: escrever testes me ajuda a entender um código em Python que o meu de anos atrás escreveu, e me faz pensar sobre o que ele deve fazer.

Além disso, eu não vou estar nesse laboratório para sempre, e estes programas que eu crio para auxiliar no nosso projeto ficarão de legado para a minha equipe. Cada hora gasta trabalhando numa plataforma de testes pode não gerar resultados práticos agora, mas tem consequências futuras. Os financiadores do meu projeto atual não poderiam se importar menos se o meu código está bem testado, e talvez até achem que eu deveria estar fazendo outra coisa; mas aposto que eles ficam felizes quando a próxima pessoa que trabalhar nisso (eu mesmo eu, daqui a algumas semanas) não perder dias de trabalho tentando resolver um problema que poderia ter sido solucionando mais rapidamente executando a plataforma de testes e vendo qual parte do sistema está defeituosa.

Anteriormente escrevi que um das grandes sacadas de pytest é “turbinar” o comando `assert`. Outra grande sacada é o uso de *fixtures*: funções que auxiliam os testes. Não quero nesse post fornecer um tutorial de Python ou mesmo de pytest; quero apenas [documentar de maneira resumida como estou *aprendendo*][show] a escrever testes e motivar pesquisadores a fazer o mesmo. Além da documentação da biblioteca, [*Python Testing with pytest*][pytestbook] é bem bom. No exemplo que usei acima, eu apresentei uma função de teste que simula a execução do modelo e confere alguns dados gerados por ele. E se, com base na mesma execução do modelo, eu queira testar outras condições, como outros vetores e matrizes gerados, arquivos que devem ser gravados, parâmetros que devem ser armazenados? Esse é um exemplo clássico de *fixture*: uma função que não é um teste, mas que é executada antes dos testes, e que passa informações “compartilhadas” entre eles. Além disso, é possível repetir o mesmo teste várias vezes, checando por diferentes “coisas”. Por exemplo:

import numpy as np
import magnet3Dpolomag
import pytest
import math

@pytest.fixture(scope='module')
def run_model():

    m = magnet3Dpolomag.Magnet3DModel()
    m.run()

    return m

def test_can_access_magnetic_profile(run_model):

    m = run_model
    # executa mais instruções para simular o modelo

    phi_profile, B_profile = m.get_profile_data()

    assert (
        np.isfinite(phi_profile).all() and 
        np.isfinite(B_profile).all()
    )

@pytest.mark.parametrize(
     'result',
     [
         "Demagnetized fraction[%]",
         "Saturated fraction[%]",
         "V_gap[l]",
         "V_magnet[l]",
         "V_demag[l]",
         "V_magnetic_steel[l]",
         "V_sat_magnetic_steel[l]",
         "V_stator[l]",
         "V_rotor[l]"
     ]
 )
 def test_access_results(self, result, run_model):
    m = run_model
    r = m.get_results_series()[result]

   assert ( (math.isfinite(r)) and (r >= 0))

A função no início é uma fixture que simplesmente cria e executa o meu modelo e retorna esse objeto, já povoado com os dados de simulação. Essa função é decorada com pytest.fixture, com um argumento scope que define que ela é executada por módulo. Ou seja, para todos os testes que usam essa fixture e que estão definidos nesse módulo (script), o modelo só é executado uma vez e passa o seu estado para todos os testes. O primeiro teste mostrado repete a lógica do exemplo da seção anterior; o segundo é outro exemplo onde quero testar que uma função que retorna uma Series tem de fato todos os registros. Essa função é decorada com pytest.mark.parametrize, onde especifico que o parâmetro result da função de teste assume cada um dos valores especificados na lista. Quando é invocado, pytest faz o loop automaticamente, chamando esse teste tantas vezes quantos são os elementos da lista que define o parâmetro.

Conclusões

Leitores podem estar confusos com a quantidade de informações técnicas. Peço desculpas (mas não muitas); esse post foi a melhor maneira que eu encontrei para consolidar todo meu aprendizado recente no assunto.

O que os leitores acham de textos nesse estilo?

Usando mapas mentais para planejar papers

Entenderam?

Sempre me espanta que mais pessoas não usem mapas mentais para planejar papers (e outros documentos escritos). É só a minha cabeça que contém ideias confusas demais e que precisam ser organizadas de alguma forma?

A imagem acima é do excelente MindNode (que eu acesso através do serviço Setapp), que infelizmente só existe para dispositivos Apple. Para Windows (para qualquer plataforma, na verdade), há o também bom MindMeister.

Meu fluxo de ficar desenvolvendo ideias até elas assumirem uma forma mais definida é totalmente influenciada pelo Presentations Field Guide, do David Sparks, e pelo episódio maravilhosamente chamado de Cooking Ideas do seu podcast Mac Power Users.

Os melhores equipamentos para dormir melhor

Quando eu morava na Dinamarca em 2017, fiquei sabendo de um app muito interessante chamado The Fabulous, que cria listas de rotinas para fazer ao longo do dia para melhorar algum aspecto da sua vida: seu sono, sua energia, sua alimentação etc. Sempre me interessei por desenvolvimento pessoal, mas na época não achava que o investimento na assinatura valeria a pena.

O quanto eu ainda tinha para aprender sobre a importância de cuidar de mim mesmo e de como tenho de ver gastos nessa área sob outra ótica…

Recentemente, soube de uma promoção para a assinatura de The Fabulous, e, agora sempre muito focado na minha saúde mental, resolvi embarcar nessa jornada (como o aplicativo trata essas rotinas). E eu não poderia estar muito impressionado com os resultados.

Eu sempre tive muita dificuldade em dormir bem, mas nas últimas semanas tenho consistentemente acordado restaurado, animado — e às vezes até depois do meu horário desejado, tamanha minha imersão no sono (nenhuma dessas vezes resultou em perder compromissos, felizmente). E o que mais me tem ajudado são alguns pequenos equipamentos:

Criado mudo com garrafa d'água, máscara de dormir e protetor auricular
Meus companheiros de sono

Eu nunca achei que diria isso, mas dormir com uma máscara de dormir e com protetores auricular transformou meu sono. O efeito para mim é principalmente psicológico: quando estou “equipado”, eu estou no meu casulo, alheio às perturbações do ambiente. Nada tem importância, exceto dormir e descansar.

E, se eu acordo no meio da noite, tenho a minha fiel garrafa d’água à disposição. Vou ao banheiro, tomo uma água para me hidratar, e logo volto a dormir.

Se o leitor tem problemas para dormir, experimente usar uma máscara e protetores — aliás, tente por uma noite, antes de achar que vai ficar desconfortável. E depois comente aqui!

O que eu faço quando trabalho em casa

Trabalhando em casa? Você deve ficar vendo Netflix, ou só dormindo, ou fazendo coisas inúteis!

Errado.

Primeiro, eu aproveito o sol e vou correr numa praça repleta de bebês também aproveitando o sol, para me lembrar da beleza da vida (principalmente depois de ter ido a um velório) e para clarear minha mente com tantos projetos profissionais em andamento.

Depois, eu aproveito o silêncio completo e faço um inventário de todas as pessoas cujo trabalho eu preciso acompanhar — e então pesquiso e testo a melhor maneira de fazer isso.

Então, eu almoço uma comida caseira com minha esposa vendo Friends — porque eu tenho o direito de ser feliz.

Em seguida, tomando um bom chá, eu testo um novo app de tomar notas — porque aprender e escrever sobre o que eu aprendi é a minha maior habilidade, e o que eu mais então.

Finalmente, eu brinco de canetinhas para me ajudar a entender o que eu preciso formular matematicamente (e implementar em um programa de computador) para um dos meus projetos profissionais.

Quer progredir no trabalho? Estude criatividade

A nobre leitora deve ter percebido um grande aumento de textos recentes nesse blog sobre criatividade. Não é de se espantar, já que Fábio Fortkamp.com reflete o que se passa na cabeça do Fábio Fortkamp, e ultimamente ele parece que só lê coisas sobre criatividade.

Mas como criatividade afeta a minha vida bastante mundana de pesquisador/marido/Guia das Oficinas? E por que eu me incomodo em partilhar isso, e porque eu encho a cabeça dos meus leitores sobre criatividade?

Para mim, criatividade é sobre ter ideias, e (quase) todo mundo pode se beneficiar disso.

Eu, sentado em um sítio, escrevendo no meu Bullet Journal, rodeado de cachorros
Eu, tendo muitas ideias

Kourosh Dini define um projeto concreto como aquele que tem passos bem definidos: ir à loja tal comprar isto; entrar no site X e submeter documento Y. Para esses tipos, que encontramos todo dia, não precisamos de muitas ideias, apenas de tempo e energia.

Na terminologia de Dini, projeto criativo, por outro lado, tem um fim desconhecido e passos não muito claro. Trabalhadores do conhecimento lidam com projetos criativos o tempo todo: escrever um relatório, preparar uma apresentação, criar um plano de negócios, preparar uma aula. O próximo passo não é simplesmente “digitar relatório”; você precisa pensar sobre ele. Ter ideias, enfim.

É por isso que, além de ler material mais técnico como artigos sobre circuitos magnéticos ou livros sobre otimização, no meu trabalho como pesquisador eu me cerco de textos sobre como aproveitar melhor essas referências e produzir mais. Quando Austin Kleon fala de usar um caderno para “guardar seus roubos”, isso não é aplicável apenas para artistas; meus cadernos estão cheios de anotações sobre incerteza experimental e precisão numérica. Quando Cal Newport fala da importância de caminhadas para a produtividade, eu levo a sério e me vejo pensando sobre alguma técnica de otimização no trajeto de casa até o laboratório. Se a Thais Godinho sugere “alternar contextos” (trabalhar no computador, depois sair um pouco das telas, depois voltar e etc), eu estou sempre com um livro ou um paper na minha mesa para descansar os olhos e aprender alguma coisa nova, sem cansar muito meu cérebro.

Nada disso é assunto “de engenharia”, mas tudo isso me ensina a ser um engenheiro pesquisador melhor.

E, de tanto ler sobre isso, essas técnicas de criatividade se irradiam para outras áreas da minha vida. Se vejo algum vídeo com uma receita interessante, da próxima vez em que for fazer alguma atividade “mundana” provavelmente estarei refletindo sobre ela, e sobre como posso usá-la para preparar um jantar para minha esposa. Ou estou meditando com algum livro da Bíblia, e faço conexões com outras passagens porque tenho muitos pensamentos registrados no meu Caderno Espiritual.

Ao prestar atenção e dedicar tempo a esse tipo de soft skill como criatividade, a minha vida fica melhor — a do leitor pode ficar também.

Para os cristãos como eu, estamos na Quaresma. E por que não usar elementos do dia a dia para nos lembrar disso?

Vaso de flores artificiais, mostrando flores brancas e roxas

Como bom marido que sou, sugerir incluir a cor roxa na decoração da casa

Screenshot do Todoist no iPhone, usando um tema de cor roxa
Meu Todoist, talvez o app que mais uso durante o dia, também me lembra diariamente da Quaresma

Captura de tela do aplicativo PCalc no iPhone, mostrando detalhes em roxo
E se for para fazer contas, que tal um toque de roxo quaresmal também?

Uma breve reflexão sobre “não ter tempo”

Uma das ideias valiosas de 168 Hours, de Laura Vanderkam, é que o nosso uso do tempo não diz respeito ao tempo em si, mas a prioridades. Num exemplo tão ingênuo quanto bom, você diz que não tempo para limpar sua casa — mas e se alguém lhe oferecesse muito dinheiro por isso, você não iria… arranjar tempo?

Ontem pude comprovar o quanto isso é verdadeiro na prática. Minha esposa pediu ajuda para um projeto pessoal, e minha reação instintiva foi dizer “eu não tenho tempo agora”. Porém, logo pensei no que tinha lido nesse livro. E sempre que eu faço uma lista de prioridades na vida, minha esposa sempre está no topo da lista. Eu jurei solenemente cuidar dela até o dia da minha morte. E o tempo é o mesmo para todo mundo e para qualquer tarefa… Eu não tenho tempo para ajudá-la, mas tenho tempo para qualquer outra coisa que eu iria fazer na hora? Como, se justamente ela é minha prioridade?

Felizmente, consegui ajudá-la. Espero, daqui para a frente, incorporar mais essa ideia daqui para a frente. Eu nunca vou “ter tempo”, ou pelo menos ter mais tempo que eu tenho agora. Se eu quero fazer alguma coisa, basta eu fazer uma escolha consciente.

A maneira mais efetiva de vencer a ansiedade é aprender a ficar em silêncio

No que eu escrevo essas palavras, faz um pouco mais de 24 horas da minha volta ao mundo real depois de 36 horas em um Retiro Espiritual para Guias das Oficinas de Oração e Vida. E como a minha esposa achou espantoso o que foi feito lá, vou compartilhar com vocês também a principal característica desse retiro:

Da noite de sexta-feira ao almoço de domingo, eu não conversei com ninguém — exceto, é claro, com o Senhor Deus, Vivo e Verdadeiro.


Eu não sou perfeito, não sou humilde, estou longe de ser santo. Tenho muitos defeitos. Sou impaciente. Mas de algo eu não abro mão de reconhecer: desde o começo do meu tratamento contra ansiedade e depressão, em 2017, eu realmente aprendi a ficar em silêncio e solidão. Quisera eu que isso se transportasse para as reuniões tensas na qual não consigo segurar a língua; tudo é uma caminhada e exige paciência. Mas, sentando observando a natureza no Morro das Pedras, não pude deixar de perceber: (1) como isso se tornou natural para mim, e (2) como isso soaria como uma loucura para a maioria das pessoas. Como assim, ficar 36 horas sem olhar Instagram, sem ver TV, sem conversar com ninguém?

Meus leitores: muitos de vocês me escrevem contando de problemas de ansiedade na pós-graduação e na vida. Mas isso nem seria necessário, porque basta eu olhar à minha volta e vejo tantas pessoas queridas acometidas por transtornos de ansiedade, perdendo qualidade de vida e achando que a vida é essa correria sem volta.

Não é. A vida pode ser calma e boa. Como foi dito nesse retiro, o ser humano foi criado por Deus para amar e ser amado, e a chave para isso é o silêncio. Quando tudo cala, só Deus fala; só o vazio absoluto pode ser preenchido pelo Infinito.

Existem muitas maneiras de começar. Procure um bom terapeuta. Procure apps e cursos de meditação — vejo cada vez mais cursos do tipo sendo oferecidos por aí, muitos gratuitos. Se você é cristão, procure as Oficinas de Oração e Vida na sua cidade. Mas não se deixe vencer pela ansiedade.

Aprenda a parar e contemplar.

Vista do Morro das Pedras, em Florianópolis, mostrando uma bela praia e morros ao fundo, num dia levemente nublado
A minha vista diária durante meu Retiro. Perdi meu tempo?

Em março, eu estou sem redes sociais e sem outras tecnologias digitais. E isso tem me feito ficar muito mais calmo e mais focado.

O que mais tem causado efeitos benéficos foi ter saído de grupos de WhatsApp. Olhando agora com essa perspectiva de quem está fora, percebo o WhatsApp como um simples substituto de SMS via WiFi que saiu totalmente de controle. Naturalmente, eu perco informações, mas as realmente importantes acabam chegando a mim, sem prejuízo da minha vida como um todo. A Thais Godinho, recentemente, fez ótima reflexão sobre isso. 

Falando nisso: eu parei de ler blogs, mas resisti a cancelar minhas diversas newsletters, porque são leituras que aprecio muito — e foi de onde tirei o link acima. Estou enganando a mim mesmo?

Também tenho aumentado muito a minha capacidade de gerar ideias ao caminhar sem escutar podcasts e audiolivros — esse tempo em solidão e silêncio, aliás, era um dos benefícios que eu estava mais buscando.

Última observação: agora que não tenho por que pegar o celular a cada momento de tédio, eu estou chocado como absolutamente todos à minha volta fazem isso o tempo todo…