2008-09-04 15 views
8

Also las ich das Google-Testblog, und es sagt, dass der globale Zustand schlecht ist und es schwierig macht, Tests zu schreiben. Ich glaube es - mein Code ist momentan schwer zu testen. Wie vermeide ich den globalen Zustand?Wie kann ich den globalen Zustand vermeiden?

Die größten Dinge, die ich global state (wie ich es verstehe) für die Verwaltung von Schlüsselinformationen zwischen unseren Entwicklungs-, Akzeptanz- und Produktionsumgebungen verwenden. Zum Beispiel habe ich eine statische Klasse namens "Globals" mit einem statischen Member namens "DBConnectionString". Wenn die Anwendung geladen wird, ermittelt sie, welche Verbindungszeichenfolge geladen werden soll, und füllt Globals.DBConnectionString auf. Ich lade Dateipfade, Servernamen und andere Informationen in der Globals-Klasse.

Einige meiner Funktionen beruhen auf den globalen Variablen. Also, wenn ich meine Funktionen teste, muss ich daran denken, bestimmte Globals zuerst zu setzen, sonst versagen die Tests. Ich möchte das vermeiden.

Gibt es eine gute Möglichkeit, Zustandsinformationen zu verwalten? (Oder verstehe ich den globalen Zustand falsch?)

Antwort

10

Abhängigkeit Injektion ist, was Sie suchen. Anstatt diese Funktionen auszulöschen und nach ihren Abhängigkeiten zu suchen, injizieren Sie die Abhängigkeiten in die Funktionen. Das heißt, wenn Sie die Funktionen aufrufen, übergeben Sie die gewünschten Daten an sie. Auf diese Weise ist es einfach, ein Test-Framework um eine Klasse herum zu platzieren, da Sie einfach Mock-Objekte injizieren können.

Es ist schwierig, einige globale Zustände zu vermeiden, aber der beste Weg, dies zu tun, ist die Verwendung von Factory-Klassen auf der höchsten Ebene Ihrer Anwendung, und alles unterhalb dieser obersten Ebene basiert auf Abhängigkeitsinjektion.

Zwei Hauptvorteile: Erstens, das Testen ist viel einfacher, und zweitens ist Ihre Anwendung viel lockerer gekoppelt. Sie verlassen sich darauf, dass Sie in der Lage sind, gegen die Schnittstelle einer Klasse zu programmieren und nicht um deren Implementierung.

1

Gute erste Frage.

Die kurze Antwort: stellen Sie sicher, dass Ihre Anwendung eine Funktion von ALLEN Eingaben (einschließlich impliziter) zu ihren Ausgaben ist.

Das Problem, das Sie beschreiben, scheint nicht wie globaler Zustand. Zumindest kein veränderbarer Zustand. Was Sie beschreiben, scheint eher das zu sein, was oft als "Konfigurationsproblem" bezeichnet wird, und es gibt eine Reihe von Lösungen. Wenn Sie Java verwenden, sollten Sie sich mit leichtgewichtigen Injektions-Frameworks wie Guice beschäftigen. In Scala wird dies normalerweise mit implicits gelöst. In einigen Sprachen können Sie ein anderes Programm laden, um Ihr Programm zur Laufzeit zu konfigurieren. So haben wir Server konfiguriert, die in Smalltalk geschrieben sind, und ich benutze einen in Haskell geschriebenen Fenstermanager namens Xmonad, dessen Konfigurationsdatei nur ein anderes Haskell-Programm ist.

2

Denken Sie daran, wenn Sie Ihre Tests tatsächlichen Ressourcen wie Datenbanken oder Dateisysteme beinhalten dann, was Sie tun sind Integrationstests statt Einheit testet. Integrationstests erfordern einige vorbereitende Einstellungen, während Komponententests unabhängig voneinander ausgeführt werden können.

Sie in der Verwendung eines Dependency Injection-Framework wie Schloss Windsor aussehen könnte, aber für einfache Fälle Sie mitten auf der Straße Ansatz in der Lage sein wie zu nehmen:

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

In Wirklichkeit würden Sie am meisten wahrscheinlich gelesen von Konfigurationsdateien in Ihrer Implementierung.Wenn Sie dafür bereit sind, ist ein vollständiges DI-Framework mit austauschbaren Konfigurationen der richtige Weg, aber ich denke, das ist zumindest besser als die Verwendung von Globals.ConnectionString.

0

Ein Beispiel für Dependency Injection in einer Einstellung MVC, hier geht:

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 fährt hier fort:

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

Und dort haben Sie es hängt der Controller auf einem Objektdienst Schicht, die auf ein dao (Datenzugriffsobjekt) Objekt, das auf einem Datenbankobjekt abhängt, hängt mit auf dem Fahrer Datenbank abhängt, Name usw.