Frage

Eine Datenbankanwendung, an der ich gerade arbeite, speichert alle möglichen Einstellungen in der Datenbank.Die meisten dieser Einstellungen dienen dazu, bestimmte Geschäftsregeln anzupassen, aber es gibt auch noch einige andere Dinge darin.

Die App enthält Objekte, die gezielt eine bestimmte Aufgabe erledigen, z. B. eine bestimmte komplizierte Berechnung.Diese Nicht-UI-Objekte werden Unit-Tests unterzogen, benötigen aber auch Zugriff auf viele dieser globalen Einstellungen.Die Art und Weise, wie wir dies jetzt implementiert haben, besteht darin, den Objekten Eigenschaften zuzuweisen, die vom Anwendungscontroller zur Laufzeit gefüllt werden.Beim Testen erstellen wir die Objekte im Test und geben Werte zum Testen ein (nicht aus der Datenbank).

Das funktioniert besser, auf jeden Fall viel besser, als wenn all diese Objekte etwas Globales benötigen Einstellungen Objekt --- das macht Unit-Tests natürlich praktisch unmöglich :) Der Nachteil kann sein, dass Sie manchmal ein Dutzend Eigenschaften festlegen müssen oder dass Sie diese Eigenschaften in Unterobjekte „durchdringen“ lassen müssen.

Die allgemeine Frage lautet also:Wie ermöglichen Sie Zugriff auf globale Anwendungseinstellungen in Ihren Projekten, ohne dass globale Variablen erforderlich sind, und können gleichzeitig Ihren Code Unit-Tests durchführen?Das muss ein Problem sein, das schon hunderte Male gelöst wurde ...

(Notiz:Ich bin kein besonders erfahrener Programmierer, wie Sie sicherlich bemerkt haben.aber ich liebe es zu lernen!Und natürlich habe ich zu diesem Thema bereits recherchiert, aber ich bin wirklich auf der Suche nach ein paar Erfahrungen aus erster Hand)

War es hilfreich?

Lösung

Sie könnten das ServiceLocator-Muster von Martin Fowlers verwenden.In PHP könnte es so aussehen:

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

Ihr Produktionscode initialisiert dann den Service Locator wie folgt:

ServiceLocator::load(new ServiceLocator());

In Ihren Testcode fügen Sie Ihre Mock-Einstellungen wie folgt ein:

ServiceLocator s = new ServiceLocator();
s->setGlobalSettings(new MockGlobalSettings());
ServiceLocator::load(s);

Es handelt sich um ein Repository für Singletons, die zu Testzwecken ausgetauscht werden können.

Andere Tipps

Ich modelliere meinen Konfigurationszugriff gerne anhand des Service Locator-Musters.Dies gibt mir einen einzigen Punkt, an dem ich jeden Konfigurationswert abrufen kann, den ich benötige, und indem ich ihn außerhalb der Anwendung in einer separaten Bibliothek ablege, ermöglicht er Wiederverwendung und Testbarkeit.Hier ist ein Beispielcode. Ich bin nicht sicher, welche Sprache Sie verwenden, aber ich habe ihn in C# geschrieben.

Zuerst erstelle ich eine generische Klasse, die mein ConfigurationItem modelliert.

public class ConfigurationItem<T>
{
    private T item;

    public ConfigurationItem(T item)
    {
        this.item = item;
    }

    public T GetValue()
    {
        return item;
    }
}

Dann erstelle ich eine Klasse, die öffentliche statische schreibgeschützte Variablen für das Konfigurationselement verfügbar macht.Hier lese ich gerade die ConnectionStringSettings aus einer Konfigurationsdatei, die nur XML ist.Natürlich können Sie für weitere Artikel die Werte aus jeder beliebigen Quelle lesen.

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"]];
    }
}

Wenn ich dann ein ConfigurationItem zur Verwendung benötige, rufe ich es so auf:

ConfigurationItems.ConnectionSettings.GetValue();

Und es gibt mir einen typsicheren Wert zurück, den ich dann zwischenspeichern oder damit machen kann, was ich will.

Hier ist ein Beispieltest:

[TestFixture]
public class ConfigurationItemsTest
{
    [Test]
    public void ShouldBeAbleToAccessConnectionStringSettings()
    {
        ConnectionStringSettings item = ConfigurationItems.ConnectionSettings.GetValue();
        Assert.IsNotNull(item);
    }
}

Hoffe das hilft.

Normalerweise wird dies durch eine INI-Datei oder XML-Konfigurationsdatei erledigt.Dann haben Sie nur noch eine Klasse, die die Einstellung bei Bedarf liest.

.NET hat dies in den ConfigurationManager-Klassen integriert, aber es ist recht einfach zu implementieren: Lesen Sie einfach Textdateien, laden Sie XML in DOM oder analysieren Sie sie manuell im Code.

Konfigurationsdateien in der Datenbank zu haben ist in Ordnung, aber es bindet Sie an die Datenbank und schafft eine zusätzliche Abhängigkeit für Ihre App, die durch INI/XML-Dateien gelöst wird.

Ich tat dies:

public class MySettings
{
    public static double Setting1
        { get { return SettingsCache.Instance.GetDouble("Setting1"); } }

    public static string Setting2
        { get { return SettingsCache.Instance.GetString("Setting2"); } }
}

Ich habe dies in ein separates Infrastrukturmodul eingefügt, um Probleme mit zirkulären Abhängigkeiten zu beseitigen.
Dabei bin ich nicht an eine bestimmte Konfigurationsmethode gebunden und es gibt keine Zeichenfolgen, die meinen Anwendungscode verwüsten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top