Rust | Macros procedurais e testes automatizados

Escrevendo testes integrados para meu projeto Moy Sekret, senti falta de um recurso nativo do Rust para fazer setup & teardown, antes e depois dos casos de teste em si, para checar e limpar alguns efeitos colaterais dele em disco, por se tratar de um programa CLI que lida com criptografia de arquivo.

Sendo Rust uma linguagem compilada para binário nativo, sem um runtime, como Java ou C#, a solução mais óbvia para resolver isto com meta-programação foi criar uma macro.

Macro rules!

Na minha primeira interação para resolver meu problema, então, criei uma macro declarativa simples para injetar um bloco de código before e after imediatamente antes e logo após as funções de testes.

Fonte: https://github.com/leandrosilva/moy-sekret/blob/master/tests/common.rs

O que a macro setup_run_test faz é criar uma outra macro, a run_test, que é a real responsável por rodar o caso de testes, sanduichado pelos blocos before_test e after_test definidos através da setup_run_test.

Depois, para fazer uso destas macros é bem simples.

Fonte: https://github.com/leandrosilva/moy-sekret/blob/a165a570c36af0eae6e020608511a43240e2fda1/tests/encrypt_test.rs

Uma vez que a macro tenha sido exportada em sua definição, quando o módulo em que ela foi definida é importado, ela torna-se também disponível para uso no contexto atual.

Programação por convenção

A despeito destas macros terem dado um bom adianto, para eu não ter que escrever do_something(); e do_something_else(); em todos os meus casos de teste (que por enquanto nem são tantos, para ser franco), isto ainda me pareceu muito trabalho ter que fazer setup dos blocos before e after e depois usar a macro run_test! em cada caso de teste.

Por que não seguir uma convenção em vez de fazer uma configuração? You bet!

Macros procedurais

Rust tem um recurso [já não tão novo assim] chamado Procedural Macros, que permite que você manipule um trecho de código fonte (AST) em tempo de compilação e produza um código diferente para ser compilado.

Há três tipos de macros procedurais:

1) Derive macros (#[derive(AwesomeDerive]) – tipo de macro já bem estável, desde a versão 1.15 de Rust, e de uso razoavelmente comum.

Fonte: https://github.com/leandrosilva/moy-sekret/blob/master/src/lib.rs

2) Function-like macros (pretty_macro!()) – este tipo de macro está estável desde a edição 2018 de Rust. É um tipo muito interessante, parecido com macro_rules!, porém bem mais flexível, já que você tem bastante liberdade em relação aos parâmetros que podem ser aceitos.

Pense em uma função que execute SQL, por exemplo.

Todo este código SQL acaba sendo encapsulado em um TokenStream, que é passado para uma função sql, que finalmente pode parseá-lo e blá, blá, blá.

3) Attribute macros (#[GorgeousAttribute])- este tipo também tornou-se estável em Rust 2018. É muito parecido com as anotações que temos em Java ou C#, porém estritamente em tempo de compilação e permitem fazer transformações no código.

Este foi o tipo de macro que ajudou a resolver minha preguiça de digitar meia dúzia de linhas de código.

Entra o Testaun

O projeto Testaun é o crate que eu criei para conter macros que me ajudem a reduzir código nos meus testes. Hoje, tudo que ele tem é uma macro. Mas por hora, é tudo que eu preciso.

Fonte: https://github.com/leandrosilva/testaun/blob/master/src/lib.rs

O que esta macro procedural faz é extrair o bloco de código de uma função de testes e sanduichar ele com chamadas às funções testaun_before e testaun_after. Na prática, o mesmo que a macro run_test! que mostrei anteriormente.

Lembra? Em lugar de fazer o setup de dois blocos para serem executados antes e depois, usamos convention over configuration e esperamos que estas duas funções tenham sido definidas. Caso contrário, pau! O compilador vai chiar.

Okay. E como é que se usa isto depois?

Tendo adicionado este crate ao projeto, basta criar as funções que rodarão antes e depois do caso de teste, anotar a função de teste e é isto.

Fonte: https://github.com/leandrosilva/testaun/blob/master/tests/tasting_test.rs

Como o código acima exemplifica, se um caso de teste não precisa de before & after, basta não anotar com #[testaun_case].

Uma coisa que percebi foi que o crate serial_test (que também manipula AST) e o testaun não se dão bem juntos. Testaun boicota o serial_test. Vou estudar a coexistência deles depois.

Não se reprima. Digo, não se REPITA!

Se você for como eu, um programador preguiçoso, a DRY kinda guy, você pode usar macros para economizar umas linhas de código repetitivo, reduzir boilerplate, padronizar o código do seu projeto e, no final das contas, torná-lo menos suscetível a falhas.

Menos código repetido, menos bugs.

Aliás, menos código total, menos bugs ainda.

Autor: Leandro Silva

I do code for a happy living.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s