2012-02-20 5 views
7

Piszę serię klas kolekcji w języku C#, z których każdy realizuje podobne niestandardowe interfejsy. Czy możliwe jest napisanie pojedynczego zbioru testów jednostkowych dla interfejsu i automatyczne uruchamianie ich wszystkich w kilku różnych implementacjach? Chciałbym uniknąć powielonego kodu testowego dla każdej implementacji.Czy mogę wdrożyć serię testów wielokrotnego użytku, aby przetestować implementację interfejsu?

Jestem gotów zaglądnąć do dowolnego frameworka (NUnit itp.) Lub rozszerzenia Visual Studio, aby to osiągnąć.


Dla tych, którzy chcą zrobić to samo, wysłałem moje konkretne rozwiązanie, oparte off avandeursen's accepted solution, jak an answer.

+1

Dodano [lsp] (http://stackoverflow.com/questions/tagged/lsp) jako znacznik, ponieważ pytanie i odpowiedź odnoszą się do dowolnej hierarchii klas przylegającej do LSP. – avandeursen

Odpowiedz

6

Tak, jest to możliwe. Sztuka polega na tym, aby hierarchia testów w klasie jednostek podążała za hierarchią klas kodu.

Załóżmy, że masz interfejs Itf z implementacją klas C1 i C2.

Najpierw należy utworzyć klasę testową dla Itf (ItfTest). Aby faktycznie wykonać test, musisz utworzyć fałszywą implementację interfejsu Itf.

Wszystkie testy w tym ItfTest powinny przejść do dowolnej implementacji Itf (!). Jeśli nie, to implementacja nie są zgodne z Liskov Substitution Principle („L” w Martina SOLID zasad projektowania OO)

Dlatego, aby stworzyć przypadek testowy dla C1, klasa C1Test może przedłużyć ItfTest. Rozszerzenie powinno zastąpić tworzenie fałszywego obiektu poprzez utworzenie obiektu C1 (wstrzyknięcie go lub użycie GoF factory method). W ten sposób wszystkie przypadki ItfTest są stosowane do wystąpień typu C1. Ponadto Twoja klasa C1Test może zawierać dodatkowe przypadki testowe specyficzne dla C1.

Podobnie dla C2. I możesz powtórzyć trik dla głębiej zagnieżdżonych klas i interfejsów.

Referencje: Wzorzec Binder'a Polymorphic Server Test, i McGregora PACT - Równoległa architektura do testowania komponentów.

+1

Aby uniknąć fałszywych implementacji, po prostu oświadczyłem, że moja klasa 'ItfTest' jest abstrakcyjna i zadeklarowana jako" chroniona abstrakcyjna funkcja Itf CreateInstance(); ". (Zauważ, że zarówno 'ItfTest' i' C1Test' muszą mieć atrybut '[TestClass]'). – dlras2

+1

Tak, użyłem również metody fabrycznej, ponieważ jest to prostsze. Użyłem tego podejścia testowego w JUnit, gdzie nie ma atrybutu "[TestClass]", ale gdzie adnotacje '@ Test' w nadklasach są dziedziczone, powodując ponowne uruchamianie przypadków klasy nadklasy w podklasach. I: dzięki za edycję. – avandeursen

2

Możesz użyć atrybutów [RowTest] w MBUnit, aby to zrobić. Poniższy przykład pokazuje, gdzie zdasz metoda ciąg wskazujący jaka klasa implementacja interfejsu chcesz instancji, a następnie tworzy tę klasę za pomocą refleksji:

[RowTest] 
[Row("Class1")] 
[Row("Class2")] 
[Row("Class3")] 
public void TestMethod(string type) 
{ 
    IMyInterface foo = Activator.CreateInstance(Type.GetType(type)) as IMyInterface; 

    //Do tests on foo: 
} 

W [Wiersz] atrybutów, można przekazać dowolną liczbę parametrów wejściowych, takich jak wartości wejściowe do testowania lub wartości oczekiwane, które mają zostać zwrócone przez wywołania metod. Będziesz musiał dodać argumenty odpowiednich typów jako argumenty wejściowe metody testowej.

+0

Czy możesz edytować swoją odpowiedź, aby dokładnie wyjaśnić, w jaki sposób przetestowałbym implementacje w ten sposób? Wydaje się, że mówisz, że każdy test jednostkowy wymaga instrukcji switch dla wszystkich implementacji, co nie jest tym, czego chcę. – dlras2

+0

Nie musisz używać instrukcji switch, jeśli nie chcesz. Wspomniałem, że możesz także użyć refleksji. Zobacz przykład Anthony'ego na ten przykład. Zaktualizowałem także mój post, aby uwzględnić przykład użycia refleksji do utworzenia różnych implementacji interfejsu. –

+0

Najprawdopodobniej zapisałbym to, aby wziąć instancje 'IMyInterface' zamiast tylko nazwy, ale nadal +1. – dlras2

2

Po rozwinięciu odpowiedzi Joe's można użyć atrybutu [TestCaseSource] w NUnit w sposób podobny do testu RowTest MBUnit. Można utworzyć źródło testowania z nazwami klas tam. Następnie można udekorować każdy test, który ma atrybut TestCaseSource. Następnie za pomocą Activator.CreateInstance możesz rzutować na interfejs i będziesz ustawić.

coś takiego (uwaga - głowica skompilowany)

string[] MyClassNameList = { "Class1", "Class2" }; 

[TestCaseSource(MyClassNameList)] 
public void Test1(string className) 
{ 
    var instance = Activator.CreateInstance(Type.FromName(className)) as IMyInterface; 

    ... 
} 
1

To jest moja konkretna realizacja oparta off avandeursen's answer:

[TestClass] 
public abstract class IMyInterfaceTests 
{ 
    protected abstract IMyInterface CreateInstance(); 

    [TestMethod] 
    public void SomeTest() 
    { 
     IMyInterface instance = CreateInstance(); 
     // Run the test 
    } 
} 

Każde wdrożenie interfejsu następnie definiuje następujące klasy badawczej:

[TestClass] 
public class MyImplementationTests : IMyInterfaceTests 
{ 
    protected override IMyInterface CreateInstance() 
    { 
     return new MyImplementation(); 
    } 
} 

SomeTest jest uruchamiany raz dla każdy beton TestClass pochodzi z IMyInterfaceTests. Używając abstrakcyjnej klasy bazowej, unikam potrzeby jakichkolwiek próbnych implementacji. Pamiętaj, aby dodać TestClassAttribute do obu klas, ale to nie zadziała. Na koniec można dodać dowolne testy specyficzne dla implementacji (takie jak konstruktory) do klasy potomnej, jeśli jest to pożądane.

+0

używasz konkretnej architektury testowej? Używam dołączonych testów jednostkowych w VS2012, a testy odziedziczone z innego "udostępnionego" projektu testowego (przestrzeni nazw) nie zostaną pobrane. – drzaus

+0

dziwne - testy dziedziczone w ramach tego samego projektu, różne obszary nazw wyglądają dobrze. to tylko wtedy, gdy są w innym projekcie, którego nie rejestrują. czego mi brakuje? – drzaus

Powiązane problemy