2013-05-23 15 views
6

Widziałem wiele odpowiedzi dotyczących "jak zapełniać swoje klasy, aby można było kontrolować, co dzieje się w ramach SUT".C stubbing. Interfejs dla każdego testowalnego obiektu?

Mówią jedno:

Załóż interfejs i wstrzyknąć tego interfejsu za pomocą iniekcji zależność i stworzyć namiastkę, przy użyciu tego samego interfejsu, który następnie wstrzyknąć do SUT.

Jednak to, co nauczyłem się w moich poprzednich miejscach pracy:

Jeżeli testów jednostkowych, testowanie wszystkie Zajęcia/funkcjonalności.

Czy to znaczy, że dla każdej klasy, która posiada specyficzną funkcję DTP trzeba utworzyć interfejs?

Oznaczałoby to, że ilość klas/plików byłaby zaledwie dwa razy większa.

Jak widać w poniższym przykładzie, czy jest to "droga do wyjścia", czy też brakuje mi czegoś w procesie testowania jednostki?

Na marginesie: Używam Express VS2012. Oznacza to brak ram "Fake'a". Korzystam ze standardowej struktury testowania jednostek VS2012.

Jako bardzo, bardzo prosty przykład, który pozwala mi na otarcie każdego interfejsu przekazanego do maszyny.

IFoo.cs

public interface IFoo 
{ 
    string GetName(); 
} 

Foo.cs

public class Foo : IFoo 
{ 
    public string GetName() 
    { 
     return "logic goes here"; 
    } 
} 

IBar.cs:

public interface IBar : IFoo 
{ 
    IFoo GetFoo(); 
} 

Bar.cs:

public class Bar : IBar 
{ 
    public string GetName() 
    { 
     return "logic goes here"; 
    } 

    public IFoo GetFoo() 
    { 
     return null; // some instance of IFoo 
    } 
} 

IBaz.cs:

public interface IBaz 
{ 
    IBar GetBar(); 
} 

Baz.cs:

public class Baz 
{ 
    public IBar GetBar() 
    { 
     return null; // some instance of IBar 
    } 
} 
+3

Moja rada: znajdź dobre szydercze ramy (Moq jest dobrym wyborem), które nie wymagają tworzenia wszystkich tych interfejsów. –

+0

Pójdę rzucić okiem na ten framework –

+1

@RobertHarvey: w jaki sposób Moq pomoże uniknąć tworzenia interfejsów? OP nadal musi pozostawić dla niego punkty wejścia (Moq), czy to wirtualne elementy czy interfejsy. –

Odpowiedz

4

Tak i nie. Aby uzyskać zależność pośredniczącą, potrzebujesz jakiejś abstrakcji, ale to w większości ze względu na sposób drwi z frameworków work (nie wszystkie, oczywiście).

Rozważ prosty przykład. Testujesz klasę A, która pobiera zależności od klas B i C. Do testów jednostkowych A do pracy potrzebne są: B i C - potrzebne będą IB i IC (lub podstawowe klasy/w wirtualnych elementów). Czy potrzebujesz IA? Nie, przynajmniej nie dla tego testu. I chyba że A staje się zależne od innej klasy, abstrahowanie to za interfejs/klasa bazowa nie jest wymagane.

Abstrakcja jest świetna, ponieważ pomaga budować źle połączony kod. Powinieneś streścić swoje zależności. Jednak w praktyce niektóre klasy nie muszą być wyodrębniane, ponieważ obsługują one najwyższy poziom/koniec hierarchii/główne role i nie są używane gdzie indziej.

+0

A więc chciałbyś, aby funkcje były wirtualne, a nie interfejs (gdzie wymagany jest interfejs _nie_). Czy to przynosi dodatkowe niechciane zachowanie? Nie przeszkadzałoby mi to, że klasy dzieci dziedziczą z moich zajęć i przesłonią każdą metodę. –

+0

@DaanTimmer: To zależy od klasy. Jednak w większości przypadków chcesz używać interfejsów. Klasy podstawowe i dziedziczenie są zwykle trudniejsze do uchwycenia iw rezultacie trudniejsze do utrzymania niż kompozycja ([patrz tutaj] (http://en.wikipedia.org/wiki/Composition_over_inheritance)). Oczywiście, gdy klasa bazowa jest uzasadniona, to właśnie powinieneś zrobić. Jeszcze jeden punkt - jeśli twoim jedynym argumentem przeciwko interfejsom jest "podwajają liczbę typów", to w ogóle nie jest to argumentem. Nie powinieneś rezygnować z właściwego projektowania sztucznych przyrostów liczb/statystyk. –

+0

@DaanTimmer: Poza tym otwarcie takiej klasy (sprawianie, że jej członkowie są wirtualni) rzadko jest dobrym pomysłem, zwłaszcza gdy nie ma uzasadnionego powodu (a "mniej typów" nie jest jednym). –

3

Może z perspektywy purist, który jest właściwym sposobem idź, ale naprawdę ważne jest, aby upewnić się, że zewnętrzne zależności (np baza danych, dostęp do sieci itp.), wszystko, co jest kosztowne obliczeniowo/czasochłonne, a wszystko, co nie jest w pełni deterministyczne, jest usuwane i łatwe do zastąpienia w testach jednostkowych.

6

Moim zdaniem, nie powinieneś tworzyć interfejsów tylko w celu testowania jednostkowego. Jeśli zaczniesz dodawać abstrakcje kodu, aby zadowolić narzędzia, to nie pomagają ci być bardziej produktywnym. Napisany kod powinien idealnie służyć konkretnemu celowi biznesowemu/potrzebie - bezpośrednio lub pośrednio, dzięki uproszczeniu podstawy kodu w celu jej utrzymania lub ewolucji.

Interfejsy czasami to robią, ale na pewno nie zawsze. Uważam, że zapewnianie interfejsów dla komponentów jest zazwyczaj dobrą rzeczą, ale staram się unikać używania interfejsów dla klas wewnętrznych (to jest kodu używanego tylko wewnątrz danego projektu, niezależnie od tego, czy typy są zadeklarowane jako publiczne, czy nie). Dzieje się tak dlatego, że komponent (jak w zestawie klas pracujących razem, aby rozwiązać jakiś konkretny problem) reprezentuje większą koncepcję (taką jak program rejestrujący lub program planujący), który jest czymś, co mogę ewentualnie zastąpić lub wyłączyć podczas testowania .

Rozwiązaniem (czubek kapelusza dla Roberta za to, że był pierwszy w komentarzach) jest użycie szyderczego schematu do wygenerowania zgodnego typu podstawiania w czasie wykonywania. Szokujące ramy pozwalają następnie zweryfikować, czy testowana klasa poprawnie oddziałuje z podstawionym manekinem. Moq jest, jak wspomniano, snazzy wyborem. Rhino.Mocks i NMock to dwie inne popularne frameworki. Izolator Typemock podłącza się do profilera i jest jedną z mocniejszych opcji (umożliwia zastępowanie nawet nie-wirtualnych członków prywatnych), ale jest narzędziem komercyjnym.

Nie jest dobrze wymyślanie zasad określających, o ile należy przeprowadzić test jednostkowy. To zależy od tego, co rozwijasz i jakie są Twoje cele - jeśli poprawność zawsze przyspiesza czas wprowadzenia produktu na rynek, a koszt nie jest czynnikiem, to jednostka testująca wszystko jest świetna. Większość ludzi nie ma tyle szczęścia i będzie musiała pójść na kompromis, aby osiągnąć rozsądny poziom pokrycia testowego. To, jak należy się sprawdzić, może również zależeć od ogólnego poziomu umiejętności zespołu, oczekiwanego czasu życia i ponownego użycia napisanego kodu, itp.

+0

Mam szczęście, że nie mam problemów z menadżerami i czasem wejścia na rynek. Jestem tylko jednym deweloperem robiącym to w czasie wolnym, aby nauczyć się kilku dodatkowych rzeczy, których nie mogę się nauczyć w mojej pracy (w tej chwili). (Jestem osadzonym S.E. używającym C w tym momencie, czasami złożonym, czasami C#, czasami C++), więc tak. Teraz znajduję moje sposoby na testowanie jednostek C#. Wcześniej wykonano wiele testów jednostkowych w Pythonie (związanych z pracą). –

0

Z perspektywy testowania nie ma potrzeby tworzenia interfejsu dla każdej klasy w swoim kod. Tworzysz interfejs, aby ukryć konkretne wykonanie zewnętrznych zależności za warstwą abstrakcji. Więc zamiast mieć klasę, która wymaga bezpośredniego połączenia HTTP z logiką, izolowałbyś kod połączenia od klasy, zaimplementował interfejs, który jest członkiem twojej klasy, i wstrzyknął próbkę w miejscu tego interfejsu . W ten sposób możesz przetestować swoją logikę w izolacji, bez zależności, a jedynym "nietestowanym" kodem jest kod połączenia HTTP, który można przetestować w inny sposób.

0

Poszedłbym ścieżką wirtualnej metody.Tworzenie interfejsów dla każdej klasy, którą musisz przetestować, staje się naprawdę uciążliwe, szczególnie gdy potrzebujesz narzędzi takich jak Resharper do "przejścia do implementacji" za każdym razem, gdy chcesz zobaczyć definicję metody. Istnieje również możliwość zarządzania i modyfikowania obu plików za każdym razem, gdy zmieniono sygnaturę metody lub dodano nową właściwość lub metodę.