2016-08-05 23 views
5

Ich hatte vor kurzem den folgenden Fehler in meinem Code, der mich für immer zu debuggen. Ich wollte eine Instanz basierend auf seiner Oberfläche so injizieren:Wie zu vermeiden oder UnitTest die versehentliche Verwendung eines konkreten Singleton-Typ anstelle seiner Abstraktion mit StructureMap

MovementController(IMotorController motorController) 

Jedoch habe ich die konkrete Art, wie diese aus Versehen verwendet:

MovementController(MotorController motorController) 

Das Projekt immer noch gebaut und lief gut, bis ich versuchte, die für den Zugriff auf motorController aus der MovementController Instanz. Da die zugrunde liegende Implementierung von IMotorController auf Hardware zugreift, muss es sich um einen Singleton oder meinen Sperrcode handeln. Da ich aber andere Klassen mit dem injizierten IMotorController hatte, hatte ich nun zwei Instanzen MotorController in meinem Objekt-Graph, die beide über eine serielle Verbindung auf die Hardware zugegriffen haben. Dies verursachte einen Fehler zur Laufzeit auf einer viel niedrigeren Ebene, der mich für immer benötigte, um die wahre Ursache zu finden und zu finden.

Wie kann ich diese Art von Fehler vermeiden oder einen Komponententest für meine StructureMap-Registrierung schreiben, um diesen kleinen Fehler zu erkennen?

+0

Mal sehen, ich verstehe: Sie wollen einen Komponententest, um zu überprüfen, dass Ihre Controller nur Schnittstellen als Konstruktor Argumente haben? Was für ein Projekt ist das? MVC, winform, wpf ...? – Nkosi

Antwort

0

Ok. So ist die Lösung, die ich mit meiner Einheit Test kam, ist es, alle Instanzen zu erhalten, die IMotorController implementieren und behaupten, dass ihr Zählwert gleich 1:

var motorControllerInstances = container.GetAllInstances<IMotorController>().Select(x => x); // cast enumerable to List using Linq 
Assert.True(motorControllerInstances.Count == 1); 

nicht sicher, dass dies die eleganteste Art und Weise ist, aber es scheint zu funktionieren .

Update 1: Dieser Code führt nicht der Bug Fang ich hatte. Ich suche immer noch nach einer richtigen Antwort auf mein Problem.

Update 2: Ich komme näher. Dies fängt zumindest an, wenn Sie versehentlich einen konkreten Typ der entsprechenden Schnittstelle registriert haben. Es scheint jedoch nicht zu überprüfen, ob eine Instanz tatsächlich erstellt wurde.

var allInterfaceInstances = dicFixture.result.Model.GetAllPossible<IMotorController>(); 
    Assert.True(allInterfaceInstance.Count() == 1); 
+0

Diese Frage ist sehr interessant für mich. Ich habe ein paar Fragen, um zu sehen, ob meine Lösung auf Ihre Situation angewendet werden kann. Ich bin mit der Strukturkarte nicht vertraut, aber wissen Sie, ob es eine Möglichkeit gibt, alle Zuordnungen im Setup und die Klassen, in denen Sie die Schnittstellen anwenden, zu identifizieren? – Nkosi

+0

@Nkosi Ich weiß leider nicht ob oder wie das möglich ist. Das wäre für mich aber auch sehr interessant, um meine Konfiguration zu validieren. Darauf werde ich in Zukunft definitiv schauen. Ich fand jedoch einen SO-Post, wo das Poster erwähnte, dass der beste Weg, den er fand, darin bestand, das Feld so zu machen, dass die Objektreferenz öffentlich gespeichert wurde und dieses Feld direkt zu überprüfen. Das scheint nicht schön, aber das Poster hat gesagt, dass er keinen besseren Weg gefunden hat. Leider habe ich den Link nicht mehr und weiß nicht, über welche Version von StructureMap er gesprochen hat. – packoman

+0

Ok. Gut für ein ASP.Net MVC-Projekt Ich benutzte DI und wollte sicherstellen, dass alle meine Controller nur Interfaces oder abstrakte Klassen injiziert wurden. Keine konkreten Implementierungen Also schrieb ich einen Komponententest, der alle Controller (MVC und WEB API) überprüft, um sicherzustellen, dass ihre Konstruktoren keine Klassen in die Controller injiziert haben, sondern nur Abstraktionen. So konnte ich diese Klassen anhand ihrer IController- bzw. ApiController-Vererbung identifizieren und dann ihre Konstruktoren überprüfen. Von Ihrem Beispiel konnte ich nicht sagen, welche Art von Projekt Sie hatten. – Nkosi

0

Die sicherste Lösung ist während der Laufzeit, die nur eine Instanz von MotorController erstellt zu überprüfen. Zum Beispiel könnten Sie die Anzahl der Instanzen von MotorController mit einer statischen Zählvariable zählen:

public class MotorController : IMotorController 
{ 
    private static bool instantiated; 

    public MotorController(...) 
    { 
     if (instantiated) 
      throw new InvalidOperationException(
       "MotorController can only be instantiated once.") 

     ... 

     instantiated = true; 
    } 

    ... 
} 

würde ich in der Regel dieses schlechte Design betrachten, denn ob eine Klasse als Singleton verwendet wird oder nicht, sind etwas, das nur die Dependency Injection Framework sollte sich darum kümmern. Beachten Sie auch, dass dies nicht Thread-sicher ist.

0

Bei dem Versuch, auf die D in SOLID

Dependency Inversion Prinzip einzuhalten, wo ein „auf Abstraktionen abhängen sollte. Verlassen Sie sich nicht auf Konkretionen

für ein Projekt. In diesem Fall, Asp.Net-MVC5, wollte ich sicherstellen, dass alle Controller (MVC und WebAPI2) diesem Muster folgen, wo sie nicht von Konkretionen abhängig sind.

Die ursprüngliche Idee kam von einem Artikel, den ich gelesen hatte, wo ein Komponententest erstellt wurde, um alle Controller zu scannen, um sicherzustellen, dass sie eine explizite Autorisierung definiert hatten. Ich habe ähnliche Überlegungen angestellt, indem ich überprüft habe, ob alle Controller Konstruktoren haben, die von Abstraktionen abhängen.

[TestClass] 
public class ControllerDependencyTests : ControllerUnitTests { 
    [TestMethod] 
    public void All_Controllers_Should_Depend_Upon_Abstractions() { 

     var controllers = UnitTestHelper.GetAssemblySources() //note this is custom code to get the assemblies to reflect. 
      .SelectMany(assembly => assembly.GetTypes()) 
      .Where(t => typeof(IController).IsAssignableFrom(t) || typeof(System.Web.Http.Controllers.IHttpController).IsAssignableFrom(t)); 

     var constructors = controllers 
      .SelectMany(type => type.GetConstructors()) 
      .Where(constructor => { 

       var parameters = constructor.GetParameters(); 
       var result = constructor.IsPublic 
        && parameters.Length > 0 
        && parameters.Any(arg => arg.ParameterType.IsClass && !arg.ParameterType.IsAbstract); 

       return result; 
      }); 
     // produce a test failure error mssage if any controllers are uncovered 
     if (constructors.Any()) { 
      var errorStrings = constructors 
       .Select(c => { 
        var parameters = string.Join(", ", c.GetParameters().Select(p => string.Format("{0} {1}", p.ParameterType.Name, p.Name))); 
        var ctor = string.Format("{0}({1})", c.DeclaringType.Name, parameters); 
        return ctor; 
       }).Distinct(); 

      Assert.Fail(String.Format("\nType depends on concretion instead of its abstraction.\n{0} Found :\n{1}", 
          errorStrings.Count(), 
          String.Join(Environment.NewLine, errorStrings))); 
     } 
    } 
} 

Also das folgende Beispiel gegeben. (Man beachte ich das MVC angepasst)

public class MovementController : Controller { 
    public MovementController(MotorController motorController) { 
     //... 
    } 
} 

public interface IMotorController { 
    //... 
} 

public class MotorController : IMotorController { 
    //... 
} 

der Unit-Test fehlschlagen würde mit ...

Result Message: Assert.Fail failed. 
Type depends on concretion instead of its abstraction. 
1 Found : 
MovementController(MotorController motorController) 

Das ist für mich gearbeitet, weil ich eine gemeinsame Art hatte zu suchen mit dem IController und ApiController.

Es gibt Raum für Verbesserungen im Test, aber es sollte ein guter Ausgangspunkt für Sie sein.

1

Sie könnten leicht mit einem statischen Analysetool wie NDepend nach diesem suchen. Damit würden Sie nur nach Typen suchen, die Controller waren, und dann ihre Konstruktoren überprüfen und warnen, wenn Sie Konstruktorparameter gefunden haben, die keine Schnittstellentypen waren.


einfach die Steve Antwort zu verfeinern, können Sie eine Coderegel schreiben, die wie folgt aussehen könnte: (mit NDepend eine Coderegel ist ein C# LINQ query mit warnif count > 0 Präfix)

// <Name>Don't use MotorController, use IMotorController instead</Name> 
warnif count > 0 
from m in Application.Methods 
where m.IsUsing ("NamespaceA.MotorController ") && 
     m.ParentType.FullName != "NamespaceB.ClassThatCanUseMotorController " 
select m 

Die Regel verfeinert werden können leicht, wenn es keine oder viele ClassThatCanUseMotorController gibt.