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:

C#
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:

C#
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:

C#
public interface IProcessAdapterFactory
{
    IProcessAdapter Create();
}

public sealed class ProcessAdapterFactory : IProcessAdapterFactory
{
    public IProcessAdapter Create() => new ProcessAdapter();
}

Benefícios da Abordagem

Com essa implementação, conseguimos:

  1. Facilitar a injeção de dependência: Podemos injetar IProcessAdapterFactory nos serviços, permitindo a criação flexível de processos.
  2. Melhorar a testabilidade: Nos testes, podemos substituir ProcessAdapter por um mock da interface IProcessAdapter, sem depender de processos reais do sistema operacional.
  3. Encapsular a complexidade: O ProcessAdapter encapsula detalhes internos da classe Process, 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

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>