Kpiąca klasa betonu - Niezalecane

11

Właśnie przeczytałem fragment książki „Growing Object-Oriented Software”, która wyjaśnia kilka powodów, dla których kpiny z konkretnej klasy nie są zalecane.

Oto przykładowy kod testu jednostkowego dla klasy MusicCentre:

public class MusicCentreTest {
  @Test public void startsCdPlayerAtTimeRequested() {
    final MutableTime scheduledTime = new MutableTime();
    CdPlayer player = new CdPlayer() { 
      @Override 
      public void scheduleToStartAt(Time startTime) {
        scheduledTime.set(startTime);
      }
    }

    MusicCentre centre = new MusicCentre(player);
    centre.startMediaAt(LATER);

    assertEquals(LATER, scheduledTime.get());
  }
}

I jego pierwsze wyjaśnienie:

Problem z tym podejściem polega na tym, że pozostawia domniemany związek między przedmiotami. Mam nadzieję, że do tej pory wyjaśniliśmy, że zamiarem rozwoju opartego na testach z Mock Objects jest odkrywanie relacji między obiektami. Jeśli podklasę, w kodzie domeny nie ma nic, co by uwidoczniło taką relację, tylko metody na obiekcie. Utrudnia to sprawdzenie, czy usługa obsługująca tę relację może być istotna w innym miejscu, i będę musiał ponownie przeprowadzić analizę następnym razem, gdy będę pracować z klasą.

Nie mogę dokładnie zrozumieć, co on ma na myśli, mówiąc:

Utrudnia to sprawdzenie, czy usługa obsługująca tę relację może być istotna w innym miejscu, i będę musiał ponownie przeprowadzić analizę następnym razem, gdy będę pracować z klasą.

Rozumiem, że usługa odpowiada MusicCentrewywołanej metodzie startMediaAt.

Co rozumie przez „gdzie indziej”?

Pełny fragment znajduje się tutaj: http://www.mockobjects.com/2007/04/test-smell-mocking-concrete-classes.html

Mik378
źródło
Dodał komentarz na swoim blogu, ponieważ nie byłem w stanie zrozumieć, co miał na myśli z tych cytatów.
oligofren,
@ oligofren To naprawdę świetna zagadka :) ...
Mik378,

Odpowiedzi:

6

Autor tego postu promuje używanie interfejsów zamiast klas członków.

It turns out that my MusicCentre object only uses the starting and stopping methods on the CdPlayer, the rest are used by some other part of the system. I'm over-specifying my MediaCentre by requiring it to talk to a CdPlayer, what it actually needs is a ScheduledDevice.

Relacją, którą martwi się o ponowne odkrycie później, jest fakt, że klasa MediaCentre nie potrzebuje całego obiektu CdPlayer. Twierdzi, że używając interfejsu (przypuszczalnie ograniczonego do uruchomienia | stop), łatwiej jest zrozumieć, czym tak naprawdę jest interakcja.

„gdzie indziej” oznacza po prostu, że inne obiekty mogą mieć podobnie ograniczone relacje, a pełny obiekt składowy nie jest wymagany - podzbiór funkcjonalności zawinięty w interfejsie powinien być wystarczający.

Roszczenie zaczyna mieć sens, gdy eksplodujesz wszystkie potencjalne funkcje:

  • początek
  • zatrzymać
  • pauza
  • rekord
  • losowa kolejność odtwarzania
  • przykładowe utwory, początek piosenki
  • przykładowe utwory, losowa próbka piosenki
  • podać informacje o mediach
  • ...

Teraz jego twierdzenie „Po prostu potrzebuję rozpocząć i zatrzymać” ma więcej sensu. Użycie konkretnego elementu członkowskiego zamiast interfejsu sprawia, że ​​przyszłym deweloperom nie jest jasne, co jest naprawdę wymagane. Przeprowadzanie testów jednostkowych z MediaCentre na wszystkich innych funkcjach w CdPlayer jest marnotrawstwem wysiłku testowego, ponieważ należą one do stanu „nie przejmuj się”. Jeśli Recordfunkcja nie działała w tym przypadku, naprawdę nas to nie obchodzi, ponieważ nie jest wymagana. Ale przyszły opiekun niekoniecznie wiedziałby o tym na podstawie napisanego kodu.

Ostatecznie założeniem autora jest wykorzystanie tylko tego, co jest potrzebne i wyjaśnienie przyszłym opiekunom tego, co było wcześniej wymagane. Celem jest zminimalizowanie przeróbek / ponownej analizy modułu kodu podczas późniejszej konserwacji.


źródło
Dzięki za tę świetną odpowiedź. Jednak powiedziałeś: „Przeprowadzanie testów jednostkowych dla wszystkich innych funkcji jest stratą wysiłku testowego, ponieważ należą one do stanu„ nie przejmuj się ”. Czy to nie raczej: „Tworzenie próbnych funkcji dla każdej z pozostałych funkcji jest stratą wysiłku testowego, ponieważ należą one do stanu„ nie przejmuj się ”.”?
Mik378,
@ Mik378 - tak, dokładnie o to mi chodziło, po prostu wyraziłem to inaczej. Zaktualizowałem moją odpowiedź, aby to wyjaśnić.
Ale uważam, że termin „przeprowadzanie testów jednostkowych” jest mylący. Oznaczałoby to, że MusicCentre ma zamiar przetestować swojego współpracownika ... podczas gdy w rzeczywistości MOCUJE swojego współpracownika, aby przetestować swoje usługi OWN. Nawiasem mówiąc, teraz rozumiem znaczenie :)
Mik378,
@ Mik378 - mówimy to samo i prawdopodobnie używam do tego mało precyzyjnej terminologii. Przepraszamy za zamieszanie.
4

Utrudnia to sprawdzenie, czy usługa obsługująca tę relację może być istotna w innym miejscu, i będę musiał ponownie przeprowadzić analizę następnym razem, gdy będę pracować z klasą.

Po zastanowieniu się nad tym, otrzymuję możliwą interpretację tego cytatu:

Cytowana „usługa” odpowiada „faktowi planowania”. Może to być wyrażone przez dobrze nazwany i „skoncentrowany na jednej roli” interfejs o nazwie „ScheduledDevice” lub wyrażone pośrednio przez konkretną implementację metody niezależną od jakichkolwiek interfejsów.

W powyższym przykładzie harmonogram jest wyrażony przez cały obiekt o pełnej nazwie CDPlayer. W ten sposób nadal prowadzi do domniemanego związku między MusicCentre„faktem planowania”.

Więc jeśli zaczniemy wstrzykiwać konkretne klasy i wyśmiewać je do obiektów wysokiego poziomu; kiedy chcemy je przetestować, musimy przeanalizować każdy wstrzyknięty „konkretny” obiekt, aby zobaczyć, czy przedstawia on konkretną zależność, którą MUSIMY PRZECZYTAĆ, ponieważ są UKRYTE (niejawne). Wręcz przeciwnie, kodowanie ZAWSZE nad interfejsem pozwala programistom bezpośrednio dowiedzieć się, jaki rodzaj relacji ma być obsługiwany przez obiekt wysokiego poziomu, a tym samym wykryć funkcje, które należy wyśmiewać, aby wyodrębnić test jednostkowy.

Mik378
źródło
Myślę, że już to masz. Niestety nie otrzymałem powiadomienia o twoim komentarzu.
Steve Freeman
3

Miałem tu na myśli usługę CDPlayer.scheduleToStartAt (). Tak nazywa się MediaCentre - współpracownik, którego potrzebuje, aby funkcjonować. MediaCentre jest testowanym obiektem.

Chodzi o to, że jeśli jasno wyrażę tylko to, od czego zależy MediaCentre, a nie klasę implementacji, mogę nadać tej roli zależności i mówić o niej. MediaCentre musi tylko wiedzieć, że rozmawia z urządzeniami ScheduledDevices. Gdy reszta systemu się zmieni, nie będę musiał zmieniać MediaCentre, chyba że zmienią się jego funkcje.

To pomaga?

Steve Freeman
źródło
(autor tego świetnego artykułu :)) to, co chciałem zinterpretować, to zdanie: „To sprawia, że ​​trudniej jest sprawdzić, czy usługa obsługująca tę relację może być istotna gdzie indziej i będę musiał ponownie przeprowadzić analizę następnym razem, gdy będę pracować z klasą ”. Jaki rodzaj analizy? Fakt wykrycia, która metoda obiektu ma zaimplementować relację, ponieważ ta jest wyraźnie ukryta?
Mik378,