Olá Habr! Apresento a você a tradução da entrada "# [test] in 2018" no blog de John Renner, que pode ser encontrada 
aqui .
Recentemente, tenho trabalhado na implementação do 
eRFC para estruturas de teste personalizadas para o Rust. Estudando a base de código do compilador, estudei as partes internas dos testes em Rust e percebi que seria interessante compartilhar isso.
Atributo # [teste]
Hoje, os programadores da Rust contam com o atributo interno 
#[test] . Tudo que você precisa fazer é marcar a função como um teste e ativar algumas verificações:
 #[test] fn my_test() { assert!(2+2 == 4); } 
Quando esse programa é compilado usando os 
rustc --test ou 
cargo test , ele cria um arquivo executável que pode executar esta e qualquer outra função de teste. Esse método de teste permite manter organicamente os testes próximos ao código. Você pode até colocar testes dentro de módulos privados:
 mod my_priv_mod { fn my_priv_func() -> bool {} #[test] fn test_priv_func() { assert!(my_priv_func()); } } 
Assim, entidades privadas podem ser facilmente testadas sem o uso de ferramentas de teste externas. Essa é a chave para testes ergonômicos no Rust. Semanticamente, no entanto, isso é bastante estranho. Como a função 
main chama esses testes se eles não são visíveis ( 
nota do tradutor : lembro que private - declarado sem usar a palavra-chave 
pub - é protegido por encapsulamento de acesso externo)? O que exatamente o 
rustc --test faz?
#[test] implementado como uma conversão de sintaxe dentro da 
libsyntax compilador libsyntax. Esta é essencialmente uma macro sofisticada que reescreve nossa caixa em 3 etapas:
Etapa 1: reexportar
Como mencionado anteriormente, os testes podem existir dentro de módulos privados, portanto, precisamos de uma maneira de expô-los à função 
main sem quebrar o código existente. Para esse fim, a 
libsyntax cria módulos locais chamados __test_reexports que __test_reexports recursivamente os testes . Esta divulgação traduz o exemplo acima em:
 mod my_priv_mod { fn my_priv_func() -> bool {} fn test_priv_func() { assert!(my_priv_func()); } pub mod __test_reexports { pub use super::test_priv_func; } } 
Agora, nosso teste está disponível como 
my_priv_mod::__test_reexports::test_priv_func . Para módulos aninhados, 
__test_reexports módulos que contêm os testes; portanto, o teste 
a::b::my_test se torna 
a::__test_reexports::b::__test_reexports::my_test . Até agora, esse processo parece bastante seguro, mas o que acontece se houver um módulo 
__test_reexports existente? Resposta: 
nada .
Para explicar, precisamos entender 
como o AST representa identificadores . O nome de cada função, variável, módulo, etc. armazenado não como uma string, mas como um 
símbolo opaco, que é essencialmente um número de identificação para cada identificador. O compilador armazena uma tabela de hash separada, que permite restaurar o nome legível do símbolo, se necessário (por exemplo, ao imprimir um erro de sintaxe). Quando o compilador cria o módulo 
__test_reexports , ele gera um novo símbolo para o identificador, portanto, embora os 
__test_reexports gerados pelo compilador possam ter o mesmo nome do módulo genérico, ele não utilizará o símbolo. Essa técnica evita colisões de nomes durante a geração de código e é a base da higiene do sistema de macro Rust.
Etapa 2: Gerando cintas
Agora que nossos testes estão acessíveis a partir da raiz do nosso engradado, precisamos fazer algo com eles. 
libsyntax gera esse módulo:
 pub mod __test { extern crate test; const TESTS: &'static [self::test::TestDescAndFn] = &[]; #[main] pub fn main() { self::test::test_static_main(TESTS); } } 
Embora essa conversão seja simples, ela nos fornece muitas informações sobre como os testes são realmente executados. Os testes são coletados em uma matriz e transmitidos ao 
test_static_main testes, chamado 
test_static_main . Voltaremos ao que 
TestDescAndFn , mas no momento a principal conclusão é que existe uma caixa chamada 
test , que faz parte do núcleo do Rust e implementa todo o tempo de execução para teste. A interface de 
test é instável, portanto, a única maneira estável de interagir com ela é a macro 
#[test] .
Etapa 3: Gerando um Objeto de Teste
Se você escreveu anteriormente testes no Rust, pode estar familiarizado com alguns dos atributos opcionais disponíveis para as funções de teste. Por exemplo, um teste pode ser anotado com 
#[should_panic] se esperamos que o teste cause pânico. Parece algo como isto:
 #[test] #[should_panic] fn foo() { panic!("intentional"); } 
Isso significa que nossos testes são mais do que simples funções e possuem informações de configuração. 
test codifica esses dados de configuração em uma estrutura chamada 
TestDesc . Para cada função de teste na caixa, a 
libsyntax analisará seus atributos e gerará uma instância do 
TestDesc . Em seguida, combina 
TestDesc e a função de teste na estrutura lógica 
TestDescAndFn , com a qual 
test_static_main trabalha. Para este teste, a instância gerada de 
TestDescAndFn parece com isso:
 self::test::TestDescAndFn { desc: self::test::TestDesc { name: self::test::StaticTestName("foo"), ignore: false, should_panic: self::test::ShouldPanic::Yes, allow_fail: false, }, testfn: self::test::StaticTestFn(|| self::test::assert_test_result(::crate::__test_reexports::foo())), } 
Depois de criarmos uma matriz desses objetos de teste, eles são passados para o executor de testes por meio da ligação gerada na etapa 2. Embora essa etapa possa ser considerada parte da segunda etapa, quero chamar a atenção para ela como um conceito separado, porque essa será a chave para implementar o teste personalizado estruturas, mas este será outro post do blog.
Posfácio: Métodos de Pesquisa
Embora eu tenha recebido muitas informações diretamente das fontes do compilador, fui capaz de descobrir que existe uma maneira muito simples de ver o que o compilador faz. A compilação noturna do compilador possui um sinalizador instável chamado 
unpretty , que você pode usar para imprimir o código-fonte do módulo após expandir as macros:
 $ rustc my_mod.rs -Z unpretty=hir 
Nota do tradutor
Interessante, ilustrarei o código do caso de teste após a divulgação macro:
Código-fonte personalizado:
 #[test] fn my_test() { assert!(2+2 == 4); } fn main() {} 
Código após a expansão de macros:
 #[prelude_import] use std::prelude::v1::*; #[macro_use] extern crate std as std; #[test] pub fn my_test() { if !(2 + 2 == 4) { { ::rt::begin_panic("assertion failed: 2 + 2 == 4", &("test_test.rs", 3u32, 3u32)) } }; } #[allow(dead_code)] fn main() { } pub mod __test_reexports { pub use super::my_test; } pub mod __test { extern crate test; #[main] pub fn main() -> () { test::test_main_static(TESTS) } const TESTS: &'static [self::test::TestDescAndFn] = &[self::test::TestDescAndFn { desc: self::test::TestDesc { name: self::test::StaticTestName("my_test"), ignore: false, should_panic: self::test::ShouldPanic::No, allow_fail: false, }, testfn: self::test::StaticTestFn(::__test_reexports::my_test), }]; }