Как я могу избежать глобального состояния?
Итак, я читал блог по тестированию Google, и там говорится, что глобальное состояние плохое и затрудняет написание тестов. Я верю в это - мой код сейчас сложно протестировать. Итак, как мне избежать глобального состояния?
Самая большая вещь, для которой я использую глобальное состояние (насколько я понимаю), это управление ключевыми частями информации между нашими средами разработки, принятия и производства. Например, у меня есть статический класс с именем "Globals" со статическим членом с именем "DBConnectionString". Когда приложение загружается, оно определяет, какую строку подключения загрузить, и заполняет Globals.DBConnectionString. Я загружаю пути к файлам, имена серверов и другую информацию в классе Globals.
Некоторые из моих функций зависят от глобальных переменных. Поэтому, когда я тестирую свои функции, я должен не забывать сначала устанавливать определенные глобальные переменные, иначе тесты не пройдут. Я хотел бы избежать этого.
Есть ли хороший способ управления информацией о состоянии? (Или я неправильно понимаю глобальное состояние?)
4 ответа
Инъекция зависимости - это то, что вы ищете. Вместо того, чтобы эти функции выходили и искали их зависимости, вставьте зависимости в функции. То есть, когда вы вызываете функции, они передают нужные им данные. Таким образом, легко создать среду тестирования вокруг класса, потому что вы можете просто внедрить фиктивные объекты в случае необходимости.
Трудно избежать некоторого глобального состояния, но лучший способ сделать это - использовать фабричные классы на самом высоком уровне вашего приложения, и все, что находится ниже этого самого верхнего уровня, основано на внедрении зависимостей.
Два основных преимущества: во-первых, тестирование намного проще, и во-вторых, ваше приложение намного слабее связано. Вы полагаетесь на способность программировать с использованием интерфейса класса, а не его реализации.
Имейте в виду, что если ваши тесты связаны с реальными ресурсами, такими как базы данных или файловые системы, тогда вы выполняете интеграционные тесты, а не юнит-тесты. Интеграционные тесты требуют предварительной настройки, в то время как модульные тесты должны выполняться независимо.
Вы могли бы изучить использование инфраструктуры внедрения зависимостей, такой как 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.
Отличный первый вопрос.
Краткий ответ: убедитесь, что ваше приложение является функцией от ВСЕХ входов (включая неявные) до выходов.
Проблема, которую вы описываете, не похожа на глобальное состояние. По крайней мере, не изменяемое состояние. Скорее, то, что вы описываете, похоже на то, что часто называют "проблемой конфигурации", и имеет ряд решений. Если вы используете Java, вы можете захотеть взглянуть на легковесные интегрированные среды, такие как Guice. В Scala это обычно решается с последствиями. На некоторых языках вы сможете загрузить другую программу для настройки вашей программы во время выполнения. Так мы привыкли настраивать серверы, написанные на Smalltalk, и я использую менеджер окон, написанный на Haskell, который называется Xmonad, чей конфигурационный файл - просто еще одна программа на Haskell.
Пример внедрения зависимости в настройке 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 (объекта доступа к данным), который зависит от объекта базы данных, в зависимости от драйвера базы данных, имени и т. Д.