Como implementar test data builders em C# com ForeverFactory
Neste artigo, apresento o ForeverFactory, biblioteca de código aberto de minha autoria focada em facilitar a criação de objetos para testes.

Nos artigos anteriores da série, exploramos como o uso de test builders pode simplificar a construção de testes.
Neste artigo, apresento a biblioteca ForeverFactory, que se propõe a simplificar a implementação de builders ao mesmo tempo que maximiza a reutilização de configurações.
#Por que criei a ForeverFactory?
Gosto muito da abordagem de escrever test builders artesanais, totalmente customizados para os cenários de um software, como apresentado neste outro artigo, fazê-los sempre desta forma pode ser bastante repetitivo.
Um dos problemas dessa abordagem, é que toda vez que quisermos customizar a configuração de uma propriedade, precisamos criar um novo método no nosso builder:
- Temos um lugar centralizado para configurar uma "pessoa padrão"
- Foi necessário implementar na mão um novo método
ComNome(string nome)para permitir customizar cada propriedade - A partir daí, podemos customizar a propriedade
Nome
E esse é o problema que uma biblioteca bastante popular visa resolver: o NBuilder.
#NBuilder
O NBuilder nos permite evitar essa escrita repetitiva de métodos de configuração através de uma interface fluente e extensível.
A título de comparação, o exemplo anterior ficaria assim:
Como podemos ver no teste acima, o NBuilder torna possível configurar de maneira flexível qualquer propriedade dos objetos que estamos construindo, e isso por si só já resolve o problema da escrita manual desses métodos de customização.
Além disso, o NBuilder também facilita a criação de coleções de objetos, como no exemplo a seguir:
Mas essa flexibidade vem com um custo: fica mais difícil reutilizar cenários comuns.
Imagine um cenário onde nossa suíte de testes contenha 300 testes que precisem de objetos do tipo Pessoa, todos com CPFs válidos, mas com configurações ligeiramente diferentes.
Como podemos ver no exemplo acima, apesar da flexibilidade, acabamos esbarrando na duplicação de setup de teste. E este é um problema que busquei resolver com a ForeverFactory tomando inspiração de uma biblioteca do ecossistema Python, chamada FactoryBoy.
#FactoryBoy
A biblioteca FactoryBoy permite criar factories reutilizáveis, que podem ser customizados ainda mais para atender cada cenário de teste.
- A classe
PessoaFactoryguarda toda a configuração padrão de uma pessoa, e configura características importantes, como por exemplo, toda pessoa possui um CPF - Apesar do teste customizar somente a propriedade idade, o resto das propriedades, como o CPF, serão herdadas das configurações definidas na classe
PessoaFactory
Assim, cada teste fica mais focado nos aspectos mais importantes aquele cenário específico, aumentando a consição sem abrir mão de configurar corretamente cada objeto.
Como pudemos ver no exemplo acima, assim como fazíamos nos nossos builders feitos na mão, a biblioteca FactoryBoy nos permite maximizar a reutilização de configurações provendo um ponto centralizado para formalizar essas configurações.
#ForeverFactory
Construí a biblioteca ForeverFactory buscando inspiração nessas duas excelentes ferramentas, com o objetivo de juntar essas duas características numa única biblioteca:
- A flexibilidade de configuração do NBuilder
- A reutilização de configurações através uma factory centralizada do FactoryBoy
A título de exemplo, a seguir temos um teste simples escrito com ForeverFactory:
- Teste sem factory customizada, como no NBuilder
- Teste com uma factory customizada
PessoaFactory, como no FactoryBoy
Nas seções a seguir, veremos em mais detalhes o que dá pra fazer com o ForeverFactory, e como tirar proveito da biblioteca para escrever testes enxutos e concisos.
#Criando objetos com ForeverFactory
Como vimos no exemplo acima, podemos facilmente criar objetos no mesmo estilo do NBuilder:
Além disso, também podemos criar coleções de objetos utilizando o método Many(x):
Ao criar coleções de objetos, podemos fazer também algumas configurações extras:
- As primeiras 50 pessoas terão idade 17
- As últimas 50 pessoas terão idade 18
#Factories customizadas
Podemos criar factories customizadas extendendo a classe MagicFactory<T>:
- O objeto
customizationnos permite configurar como uma "Pessoa padrão" deve ser construída - O método
Setinstrui a factory a inicializar a propriedade com o valor informado. Este valor pode ser sobrescrito posteriormente, caso-a-caso
#Criando conjuntos complexos de objetos
Considere uma factory customizada como a classe PessoaFactory abaixo:
PessoaFactory.cs
Com ela, podemos criar conjuntos mais complexos para nossos cenários de teste, conforme necessário:
Teste.cs
- Cria 10 instâncias de
Pessoacom a configuração{ CPF = "xxx.xxx.xxx-xx", Nome = "Albert Einstein", Idade = 17 } - Cria 10 instâncias de
Pessoacom a configuração{ CPF = "xxx.xxx.xxx-xx", Nome = "Albert Einstein", Idade = 18 } - Cria 9 instâncias de
Pessoacom a configuração{ CPF = "xxx.xxx.xxx-xx", Nome = "Albert Einstein", Idade = 100 } - Cria 1 instância de
Pessoacom a configuração{ CPF = "xxx.xxx.xxx-xx", Nome = "Stephen Hawking", Idade = 56 }
#Benchmark
Para fins de comparação, montei alguns cenários benchmark de configuração equivalente comparando ForeverFactory 4.0.2 e NBuilder 6.1.0:
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Allocated |
|---|---|---|---|---|---|---|
| BuildSingleObjectForeverFactory | 683.6 ns | 7.34 ns | 6.86 ns | 0.1373 | - | 1,152 B |
| BuildSingleObjectNBuilder | 1,939.7 ns | 20.81 ns | 19.47 ns | 0.0935 | - | 784 B |
| BuildThousandObjectsForeverFactory | 243,524.0 ns | 3,502.33 ns | 2,924.61 ns | 53.7109 | 5.8594 | 449,403 B |
| BuildThousandObjectsNBuilder | 1,555,241.7 ns | 17,075.19 ns | 14,258.56 ns | 76.1719 | 15.6250 | 653,402 B |
Estes resultados foram obtidos com base nos seguintes testes de benchmark:
#Conclusão
A biblioteca ForeverFactory nos permite criar objetos para teste de forma flexível e reaproveitar configurações importantes através da implementação de factories customizadas. Além disso, em cenários equivalentes, chega a ser 6x mais rápida que o NBuilder.
Podemos utilizá-la para criar testes mais focados nos detalhes que importam para cada cenário, e estender cenários base válidos.