Czy można wprowadzać metody stosowane tylko podczas testów jednostkowych?

12

Ostatnio TDD stosowałem metodę fabryczną. Metoda polegała na utworzeniu zwykłego obiektu lub obiektu owiniętego dekoratorem. Udekorowany obiekt może być jednego z kilku typów rozszerzających StrategyClass.

W moim teście chciałem sprawdzić, czy klasa zwracanego obiektu jest zgodna z oczekiwaniami. To proste, gdy powróci zwykły obiekt, ale co zrobić, gdy jest zawinięty w dekoratorze?

Piszę w języku PHP, aby móc ext/Reflectionznaleźć klasę zawiniętego obiektu, ale wydawało mi się, że jest to zbyt skomplikowane i nieco przypomina zasady TDD.

Zamiast tego zdecydowałem się wprowadzić getClassName(), która zwróci nazwę klasy obiektu, gdy zostanie wywołana ze StrategyClass. Jednak wywołany z dekoratora zwróci wartość zwróconą tą samą metodą w dekorowanym obiekcie.

Niektóre kody, aby to wyjaśnić:

interface StrategyInterface {
  public function getClassName();
}

abstract class StrategyClass implements StrategyInterface {
  public function getClassName() {
    return \get_class($this);
  }
}

abstract class StrategyDecorator implements StrategyInterface {

  private $decorated;

  public function __construct(StrategyClass $decorated) {
    $this->decorated = $decorated;
  }

  public function getClassName() {
    return $this->decorated->getClassName();
  }

}

I test PHPUnit

 /**
   * @dataProvider providerForTestGetStrategy
   * @param array $arguments
   * @param string $expected
   */
  public function testGetStrategy($arguments, $expected) {

    $this->assertEquals(
      __NAMESPACE__.'\\'.$expected,  
      $this->object->getStrategy($arguments)->getClassName()
    )
  }

//below there's another test to check if proper decorator is being used

Chodzi mi o to: czy można wprowadzić takie metody, które nie mają innego zastosowania niż ułatwienie testów jednostkowych? Jakoś mi to nie pasuje.

Mchl
źródło
Być może to pytanie rzuci więcej wglądu na twoje pytanie, programmers.stackexchange.com/questions/231237/…. Wierzę, że zależy to od użycia i tego, czy metody znacznie pomogą w testowaniu jednostkowym i debugowaniu dowolnej aplikacji, którą opracowujesz. .
Klemens Mark-Aaba

Odpowiedzi:

13

Moje myśli nie to, że nie należy niczego robić tylko dlatego, że jest potrzebny do sprawdzalności. Wiele decyzji podejmowanych przez ludzi ma korzystny wpływ na testowalność, a testowalność może nawet być główną korzyścią, ale powinna być dobrą decyzją projektową w oparciu o inne zalety. Oznacza to, że niektórych pożądanych właściwości nie można przetestować. Innym przykładem jest to, że musisz wiedzieć, jak skuteczna jest jakaś procedura, na przykład czy Hashmap używa równomiernie rozłożonego zakresu wartości skrótu - nic w interfejsie zewnętrznym nie byłoby w stanie tego powiedzieć.

Zamiast myśleć: „Czy dostaję odpowiednią klasę strategii”, „myślę” czy klasa, którą dostaję, wykonuje to, co ta specyfikacja próbuje przetestować? ” Fajnie jest, gdy możesz przetestować hydraulikę wewnętrzną, ale nie musisz, po prostu przetestuj pokrętło i sprawdź, czy dostajesz ciepłej lub zimnej wody.

Jeremy
źródło
+1 OP opisuje testowanie jednostek „czystego pola”, a nie testowanie funkcji TDD
Steven A. Lowe
1
Rozumiem, ale nie chcę dodawać testowania algorytmów StrategyClass, gdy chcę sprawdzić, czy metoda fabryczna wykonuje swoje zadanie. Ten rodzaj przełamuje izolację IMHO. Innym powodem, dla którego chcę tego uniknąć, jest to, że te konkretne klasy działają na bazie danych, więc ich testowanie wymaga dodatkowego kpowania / karczowania.
Mchl
Z drugiej strony, w świetle tego pytania: programmers.stackexchange.com/questions/86656/…, kiedy rozróżniamy „testy TDD” od „testów jednostkowych”, staje się to całkowicie w porządku (choć hydraulika bazy danych: P)
Mchl
Po dodaniu metod staną się one częścią umowy z użytkownikami. Skończysz z koderami, którzy wywołują twoje funkcje tylko testowe i rozgałęziają się na podstawie wyników. Generalnie wolę wystawiać jak najmniej klasy.
BillThor
5

Podejrzewam, że - czasami trzeba trochę przerobić kod źródłowy, aby był bardziej testowalny. Nie jest to idealne i nie powinno być usprawiedliwieniem do zaśmiecania interfejsu funkcjami, które mają być używane tylko do testowania, więc moderacja jest tu na ogół kluczem. Nie chcesz również znajdować się w sytuacji, w której użytkownicy twojego kodu nagle używają funkcji interfejsu testowego do normalnych interakcji z twoim obiektem.

Moim preferowanym sposobem na poradzenie sobie z tym (i przepraszam, że nie mogę pokazać, jak to zrobić w PHP, ponieważ głównie koduję w językach w stylu C), jest zapewnienie funkcji „testowych” w taki sposób, aby nie były wystawione na świat zewnętrzny przez sam obiekt, ale mogą być dostępne dla obiektów pochodnych. Dla celów testowych wyprowadziłbym klasę, która obsługiwałaby interakcję z obiektem, który faktycznie chcę przetestować, i aby test jednostkowy używał tego konkretnego obiektu. Przykład w C ++ mógłby wyglądać mniej więcej tak:

Klasa typu produkcji:

class ProdObj
{
  ...
  protected:
     virtual bool checkCertainState();
};

Klasa pomocnika testowego:

class TestHelperProdObj : public ProdObj
{
  ...
  public:
     virtual bool checkCertainState() { return ProdObj::checkCertainState(); }
};

W ten sposób jesteś przynajmniej w pozycji, w której nie musisz ujawniać funkcji typu „test” w głównym obiekcie.

Timo Geusch
źródło
Ciekawe podejście. Muszę zobaczyć, jak mogę to dostosować
Mchl,
3

Kilka miesięcy temu, kiedy umieściłem nowo zakupioną zmywarkę, z jej węża wypłynęła duża ilość wody, zdałem sobie sprawę, że jest to prawdopodobnie spowodowane tym, że została właściwie przetestowana w fabryce, z której pochodzi. Nierzadko widać otwory montażowe i inne elementy maszyn, które są tam - tylko - w celu testowania na linii montażowej.

Testowanie jest ważne, w razie potrzeby po prostu dodaj coś do tego.

Ale spróbuj niektórych alternatyw. Twoja opcja oparta na refleksji nie jest wcale taka zła. Możesz mieć chronionego wirtualnego akcesorium do tego, czego potrzebujesz, i stworzyć klasę pochodną do testowania i potwierdzania. Być może możesz podzielić swoją klasę i przetestować bezpośrednio wynikową klasę liści. Ukrywanie metody testowej za pomocą zmiennej kompilatora w kodzie źródłowym jest również opcją (ledwo znam PHP, nie jestem pewien, czy jest to możliwe w PHP).

W twoim kontekście możesz zdecydować, że nie przetestujesz odpowiedniej kompozycji w dekoratorze, ale przetestuj oczekiwane zachowanie, jakie powinna mieć dekoracja. Być może bardziej skupiłoby się to na oczekiwanym zachowaniu systemu, a nie na specyfikacji technicznej (co kupuje wzór dekoratora z funkcjonalnej perspektywy?).

Joppe
źródło
1

Jestem absolutnym nowicjuszem TDD, ale wydaje się, że zależy to od dodanej metody. Z tego, co rozumiem na temat TDD, twoje testy mają w pewnym stopniu „napędzać” tworzenie twojego API.

Kiedy jest OK:

  • O ile metoda nie przerywa enkapsulacji i służy celowi zgodnemu z odpowiedzialnością obiektu.

Kiedy nie jest OK:

  • Jeśli metoda wydaje się być czymś, co nigdy nie byłoby przydatne lub nie ma sensu w stosunku do innych metod interfejsu, może być niespójna lub myląca. W tym momencie utrudniłoby mi to zrozumienie tego obiektu.
  • Przykład Jeremy'ego: „... gdy trzeba wiedzieć, jak skuteczna jest jakaś procedura, na przykład czy Hashmap używa równomiernie rozłożonego zakresu wartości skrótu - nic w interfejsie zewnętrznym nie byłoby w stanie tego powiedzieć”.
szczenię
źródło