Melhorando a Testabilidade do Código com Factory e Adapter
A testabilidade do código é um fator essencial para garantir a qualidade e a manutenção do software. Quando lidamos com classes que não são interfaces ou que possuem dependências complexas, testar esses componentes pode se tornar um desafio. Neste post, vamos explorar como podemos melhorar a testabilidade do código usando dois padrões de projeto: Adapter e Factory.
O Problema
Suponha que temos uma classe Process
, que representa um processo do sistema operacional. No entanto, essa classe não implementa uma interface e possui métodos que fazem chamadas diretas ao sistema, o que dificulta a substituição dessa dependência nos testes.
Para resolver esse problema, utilizaremos o padrão Adapter para encapsular a classe Process
em uma interface testável e o padrão Factory para criar instâncias dessa interface de forma flexível.
Implementação do Adapter
A primeira etapa é criar uma interface para representar a funcionalidade do Process
de forma genérica e testável:
public interface IProcessAdapter
{
void BeginErrorReadLine();
void BeginOutputReadLine();
void Close();
void SetErrorDataReceived(Action<string?> action);
void SetOutputDataReceived(Action<string?> action);
void SetStartInfoArguments(string arguments);
void SetStartInfoFileName(string fileName);
void SetStartInfoRedirectStandardError(bool redirectStandardError);
void SetStartInfoRedirectStandardOutput(bool redirectStandardOutput);
void SetStartInfoUseShellExecute(bool useShellExecute);
void SetStartInfoVerb(string verb);
bool Start();
Task WaitForExitAsync(CancellationToken cancellationToken = default);
}
Agora, criamos uma classe ProcessAdapter
que implementa essa interface e encapsula a funcionalidade da classe Process
:
public sealed class ProcessAdapter : IProcessAdapter
{
private readonly Process _process;
private readonly ProcessStartInfo _processStartInfo;
public ProcessAdapter()
{
_process = new Process();
_processStartInfo = new ProcessStartInfo();
_process.StartInfo = _processStartInfo;
}
public void BeginErrorReadLine() => _process.BeginErrorReadLine();
public void BeginOutputReadLine() => _process.BeginOutputReadLine();
public void Close() => _process.Close();
public void SetErrorDataReceived(Action<string?> action) =>
_process.ErrorDataReceived += (sender, args) => action(args.Data);
public void SetOutputDataReceived(Action<string?> action) =>
_process.OutputDataReceived += (sender, args) => action(args.Data);
public void SetStartInfoArguments(string arguments) =>
_processStartInfo.Arguments = arguments;
public void SetStartInfoFileName(string fileName) =>
_processStartInfo.FileName = fileName;
public void SetStartInfoRedirectStandardError(bool redirectStandardError) =>
_processStartInfo.RedirectStandardError = redirectStandardError;
public void SetStartInfoRedirectStandardOutput(bool redirectStandardOutput) =>
_processStartInfo.RedirectStandardOutput = redirectStandardOutput;
public void SetStartInfoUseShellExecute(bool useShellExecute) =>
_processStartInfo.UseShellExecute = useShellExecute;
public void SetStartInfoVerb(string verb) =>
_processStartInfo.Verb = verb;
public bool Start() => _process.Start();
public Task WaitForExitAsync(CancellationToken cancellationToken = default) =>
_process.WaitForExitAsync(cancellationToken);
}
Implementação do Factory
Agora, para facilitar a criação e a substituição do ProcessAdapter
nos testes, usamos um Factory para encapsular a instância:
public interface IProcessAdapterFactory
{
IProcessAdapter Create();
}
public sealed class ProcessAdapterFactory : IProcessAdapterFactory
{
public IProcessAdapter Create() => new ProcessAdapter();
}
Benefícios da Abordagem
Com essa implementação, conseguimos:
- Facilitar a injeção de dependência: Podemos injetar
IProcessAdapterFactory
nos serviços, permitindo a criação flexível de processos. - Melhorar a testabilidade: Nos testes, podemos substituir
ProcessAdapter
por um mock da interfaceIProcessAdapter
, sem depender de processos reais do sistema operacional. - Encapsular a complexidade: O
ProcessAdapter
encapsula detalhes internos da classeProcess
, proporcionando uma interface mais controlada e previsível.
Conclusão
Os padrões Adapter e Factory são ferramentas poderosas para melhorar a testabilidade e a flexibilidade do código. Adaptando classes que não seguem princípios SOLID para interfaces testáveis e utilizando fábricas para criar instâncias controladas, conseguimos construir um código mais modular, reutilizável e fácil de testar.
Se você já teve dificuldades em testar código acoplado a classes do sistema, experimente essa abordagem e veja a diferença!
0 Comments