У чьому різниця між Stub і Mock у РHPUnit

Stub і Mock (стаб і мок) – це дві різновидності Test Double, тобто тестового двійника. Тестовий двійник – це обʼєкт, що передається в SUT (систему, що тестується). Крім них, існує ще декілька різновидів, найпопулярніші з них: Dummy, Fake, Spy.

Test Double
            |
            -> Dummy
            |
            -> Stub
            |
            -> Mock
            |
            -> Fake
            |
            -> Spy

Найпростішим тестовим двійником є dummy, єдина функція якого – задовільнити умовам інтерфейсу. Методи цього класу не викликаються, чи їх виклик не впливає на тестуєму систему.

Для stub головне – повернути заздалегідь визначені данні, при чьому не важливо, скільки разів ці данні запитувались.

Для mock навпаки, найголовніше – це простежити що визначені методи викликались задану кількість разів. Мок може повертати дані для того, щоб тест пройшов по потрібним гілкам, проте основний акцент залишається на взаємодії тестуємої системи і мока.

Fake – це спрощена реалізація обʼєкта. Прикладом фейка може бути in-memory база даних, що збергіє данні в масив, замість того щоб робити справжній запит у базу даних.

Spy – здатний записувати взаємодію тестуємої системи з обʼєктом, які можна провалідитувати пізніше.

Тестові двійники у PHPUnit

У PHPUnit явним чином виділені сутності stub і mock. Замість dummy можна використовувати звичайний клас, стаб чи мок. Fake – зазвичай імплементується як звийчайний клас. Про використання Spy у PHPUnit є гарна стаття (див. посилання внизу).

Як використовувати stub у тесті?

// Arrange
$dependency = $this->createStub(Dependency::class);
$dependency->method('getExpression')
           ->willReturn('40 + 2');
$sut = new Calculator(dependency);

// Act
$result = $sut->process();

// Assert
$this->assertEquals(42, $result);

У прикладі вище створюється стаб, який імплементує метод getExpression, що завжди повертає строку з виразом ’40 + 2′. Далі стаб передається в обʼєкт калькулятора як залежність – це єтап arrange. На єтапі act – виконується тестуєма дія. На єтапі assert – перевіряється результат виконуємої дії.

Тестові стаби можуть повертати список завчасно вказаних значень, при послідовних викликах. Приклад з документації PHPUnit:

// Create a stub for the SomeClass class.
$stub = $this->createStub(SomeClass::class);

// Configure the stub.
$stub->method('doSomething')
            ->willReturn(1, 2, 3);

// $stub->doSomething() returns a different value each time
$this->assertSame(1, $stub->doSomething());
$this->assertSame(2, $stub->doSomething());
$this->assertSame(3, $stub->doSomething());

Також існує спрощенний синтаксис створеня мока, у якому необхідно визначити декілька функцій.

$o = $this->createConfiguredStub(
   SomeInterface::class,
   [
       'doSomething'     => 'foo',
       'doSomethingElse' => 'bar',
   ]
);

Не всі IDE підтримують синтаксис createConfiguredStub: строки з назвами методів не сприймаються як методи. Відповідно при рефакторингу коду IDE може не побачити ці методи, що призведе до падіння тесту.

Як використовувати mock у тесті?

// Arrange
$dependency = $this->createMock(Dependency::class);
$dependency->expects($this->once())
           ->method('fetch')
           ->willReturn('{"data":"..."}');
$sut = new CacheService(dependency);

// Act
$result = $sut->getData();

// Assert
$this->assertStringEndsWith('{"data":"..."}', $result);

Використання моків схоже з використанням стабів, різниця полягає у єтапі arrange, де налаштовується перевірка на виклики методів тестового двійника. У прикладі використовується обмеження once – на один виклик метода. Якщо у кінці теста метод не викликався чи викликався більше одного разу – тест впаде.

Існує 6 видів обмежень:

  • any() – будь яка кількість викликів(навіть 0)
  • never() – жодного виклику
  • atLeastOnce() – мінімум один виклик
  • once() – рівно один виклик
  • atMost(int $count) – не більше ніж count разів
  • exactly(int $count) – рівно count разів

Корисні посилання

Використання Spy у PHPUnit

Документація PHPUnit про тестові двійники

Пост Мартіна Вофлера про види тестових двійників