Podczas pisania testów z wykorzystaniem PHPUnit często pojawia się zagadnienie związane z przekazywaniem zmiennych pomiędzy poszczególnymi testami.

Załóżmy bowiem sytuację, gdy mamy test, w którym tworzymy sobie instancję jakiegoś obiektu i sprawdzamy działanie jednej z metod. W kolejnym teście chcemy przetestować kolejną z metod. Prześledźmy to może na przykładzie.

Testowana klasa:

class Car {
  public function __construct() {}

  public function startEngine() {
    return 'Engine started';
  }

  public function turnLeft() {
    return 'Turning left';
  }

  public function turnRight() {
    return 'Turning right';
  }

  public function stopEngine() {
    return 'Engine stopped';
  }
}

Klasa testowa dla powyższej klasy:

class CarTest extends PHPUnit_Framework_TestCase {

  public function testShouldStartEngine() {
    $car = new Car();

    $this->assertEquals('Engine started', $car->startEngine());
  }

  public function testShouldStopEngine() {
    $car = new Car();

    $this->assertEquals('Engine stopped', $car->stopEngine());
  }
}

Jak więc widzimy na powyższym przykładzie w każdym z testów tworzona jest nowa instancja obiektu Car. Dla powyższego przykładu nie jest to zbyt skomplikowane i można sobie zadać pytanie po co kombinować. Ale co w sytuacji, jeżeli nasz obiekt jest dość rozbudowany i chcielibyśmy w każdym kolejnym teście wywoływać na nim kolejną metodę i aby stan obiektu po wywołaniu metody był od razu dostępny w kolejnym teście, bez ponownego wywoływania wszystkich metod.

Rozwiązanie okazuje się bardzo proste. Wystarczy bowiem skorzystać z annotacji @depends, gdzie przeazujemy nazwę testu, który jest wykorzystywany do uruchomienia bieżącego testu. Dla powyższej klasy testowej wyglądałoby to następująco:

class CarTest extends PHPUnit_Framework_TestCase {

  public function testShouldStartEngine() {
    $car = new Car();

    $this->assertEquals('Engine started', $car->startEngine());
    
    return $car
  }

  /**
  * @depends testShouldStartEngine
  * @param Car car
  */
  public function testShouldStopEngine($car) {
    $this->assertEquals('Engine stopped', $car->stopEngine());
  }
}

W teście testShouldStartEngine zwracamy instancję obiektu Car, która jest wykorzystywana w kolejnym teście, czyli testShouldStopEngine.

Rozwiązanie bardzo proste i skuteczne.
PHPUnit oferuje nam więcej ułatwień związanych z przygotowywaniem danych potrzebnych do wykonania poszczególnych testów tak, aby sama metoda testująca była maksymalnie prosta i przygotowywania obiektu nie zaciemniało jej ciała. Mamy między innymi dostępne metody: setUp, setUpBeforeClass, itp., ale o tym w kolejnym wpisie.

PHPUnit – przekazywanie zmiennych pomiędzy testami
Tagged on:                 

5 thoughts on “PHPUnit – przekazywanie zmiennych pomiędzy testami

  • 2012-01-15 at 19:26
    Permalink

    Ale czy takie podejście jakie jest sprzeczne z założeniem jakie powinno być stosowane w testach – każdy test jest nie zależy. Prezentowane rozwiązanie zmienia stan testowanego obiektu. Stosując podejście takie jak wyżej, test drugi w kolejności zależy od tego co się stanie w pierwszym.

    Zaciemnia to wynik testów, teraz nie wiem czy problem był związany z metodą testowaną w pierwszym teście, czy jest to awaria całego obiektu (jakiś wewnętrznych mechanizmów)

    Reply
    • 2012-01-15 at 21:55
      Permalink

      Zgadza się, każdy test powinien być niezależny wówczas, gdy mówimy o testach jednostkowych. Z wykorzystaniem PHPUnit możemy tworzyć też smoke tests i sanity tests. Testujemy wówczas większy, aczkolwiek odpowiednio mały fragment aplikacji. Możemy np. przetestować poprawność wykonywania operacji na bazie danych – począwszy od stworzenia encji w bazie, poprzez jej modyfikację, wyszukanie, aż po usunięcie – słowem przetestowanie całego CRUD’a. Warto wówczas pracować na jednej konkretnej instancji obiektu, a nie tworzyć w każdym teście nową instację i nową encję.

      Co do zaciemniania wyników testów – w codziennej pracy wykorzystuję te możliwości i nie stanowi dla mnie problemu znalezienie błędu w aplikacji. Gdy dany test nie przejdzie, automatycznie wszystkie testy od niego zależne nie są uruchamiane.

      Reply
      • 2012-01-22 at 20:44
        Permalink

        Ciężko mi się zgodzić z tezą, że zasady izolacji i niezależności testów nie aplikują się w jakimkolwiek z aspektów/technice testowania. W przypadku Twojego podejścia otrzymujemy “N” failujących testów nawet przy jednym błędzie w aplikacji przez co nie otrzymujemy natychmiastowej odpowiedzi co rzeczywiście nie działa. Pisząc “w codziennej pracy wykorzystuję te możliwości i nie stanowi dla mnie problemu znalezienie błędu w aplikacji. ” stwierdziłeś, że błędu musisz szukać, jednakże przy dobrzej siatce testów powinieneś wiedzieć co nie działa bez szukania 🙂

        Reply
        • 2012-01-22 at 22:00
          Permalink

          No nie do końca jest tak jak piszesz. Nie otrzymamy bowiem “N” failujących testów, bowiem fail będzie tylko na pierwszym teście, natomiast wszystkie od niego zależne się nie wykonają. Idąc dalej – test powinien testować jak najmniejszą część aplikacji, więc w przypadku faila wiem od razu, która część aplikacji nie zadziałała prawidłowo. Znalezienie błędu nie jest więc problemem – problemem jest słowo znalezienie. Nie oznacza ono jakiegokolwiek długotrwałego szukania. Znalezienie raczej to sprawdzenie, który test nie przeszedł i mamy już odpowiedź, gdzie powstał błąd.

          W każdym bądź razie – pokazałem tu dostępną metodę, jednak samo jej wykorzystanie leży już w gestii programisty. Jeżeli mu będzie zaciemniać testy i ich wyniki może tego nie używać, jednak czasami może być to przydatne 😉

          Reply
  • Pingback:PHPUnit – metody wywoływane przed i po testach | Mariusz Tulikowski – dev blog

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Social Widgets powered by AB-WebLog.com.