去上了鐵哥的 PHP 測試的課程,回來一股腦的想要把每個看的到的可怕東西都 refactor,但鐵哥有交代,refactor 之前最好還是先下測試,正所謂先套保命索,再去走鋼索,好詩好詩,於是這幾天我除了重複看鐵哥的 readme 想熟記心法以外,完全醉心於 Mockery 了,之前也寫過一陣子測試,但有些情況很難模擬,部分原因是自己 code 拆的不夠細不好測,不然就是某些情況必須有外部的結果才能測試,例如說 mail 啦、資料庫等等,鐵哥介紹 Mockery 這個好用的工具以後解決了我的問題,而且我發現寫 code 為了想要好 mock 來拆 code 變成一種 refactoring 的依據,筆記一下這幾天的心得,用最簡單的方式解說,中間跳過一些繁複的過程。
DI
Game.php
class Game { public function result(DB $db) { return $db->data(); } }
DB.php
class DB { public function data() { return false; } }
上面執行 var_dump((new Game)->result(new DB));
的結果會是 false
,我們來撰寫測試。
GameTest.php
use Mockery as m; class GameTest extends PHPUnit_Framework_TestCase { public function testResult() { $db = new DB; $game = new Game(); $this->assertTrue($game->result($db)); } public function tearDown() { m::close(); } }
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors. F Time: 519 ms, Memory: 9.75Mb There was 1 failure: 1) GameTest::testResult Failed asserting that false is true. D:\www\phpunit\tests\GameTest.php:12 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
因為 DB
的 data()
回傳的是 false
,讓我們來 mock 他。
use Mockery as m; class GameTest extends PHPUnit_Framework_TestCase { public function testResult() { $db = m::mock('DB'); $db->shouldReceive('data') ->once() ->andReturn(true); $game = new Game(); $this->assertTrue($game->result($db)); } public function tearDown() { m::close(); } }
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors. . Time: 485 ms, Memory: 10.25Mb OK (1 test, 1 assertion)
Mock 內部呼叫 method
Game.php
class Game { public function result() { return $this->data(); } public function data() { return false; } }
執行 var_dump((new Game)->result());
的結果為 false
。
GameTest.php
use Mockery as m; class GameTest extends PHPUnit_Framework_TestCase { public function testResult() { $game = new Game(); $this->assertTrue($game->result()); } public function tearDown() { m::close(); } }
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors. F Time: 415 ms, Memory: 9.75Mb There was 1 failure: 1) GameTest::testResult Failed asserting that false is true. D:\www\phpunit\tests\GameTest.php:11 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
讓我們使用 partial 來 mock 他。
use Mockery as m; class GameTest extends PHPUnit_Framework_TestCase { public function testResult() { $game = m::mock('Game[data]'); $game->shouldReceive('data') ->once() ->andReturn(true); $this->assertTrue($game->result()); } public function tearDown() { m::close(); } }
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors. . Time: 442 ms, Memory: 10.25Mb OK (1 test, 1 assertion)
Mock new class
Game.php
class Game { public function result() { $db = new DB; return $db->data(); } }
執行 var_dump((new Game)->result());
結果為 false
。
GameTest.php
use Mockery as m; class GameTest extends PHPUnit_Framework_TestCase { public function testResult() { $game = new Game; $this->assertTrue($game->result()); } public function tearDown() { m::close(); } }
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors. F Time: 403 ms, Memory: 9.75Mb There was 1 failure: 1) GameTest::testResult Failed asserting that false is true. D:\www\phpunit\tests\GameTest.php:11 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
讓我們用 overload 來 mock 他。
use Mockery as m; class GameTest extends PHPUnit_Framework_TestCase { public function testResult() { $game = new Game; $mock = m::mock('overload:DB'); $mock->shouldReceive('data') ->once() ->andReturn(true); $this->assertTrue($game->result()); } public function tearDown() { m::close(); } }
執行結果
PHPUnit 4.7.5 by Sebastian Bergmann and contributors. . Time: 466 ms, Memory: 10.25Mb OK (1 test, 1 assertion)
overload
目前發現對 static
method 也有效用,但他會無法偵測到其他事件,例如 once()
、twice()
,所以 static
function 建議抽出來執行。
class Game { public function result() { $data = $this->data(); return $data; } public function data() { return DB::data(); } }
這樣就可以使用剛介紹 partial 的方法驗證了。