Pergunta

Então, eu estava lendo o blog de testes do Google e ele diz que o estado global é ruim e dificulta a escrita de testes.Eu acredito – meu código é difícil de testar agora.Então, como posso evitar o estado global?

A principal coisa para a qual uso o estado global (como eu o entendo) é gerenciar informações importantes entre nossos ambientes de desenvolvimento, aceitação e produção.Por exemplo, tenho uma classe estática chamada "Globals" com um membro estático chamado "DBConnectionString". Quando o aplicativo é carregado, ele determina qual sequência de conexão para carregar e preenche o globals.dbconnectionString.Carrego caminhos de arquivos, nomes de servidores e outras informações na classe Globals.

Algumas das minhas funções dependem de variáveis ​​globais.Portanto, quando testo minhas funções, preciso me lembrar de definir primeiro alguns globais, caso contrário os testes falharão.Eu gostaria de evitar isso.

Existe uma boa maneira de gerenciar informações de estado?(Ou estou entendendo o estado global incorretamente?)

Foi útil?

Solução

Injeção de dependência é o que você está procurando.Em vez de fazer com que essas funções saiam e procurem suas dependências, injete as dependências nas funções.Ou seja, quando você chama as funções passam os dados que elas desejam para elas.Dessa forma, é fácil colocar uma estrutura de teste em torno de uma classe porque você pode simplesmente injetar objetos simulados quando apropriado.

É difícil evitar algum estado global, mas a melhor maneira de fazer isso é usar classes de fábrica no nível mais alto do seu aplicativo, e tudo abaixo desse nível superior é baseado na injeção de dependência.

Dois benefícios principais:primeiro, o teste é muito mais fácil e, segundo, seu aplicativo é muito mais flexível.Você confia na capacidade de programar na interface de uma classe, e não em sua implementação.

Outras dicas

Lembre-se de que se seus testes envolvem recursos reais, como bancos de dados ou sistemas de arquivos, o que você está fazendo é testes de integração em vez de testes unitários.Os testes de integração requerem alguma configuração preliminar, enquanto os testes de unidade devem poder ser executados de forma independente.

Você pode considerar o uso de uma estrutura de injeção de dependência, como Castle Windsor, mas para casos simples, você pode adotar uma abordagem intermediária, como:

public interface ISettingsProvider
{
    string ConnectionString { get; }
}

public class TestSettings : ISettingsProvider
{        
    public string ConnectionString { get { return "testdatabase"; } };
}

public class DataStuff
{
    private ISettingsProvider settings;

    public DataStuff(ISettingsProvider settings)
    {
        this.settings = settings;
    }

    public void DoSomething()
    {
        // use settings.ConnectionString
    }
}

Na realidade, você provavelmente leria os arquivos de configuração em sua implementação.Se você quiser, uma estrutura de DI completa com configurações trocáveis ​​é o caminho a seguir, mas acho que isso é pelo menos melhor do que usar Globals.ConnectionString.

Ótima primeira pergunta.

A resposta curta:certifique-se de que seu aplicativo seja uma função de TODAS as suas entradas (incluindo as implícitas) até suas saídas.

O problema que você está descrevendo não parece um estado global.Pelo menos não um estado mutável.Em vez disso, o que você está descrevendo parece ser o que costuma ser chamado de "O problema da configuração" e tem várias soluções.Se você estiver usando Java, você pode querer dar uma olhada em estruturas de injeção leves como Guice.No Scala, isso geralmente é resolvido com implícitos.Em algumas linguagens, você poderá carregar outro programa para configurá-lo em tempo de execução.É assim que costumávamos configurar servidores escritos em Smalltalk, e eu uso um gerenciador de janelas escrito em Haskell chamado Xmonad cujo arquivo de configuração é apenas mais um programa Haskell.

Um exemplo de injeção de dependência em uma configuração MVC, aqui vai:

index.php

$container = new Container();
include_file('container.php');

contêiner.php

container.add("database.driver", "mysql");
container.add("database.name","app");

...

$container.add(new Database($container->get('database.driver', "database.name")), 'database');
$container.add(new Dao($container->get('database')), 'dao');
$container.add(new Service($container->get('dao')));
$container.add(new Controller($container->get('service')), 'controller');

$container.add(new FrontController(),'frontController');

index.php continua aqui:

$frontController = $container->get('frontController');
$controllerClass = $frontController->getController($_SERVER['request_uri']);
$controllerAction = $frontController->getAction($_SERVER['request_uri']);
$controller = $container->get('controller');
$controller->$action();

E aí está, o controlador depende de um objeto de camada de serviço que depende de um objeto DAO (Data Access Object) que depende de um objeto de banco de dados depende do driver do banco de dados, nome etc.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top