Pytanie Testowanie jednostek: kodowanie do interfejsów?


Obecnie mój projekt składa się z różnych konkretnych klas. Teraz gdy przechodzę do testów jednostkowych, wygląda na to, że mam stworzyć interfejs dla każdej klasy (skutecznie podwajając liczbę klas w moim projekcie)? Tak się składa, że ​​używam Google Mock'a jako szyderczego frameworka. Widzieć Google Mock CookBook na interfejsach. Podczas gdy przedtem mógłbym mieć tylko zajęcia Car i Engine, teraz miałbym klasy abstrakcyjne (również interfejsy C ++) Car i Engine a następnie klasy implementacji CarImplementation i EngineImpl lub cokolwiek. To pozwoliłoby mi się wydostać CarZależność zależy od Engine.

Są dwie linie myślenia, które natknąłem się na badanie tego:

  1. Używaj interfejsów tylko wtedy, gdy potrzebujesz więcej niż jednego implementacja danej abstrakcji i / lub do wykorzystania w publicznych interfejsach API, więc w przeciwnym razie nie tworzy się niepotrzebnie interfejsów.

  2. Testy jednostkowe / mocks często  "inne wdrożenie", a więc tak, powinieneś stworzyć intefaces.

Czy podczas testowania jednostkowego powinienem utworzyć interfejs dla każdej klasy w moim projekcie? (Opieram się na tworzeniu interfejsów dla łatwości testowania)


12
2017-07-08 01:41


pochodzenie




Odpowiedzi:


Myślisz, że masz wiele opcji. Jak mówisz, jedną z opcji jest stworzenie interfejsów. Powiedz, że masz zajęcia

class Engine:
{
public:
    void start(){ };
};

class Car
{
public: 
    void start()
    {
        // do car specific stuff
        e_.start();

private:
    Engine e;
};

Aby wprowadzić interfejsy - musiałbyś zmienić samochód, aby wziąć silnik

class Car
{
public: 
    Car(Engine* engine) :
    e_(engine)
    {}

    void start()
    {
        // do car specific stuff
        e_->start();

private:
    Engine *e_;
};

Jeśli masz tylko jeden typ silnika - nagle twoje obiekty samochodowe są trudniejsze w użyciu (kto tworzy silniki, kto jest właścicielem silników). Samochody mają wiele części - więc ten problem będzie nadal wzrastał.

Jeśli chcesz mieć osobne implementacje, innym sposobem będzie szablon. To eliminuje potrzebę stosowania interfejsów.

class Car<type EngineType = Engine>
{
public: 
    void start()
    {
        // do car specific stuff
        e_.start();

private:
    EngineType e;
};

W swoich mockach możesz następnie tworzyć samochody z wyspecjalizowanymi silnikami:

Car<MockEngine> testEngine;

Innym, innym podejściem byłoby dodawanie metod do silnika, aby umożliwić jego testowanie, coś takiego:

class Engine:
{
public:
    void start();
    bool hasStarted() const;
};

Następnie możesz dodać metodę kontroli do samochodu lub dziedziczyć z samochodu, aby przetestować.

class TestCar : public Car
{
public:
    bool hasEngineStarted() { return e_.hasStarted(); }
};

To wymagałoby zmiany silnika z prywatnego na chroniony w klasie samochodu.

W zależności od rzeczywistej sytuacji będzie zależeć od tego, które rozwiązanie jest najlepsze. Ponadto każdy twórca będzie miał swojego świętego Graala, w jaki sposób jego zdaniem kod powinien być testowany jednostkowo. Moje osobiste opinie mają na celu utrzymanie klienta / klienta. Załóżmy, że Twoi klienci (być może inni deweloperzy w twoim zespole) będą tworzyć samochody i nie dbają o silniki. W związku z tym nie chciałbym ujawniać koncepcji Silników (klasy wewnętrznej mojej biblioteki) tylko po to, abym mógł je przetestować. Wolałbym nie tworzyć interfejsów i testować dwie klasy razem (trzecia opcja, którą dałem).


5
2017-07-27 12:15





istnieją dwie kategorie testów dotyczących widoczności implementacji: testowanie w czerni i testy w trybie "white-box"

  • Testowanie w trybie black-box koncentruje się na testowaniu implementacji poprzez ich interfejsy i sprawdzaniu poprawności dostosowania do ich specyfikacji.

  • białe testy testowe szczegółowe informacje o implementacji tego NIE POWINIENEŚ ogólnie dostępne z zewnątrz. Tego rodzaju testowanie potwierdzi, że komponenty implementacji działają zgodnie z przeznaczeniem. Więc ich wyniki są w większości interesujące dla programistów próbujących dowiedzieć się, co jest zepsute, lub wymaga utrzymania

krojenie według ich definicji pasuje do architektury modułowej, ale nie oznacza to, że wszystkie klasy w projekcie muszą być całkowicie modułowe. Zupełnie dobrze jest narysować linię, gdy grupa klas będzie się znała. Jako grupa mogą prezentować inne moduły z persepektywnej klasy interfejsu elewacyjnego. Jednak nadal będziesz chciał mieć sterowniki testowe białoskrzynkowe w tym module z wiedzą o szczegółach implementacji. Stąd ten rodzaj testowania jest nie pasuje do drwin.

Z tego wynika banalnie, że nie trzeba mieć makiet ani interfejsów do wszystkiego. Wystarczy wziąć komponenty projektowe wysokiego poziomu, które implementują interfejsy elewacyjne i tworzyć dla nich makiety. Daje ci to słodkie miejsce, w którym fałszywe testowanie się opłaca moim zdaniem

powiedziawszy to, spróbuj użyć tego narzędzia do swoich potrzeb, zamiast pozwalać narzędziu na zmuszanie cię do zmian, które Twoim zdaniem na dłuższą metę nie będą korzystne


1
2017-07-12 20:09



Dzięki za odpowiedź; Próbuję to zignorować. Czy mówisz, że test Whitebox nie pasuje do mocks? Sposób, w jaki patrzę na testy jednostkowe, polega na tym, że próbuję przetestować każdą jednostkę kodu. Na przykład chcę przetestować Car i Car zależy od Engine klasa. Aby upewnić się, że robię jednostka test, a nie integracja test, ja potrzeba drwić z Engine klasa (w taki czy inny sposób Car używać mojego wyśmiewanego Engine). W przeciwnym razie testuję dwie klasy i wykonuję test integracyjny, a nie test jednostkowy. A żeby łatwo kpić z Engine klasa wydaje się, że musi to być interfejs. - User
jedyna różnica polega na tym, co jednostka reprezentuje. Zwykle dla niektórych osób testowana jednostka reprezentuje jednostkę kompilacji lub klasę, ale nie musi tak być. Na przykład, jeśli utworzysz klasę wykresu, węzeł i krawędź będą ze sobą silnie sprzężone, więc nie ma sensu testować ich w izolacji. Po prostu umieść cały moduł wykresu w interfejsie elewacji i kiedy inne moduły muszą wchodzić w interakcje z pozorowaną reprezentacją wykresu, pozoruj fasady grafu, a nie poszczególne klasy (jako odpowiednia fasada, będą potrzebne reprezentacje poziomu węzłów w interfejsie API, takie jak liczby całkowite lub id) - lurscher
W moim rozumieniu testowania jednostkowego, jeśli klasa krawędzi używa klasy węzła, wtedy gdy testuję klasę krawędzi, chciałbym wyśmiewać klasę węzła, ponieważ nie chcę testować dwóch "jednostek" w tym samym czasie. Jeśli test na klasę brzegową się nie powiedzie, nie wiedziałbym, czy to naprawdę krawędź, czy węzeł naprawdę się nie powiódł. Myślę, że nazwałbym to, co mówisz o testowaniu integracyjnym (zdając sobie sprawę, że jest to po prostu semantyka do pewnego stopnia). - User
tak, szczerze mówiąc uważam, że jest to kwestia gustu, co pojmiecie jako jednostkę i przy jakiej rozdzielczości zatrzymacie się i powiecie "tu mówię, to będzie moja konceptualna jednostka", moim zdaniem krawędzie i węzły są elementy sintaktyczne, ale bez prawdziwej funkcjonalności same w sobie; tylko poznają swoją semantykę we wzajemnej interakcji. Tak więc dla mnie testowanie jednostkowe musi przetestować API jednostki (tutaj, wykres) i zweryfikować, czy pasuje do oczekiwanej semantyki (tylko znaczenie jako całość). Oczywiście nie chcesz zbytnio ich rozwijać, inaczej skończysz ponownie, odkrywając niepołączony kod - lurscher


Tworzenie interfejsów dla każdej klasy w projekcie może być konieczne lub nie. Jest to całkowicie decyzja projektowa. Odkryłem, że w większości tak nie jest. Często w n-tier projektujesz abstrakcję warstwy między dostępem do danych a logiką. Twierdzę, że powinieneś pracować w tym kierunku, ponieważ pomaga to w testowaniu logiki bez dużej infrastruktury niezbędnej do testów. Metody abstrakcji jak iniekcja zależności i IoC wymagałoby wykonania czegoś takiego i ułatwiłoby przetestowanie wspomnianej logiki.

Chciałbym zbadać, co próbujesz przetestować i skupić się na obszarach, które uważasz za najbardziej podatne na błędy. Może to pomóc w podjęciu decyzji, czy interfejsy są niezbędne.


0
2017-07-08 01:56