2016-08-05 32 views
5

Niedawno miałem następujący błąd w moim kodzie, który zabrał mnie na zawsze do debugowania. Chciałem wstrzyknąć instancji na podstawie jego interfejs jak poniżej:Jak uniknąć lub UnitTest przypadkowego użycia konkretnego typu singleton zamiast jego abstrakcji z StructureMap

MovementController(IMotorController motorController) 

Jednak przypadkowo użył konkretny typ takiego:

MovementController(MotorController motorController) 

Projekt nadal budowane i pobiegł w porządku, dopóki nie próbował uzyskać dostęp do motorController z instancji MovementController. Ponieważ podstawowa implementacja IMotorController uzyskuje dostęp do sprzętu, musi to być kod pojedynczej lub mojej blokady. Ponieważ jednak miałem inne klasy z wtryskiwanym IMotorController, miałem teraz dwa instancje MotorController w moim grafie obiektowym, które zarówno uzyskiwały dostęp do sprzętu przez połączenie szeregowe. Spowodowało to błąd w czasie wykonywania na znacznie niższym poziomie, co zabrało mnie na zawsze do debugowania i znalezienia prawdziwej przyczyny.

Jak mogę uniknąć tego typu błędów lub napisać test jednostkowy dla mojego rejestru StructureMap, aby wykryć ten subtelny błąd?

+0

Zobaczmy, rozumiem: Chcesz test jednostkowy, aby sprawdzić, czy kontrolery mają tylko interfejsy jako argumenty konstruktora? jaki to jest projekt? MVC, winform, wpf ...? – Nkosi

Odpowiedz

0

Ok. rozwiązanie wpadłem na mojego badanej jednostki tak, jest, aby wszystkie instancje, które implementują IMotorController i twierdzą, że ich liczba jest równa 1:

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

Nie wiem, to jest najbardziej elegancki sposób, ale wydaje się działać .

Aktualizacja 1: Ten kod robi nie złapać bug miałem. Wciąż szukam poprawnej odpowiedzi na mój problem.

Aktualizacja 2: Zbliżam się. To będzie co najmniej złapać, jeśli przypadkowo zarejestrowany konkretnego typu odpowiedniego interfejsu. Jednak nie wydaje się, aby sprawdzić, czy faktycznie została zbudowana jego instancja.

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

To pytanie jest dla mnie bardzo interesujące. Mam kilka pytań, aby sprawdzić, czy moje rozwiązanie można zastosować do Twojej sytuacji. Nie jestem zaznajomiony z mapą struktury, ale czy wiesz, w jaki sposób można zidentyfikować wszystkie odwzorowania w konfiguracji i klasach, w których stosujesz interfejsy? – Nkosi

+0

@Nkosi Niestety nie wiem, czy i jak to jest możliwe. Byłoby jednak bardzo interesujące dla mnie, aby potwierdzić moją konfigurację. Na pewno będę się nad tym zastanawiać w przyszłości. Jednak znalazłem trochę postu SO, gdzie wspomniany plakat, że najlepszym sposobem, jaki znalazł, było sprawienie, by pole było odniesieniem do obiektu, było przechowywane publicznie i bezpośrednie zweryfikowanie tego pola. To nie wydaje się piękne, ale plakat powiedział, że nie znalazł lepszego sposobu. Niestety nie mam już linku i nie wiem, o której wersji StructureMap mówił. – packoman

+0

Ok. dobrze dla projektu ASP.Net MVC Używałem DI i chciałem upewnić się, że wszystkie moje kontrolery były wstrzykiwane tylko z interfejsami lub klasami abstrakcyjnymi. Brak konkretnych wdrożeń. Napisałem więc test jednostkowy, który sprawdza wszystkie kontrolery (MVC i WEB API), aby upewnić się, że ich konstruktory nie mają żadnych klas, które są wtryskiwane do kontrolerów, tylko abstrakcje. Tak więc byłem w stanie zidentyfikować te klasy odpowiednio przez ich dziedziczenie "IController" i "ApiController", a następnie sprawdzić ich konstruktorów. Z twojego przykładu nie byłem w stanie powiedzieć, jaki typ projektu miałeś. – Nkosi

0

Najbezpieczniejszym rozwiązaniem jest sprawdzenie w czasie wykonywania, który jest tworzony tylko jedna instancja MotorController. Na przykład można policzyć liczbę wystąpień MotorController ze statycznym zmiennej licznika:

public class MotorController : IMotorController 
{ 
    private static bool instantiated; 

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

     ... 

     instantiated = true; 
    } 

    ... 
} 

ja zwykle uważają ten zły projekt, bo czy klasa jest używana jako pojedyncza lub nie jest czymś tylko wtrysk zależność ramy powinny dbać o. Zwróć też uwagę, że nie jest to bezpieczne dla wątków.

0

Próbując przylegają do D w SOLID

Dependency zasadzie inwersji gdzie jeden powinien „zależeć abstrakcje. Nie zależą od konkrecji

dla projektu. W tym przypadku Asp.Net-MVC5, chciałem, aby upewnić się, że wszystkie kontrolery (MVC i WebAPI2) podążały za tym wzorcem, w którym nie były zależne od konkrecji.

Pierwotny pomysł pochodzi z artykułu, który przeczytałem, gdzie utworzono test jednostkowy, aby zeskanować wszystkie kontrolery, aby upewnić się, że zostały zdefiniowane wyraźne uprawnienia. Zastosowałem podobne myślenie, sprawdzając, czy wszystkie kontrolery mają konstruktory zależne od abstrakcji.

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

Tak biorąc pod uwagę następujący przykład. (Zauważ, że ten przystosowany do MVC)

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

public interface IMotorController { 
    //... 
} 

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

test jednostka nie powiedzie się z ...

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

ten pracował dla mnie, bo miałem wspólny typ szukać z IController i ApiController.

Jest miejsce na ulepszenie w teście, ale powinno być dobrym punktem wyjścia dla ciebie.

1

Można to łatwo sprawdzić za pomocą statycznego narzędzia analitycznego, takiego jak NDepend. Dzięki niemu po prostu szukałeś typów kontrolerów, a następnie sprawdzałeś ich konstruktory i ostrzegałeś, gdybyś znalazł parametry konstruktorów, które nie były typami interfejsów.


Wystarczy dopracować odpowiedź Steve, można napisać regułę kodu, który mógłby wyglądać tak: (z NDepend reguła kod jest C# LINQ query prefiksem warnif count > 0)

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

Reguła może być rafinowane łatwo, jeśli jest zero lub wiele ClassThatCanUseMotorController.

Powiązane problemy