Приложение базы данных, над которым я сейчас работаю, хранит в базе данных всевозможные настройки. Большинство из этих настроек используются для настройки определенных бизнес-правил, но есть и некоторые другие.
Приложение содержит объекты, которые специально выполняют определенную задачу, например, сложный расчет. Эти объекты, не относящиеся к пользовательскому интерфейсу, тестируются на модулях, но также нуждаются в доступе к множеству глобальных настроек. Как мы сейчас это реализовали, так это предоставили объектам свойства, которые заполняются контроллером приложения во время выполнения. При тестировании мы создаем объекты в тесте и заполняем значения для тестирования (не из базы данных).
Это работает лучше, в любом случае намного лучше, чем если бы все эти объекты нуждались в каком-то глобальном объекте Settings --- что, конечно, фактически делает юнит-тестирование невозможным :) Недостатком может быть то, что вам иногда нужно установить дюжину свойств, или то, что вам нужно позволить этим свойствам 'просочиться' в под-объекты.
Итак, общий вопрос: как обеспечить доступ к глобальным настройкам приложения в ваших проектах, без необходимости использования глобальных переменных, при этом сохраняя возможность модульного тестирования кода? Должно быть, эта проблема решалась сотни раз...
(Примечание: я'не слишком опытный программист, как вы'заметили; но я люблю учиться! И, конечно, я уже провел исследование на эту тему, но мне действительно нужен опыт из первых рук).
Вы можете использовать паттерн ServiceLocator Мартина Фаулерса. На php это может выглядеть следующим образом:
class ServiceLocator {
private static $soleInstance;
private $globalSettings;
public static function load($locator) {
self::$soleInstance = $locator;
}
public static function globalSettings() {
if (!isset(self::$soleInstance->globalSettings)) {
self::$soleInstance->setGlobalSettings(new GlobalSettings());
}
return self::$soleInstance->globalSettings;
}
}
Затем ваш производственный код инициализирует локатор сервиса следующим образом:
ServiceLocator::load(new ServiceLocator());
В тестовом коде вы вставляете свои mock-настройки следующим образом:
ServiceLocator s = new ServiceLocator();
s->setGlobalSettings(new MockGlobalSettings());
ServiceLocator::load(s);
Это хранилище синглтонов, которыми можно обмениваться в целях тестирования.
Мне нравится модель моего доступа к конфигурации с узором Служба поиска. Это дает мне хоть один пункт, чтобы получить любое значение конфигурации, что мне нужно, и, поставив его вне приложения в отдельной библиотеке, это позволяет повторного использования и тестирования. Вот некоторые примеры кода, я не уверен, какой язык вы используете, но я написал это в C#.
Сначала я создал универсальный класс, который будет моделей мой элемент конфигурации.
public class ConfigurationItem<T>
{
private T item;
public ConfigurationItem(T item)
{
this.item = item;
}
public T GetValue()
{
return item;
}
}
Затем я создаю класс, который предоставляет открытые статические переменные readonly для элемента конфигурации. Вот я просто читала ConnectionStringSettings из файла config, который находится всего в XML. Конечно, для больше деталей, вы можете прочитать значения из любого источника.
public class ConfigurationItems
{
public static ConfigurationItem<ConnectionStringSettings> ConnectionSettings = new ConfigurationItem<ConnectionStringSettings>(RetrieveConnectionString());
private static ConnectionStringSettings RetrieveConnectionString()
{
// In .Net, we store our connection string in the application/web config file.
// We can access those values through the ConfigurationManager class.
return ConfigurationManager.ConnectionStrings[ConfigurationManager.AppSettings["ConnectionKey"]];
}
}
Потом, когда мне нужен элемент конфигурации для использования, я называю это так:
ConfigurationItems.ConnectionSettings.GetValue();
И он вернет мне типа безопасное значение, которые затем я могу кэш или делать, что хочу.
Здесь'ы опытного образца:
[TestFixture]
public class ConfigurationItemsTest
{
[Test]
public void ShouldBeAbleToAccessConnectionStringSettings()
{
ConnectionStringSettings item = ConfigurationItems.ConnectionSettings.GetValue();
Assert.IsNotNull(item);
}
}
Надеюсь, что это помогает.
Обычно для этого используется ini-файл или файл конфигурации XML. Затем у вас просто есть класс, который считывает настройки, когда это необходимо.
В .NET это встроено в классы ConfigurationManager, но это довольно легко реализовать, просто читайте текстовые файлы, или загружайте XML в DOM, или разбирайте их вручную в коде.
Наличие конфигурационных файлов в базе данных - это хорошо, но это привязывает вас к базе данных и создает дополнительную зависимость для вашего приложения, которую решают ini/xml файлы.
Я сделал это:
public class MySettings
{
public static double Setting1
{ get { return SettingsCache.Instance.GetDouble("Setting1"); } }
public static string Setting2
{ get { return SettingsCache.Instance.GetString("Setting2"); } }
}
Я поместил это в отдельный модуль инфраструктуры, чтобы устранить любые проблемы с круговыми зависимостями.
Таким образом, я не привязан к какому-либо конкретному методу конфигурации, и у меня нет строк, создающих хаос в коде моих приложений.