Как я могу избежать глобального состояния? - PullRequest
10 голосов
/ 04 сентября 2008

Итак, я читал блог по тестированию Google, в котором говорится, что глобальное состояние плохое и затрудняет написание тестов. Я верю в это - мой код сейчас сложно протестировать. Итак, как мне избежать глобального состояния?

Самая большая вещь, для которой я использую глобальное состояние (насколько я понимаю), это управление ключевыми частями информации между нашей средой разработки, принятия и производства. Например, у меня есть статический класс с именем «Globals» со статическим членом с именем «DBConnectionString». Когда приложение загружается, оно определяет, какую строку подключения загрузить, и заполняет Globals.DBConnectionString. Я загружаю пути к файлам, имена серверов и другую информацию в классе Globals.

Некоторые из моих функций основаны на глобальных переменных. Поэтому, когда я проверяю свои функции, я должен не забывать сначала устанавливать определенные глобальные переменные, иначе тесты не пройдут. Я бы хотел этого избежать.

Есть ли хороший способ управления информацией о состоянии? (Или я неправильно понимаю глобальное состояние?)

Ответы [ 4 ]

12 голосов
/ 04 сентября 2008

Инъекция зависимости - это то, что вы ищете. Вместо того, чтобы эти функции выходили и искали их зависимости, вставьте зависимости в функции. То есть, когда вы вызываете функции, они передают нужные им данные. Таким образом, легко создать среду тестирования вокруг класса, потому что вы можете просто внедрить фиктивные объекты, где это уместно.

Трудно избежать некоторого глобального состояния, но лучший способ сделать это - использовать фабричные классы на самом высоком уровне вашего приложения, и все, что ниже этого самого верхнего уровня, основано на внедрении зависимостей.

Два основных преимущества: во-первых, тестирование намного проще, и во-вторых, ваше приложение намного слабее связано. Вы полагаетесь на возможность программировать с использованием интерфейса класса, а не его реализации.

2 голосов
/ 04 сентября 2008

Имейте в виду, что если в ваших тестах задействованы реальные ресурсы, такие как базы данных или файловые системы, то вы будете интеграционные тесты , а не модульные тесты . Интеграционные тесты требуют предварительной настройки, в то время как модульные тесты должны выполняться независимо.

Вы можете изучить использование инфраструктуры внедрения зависимостей, такой как Castle Windsor, но в простых случаях вы можете выбрать подход на середине пути, например:

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
    }
}

В действительности вы, скорее всего, читали бы из файлов конфигурации в вашей реализации. Если вы готовы, то лучше всего использовать полнофункциональную DI-инфраструктуру с заменяемыми конфигурациями, но я думаю, что это, по крайней мере, лучше, чем использование Globals.ConnectionString.

1 голос
/ 04 сентября 2008

Отличный первый вопрос.

Краткий ответ: убедитесь, что ваше приложение является функцией от ВСЕХ входов (включая неявные) до выходов.

Проблема, которую вы описываете, не похожа на глобальное состояние. По крайней мере, не изменяемое состояние. Скорее, то, что вы описываете, похоже на то, что часто называют «проблемой конфигурации», и имеет ряд решений. Если вы используете Java, возможно, вы захотите взглянуть на легковесные интегрированные среды, такие как Guice . В Scala это обычно решается с имплицитами . На некоторых языках вы сможете загрузить другую программу для настройки вашей программы во время выполнения. Именно так мы настраивали серверы, написанные на Smalltalk, и я использую менеджер окон, написанный на Haskell, который называется Xmonad, чей конфигурационный файл - просто еще одна программа на Haskell.

0 голосов
/ 31 января 2014

Пример внедрения зависимости в настройке MVC, здесь идет:

index.php

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

container.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 продолжается здесь:

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

И у вас это есть, контроллер зависит от объекта уровня сервиса, который зависит от объект dao (объект доступа к данным), который зависит от объекта базы данных с зависит от драйвер базы данных, имя и т. д.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...