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
PessoaFactory
guarda 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
customization
nos permite configurar como uma "Pessoa padrão" deve ser construída - O método
Set
instrui 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
Pessoa
com a configuração{ CPF = "xxx.xxx.xxx-xx", Nome = "Albert Einstein", Idade = 17 }
- Cria 10 instâncias de
Pessoa
com a configuração{ CPF = "xxx.xxx.xxx-xx", Nome = "Albert Einstein", Idade = 18 }
- Cria 9 instâncias de
Pessoa
com a configuração{ CPF = "xxx.xxx.xxx-xx", Nome = "Albert Einstein", Idade = 100 }
- Cria 1 instância de
Pessoa
com 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.