Stub object – co to jest i jak nam pomaga?

Co to jest stub object? Do czego służy, jak nam pomaga i jak pomaga nam robić unit testy?

Dziś zajmiemy się zrozumieniem co to jest stub object. W kolejnej części opiszę też mock object. Ale zacznijmy od ich przeznaczenia.

Problem z testami

Dobre unit testy powinny działać szybko. Mają też testować pojedyncze elementy systemu. Ale co zrobić, gdy obiekt zapisuje do bazy, wysyła informacje, pobiera coś z sieci czy robi inne rzeczy na zewnętrznych serwisach?

Przykłady będą w PHP, ale w każdym języku działa to podobnie.

Mój znienawidzony Active Record jest dobrym przykładem. Mamy obiekt, który wypełniamy danymi i zapisujemy. Podczas zapisu dzieją się różne rzeczy – walidacja, wypełnianie pól itp. Weźmy taką metodę w active record:

class User extends ActiveRecord
{
   public function createUser($name, $email)
   {
       $this->name = $name;
       $this->e-mail = $email;
       if ($this->vaidate()) {
          return $this->save();
       } else {
          return false;
       }
   }
}

Jak to przetestować? Jak sprawdzić, czy nasza funkcja jest odporna na błędy?

Możemy wrzucać błędne wartości, null zamiast nazwy, pusty tekst zamiast e-maila itp. Ale jeśli funkcja źle działa to naśmieci nam w bazie.

Inny przykład, mamy obiekt wysyłający maile. Niech ma np. funkcję send():

class MyMail {
   public function __construct(\Mailer $mailer)
   {
      $this->mailer = $mailer;
   }

   public function send($to, $title, $body)
   {
      if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
         return false;
      }
      $body .= "\nSent by my mailer";
      return $this->mailer->send($to, $title, $body);
   }
}

Jak sprawdzić czy dokleja właściwą stopkę? Jak sprawdzić, czy przyjmuje właściwe parametry? Jak sprawdzić, czy weryfikuje dane?

Możemy oczywiście ustawić testową bazę danych lub testowy adres e-mail. Ale grzebanie w bazie lub wysyłanie maili zajmuje czas. Testy jednostkowe mają być szybkie. Dlatego korzystamy z mocków.

Stub object – jak to działa?

W pierwszym przykładzie chcemy wyłączyć zapisywanie do bazy czyli metodę save(). W drugim, chcemy nie wysyłać maila naprawdę a jedynie sprawdzić, czy funkcja poprawnie waliduje e-mail.

Object doble to kopia naszego obiektu ze zmodyfikowaną funkcjonalnością. W PHPUnit tworzymy doubla w ten sposób:

// kopiujemy naszą klasę User
$stub = $this->getMock('User');
// zmieniamy działanie jednej z metod
$stub->method('save')->willReturn(true);

Co tu się stało? W pierwszej linii stworzyliśmy obiekt klasy User, ale taki, którego zachowanie możemy zmieniać. W drugiej zmieniliśmy zachowanie funkcji save(). Teraz funkcja save() nic nie robi, jedynie zwraca true.

Nasz test wygląda teraz łatwo:

class UserTest extends PHPUnit_Framework_TestCase
{
   public function testUser()
   {
      // kopiujemy naszą klasę User
      $stub = $this->getMock('User');
      // zmieniamy działanie jednej z metod
      $stub->method('save')->willReturn(true);

      // test niepoprawnego adresu e-mail
      $this->assertFalse($stub->createUser('username', 'this-is-not-an-email'));

      // test dobrego adresu e-mail
      $this->assertTrue($stub->createUser('username', 'valid@e.mail'));
   }
}

Gotowe! Mamy test walidacji poprawnych danych! Nic nie zapisuje się do bazy. Sprawdzamy tylko czy medoda działa jak powinna.

W drugim przykładzie jest podobnie. Przetestujemy funckję send podając fikcyjny mailer w konstruktorze:

class MyMailTest extends PHPUnit_Framework_TestCase
{
   public function testSend()
   {
      // kopiujemy klasę Mailer i wyłączamy metodę send()
      $stub = $this->getMock('Mailer');
      $stub->method('send')->willReturn(true);

      // teraz możemy podłożyć nasz obiekt klasie MyMail

      $mail = new MyMail($stub);

      // test niepoprawnego adresu e-mail
      $this->assertFalse($mail->send('this-is-not-an-email', 'title', 'body'));

      // test dobrego adresu e-mail
      $this->assertTrue($mail->send('valid@e.mail', 'title', 'body'));
   }
}

Teraz test działa bez ryzyka wysłania prawdziwego maila.

Co dalej?

W kolejnym wpisie poznamy mock object. Opiszę też czym się różni double, stub i mock. Zobaczymy przykłady jak z nich korzystać.

Warto poczytać dokumentację PHPUnit o object doubles. W praktycznie każdym języku mamy możliwości tworzenia takich obiektów.

2 myśli na temat “Stub object – co to jest i jak nam pomaga?”

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *