Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Effective Unit Testing

Effective Unit Testing

KKBOX 內訓簡報

大澤木小鐵

March 16, 2017
Tweet

More Decks by 大澤木小鐵

Other Decks in Programming

Transcript

  1. class StubRepository implements RepositoryInterface { public function findUser(int $id): User

    { return new StubUser([ 'id' => 1 ]); } public function fetchUsers(array $ids): Collection { throw new \InvalidArgumentException(); } } Stub 範例
  2. class FakeSession implements SessionInterface { private $storage = []; public

    function put($name, $value) { $this->storage[$name] = $value; } public function get($name) { return $this->storage[$name] ?? null; } } Fake 範例
  3. <?xml version="1.0" encoding="UTF-8"?> <phpunit> <php> <env name="APP_ENV" value="testing"/> <env name="CACHE_DRIVER"

    value="array"/> <env name="SESSION_DRIVER" value="array"/> <env name="QUEUE_DRIVER" value="sync"/> <env name="DB_DRIVER" value="sqlite"/> <env name="DB_DATABASE" value=":memory:"/> </php> </phpunit> Laravel 中的應用
  4. $service = Mockery::mock(UserService::class); $service->shouldReceive('authenticate') ->with('jaceju', '123456') ->andReturn(false); $service->shouldReceive('authenticate') ->with('butany', '123456')

    ->andReturn(true); $userController = new UserController($service); $actual = $userController->login('jaceju', '123456'); $this->assertFalse($actual); $actual = $userController->login('butany', '123456'); $this->assertTrue($actual); Mock 範例
  5. /** @expectedException \Exception */ public function textServiceShouldThrowExceptionWhenSomethingWrong() { // ...

    $service = new UserService(); $service->getVerifiedUsers(); } 斷言範圍太廣
  6. /** * @expectedException \Illuminate\Database\QueryException */ public function textThrowExceptionWhenQueryFailed() { //

    ... $service = new UserService(); $service->getVerifiedUsers(); } /** * @expectedException \Predis\Connection\ConnectionException */ public function textThrowExceptionWhenRedisIsDisconnect() { // ... $service = new UserService(); $service->getVerifiedUsers(); } 縮小斷言範圍
  7. public function testAlbum() { $albumId = 10001; $albumIds = [$albumId];

    $wrapper = new ImageApiWrapper(); $actual = $wrapper->getAlbumUrls($albumIds); $this->assertEquals('10001/300x300.jpg', $actual[$albumId]); $actual = $wrapper->getAlbumInfos($albumIds); $this->assertEquals('10001/300x300.jpg', $actual[$albumId]['url']); } 一次測太多事
  8. protected function setUp() { $this->wrapper = new ImageApiWrapper(); } public

    function testGetAlbumUrlsSuccess() { $albumId = 10001; $expected = [ $albumId => '10001/300x300.jpg', ]; $actual = $this->wrapper->getAlbumUrls([$albumId]); $this->assertEquals($expected, $actual); } 一次只測一件事
  9. public function testGetAlbumInfoSuccess() { $albumId = 10001; $expected = [

    $albumId => [ 'id' => '10001', 'url' => '10001/300x300.jpg', ], ]; $actual = $this->wrapper->getAlbumInfos([$albumId], 'tw'); $this->assertEquals($expected, $actual); } 一次只測一件事 (續)
  10. $service = new UserService(); $verifiedUsers = $service->getVerifiedUsers(); foreach ($verifiedUsers as

    $index => $verifiedUser) { if ($index === 0) { $this->assertEquals('butany', $verifiedUser['name']); } if ($index === 1) { $this->assertEquals('jaceju', $verifiedUser['name']); } } 條件邏輯
  11. public function testUserServiceShouldReturnExpectedVerifiedUsers() { $service = new UserService(); $verifiedUsers =

    $service->getVerifiedUsers(); $this->assertCount(2, $verifiedUsers); $this->assertArrayHasKey('name', $verifiedUsers[0]); $this->assertEquals('butany', $verifiedUsers[0]['name']); $this->assertArrayHasKey('name', $verifiedUsers[1]); $this->assertEquals('jaceju', $verifiedUsers[1]['name']); } 重複的程式碼
  12. class UserServiceTest { use AssertionHelper; private $service; public function setUp()

    { $this->service = new UserService(); } } trait AssertionHelper { private function assertArrayHasKeyWithValue($array, $key, $value) { $this->assertArrayHasKey($key, $array); $this->assertEquals($value, $array[$key]); } } 利用 setUp 和 Trait
  13. class ConfigTest extends TestCase { private $config; public function testAddConfigSuccess()

    { $configFile = __DIR__ . '/fixture/config.json'; $this->config = new Config($configFile); $this->config->add('debug', true); $this->assertObjectHasAttribute('debug', $this->config); } public function testInjectConfigToAppShouldWork() { $app = new App($this->config); assertThat($app->debugMode, isTrue()); } } 相依的測試
  14. class AppTest extends TestCase { public function testInjectConfigToAppShouldWork() { $config

    = Mockery::mock(Config::class); $config->shouldRecive('toArray') ->andReturn(['debug' => true]); $app = new App($config); assertThat($app->debugMode, isTrue()); } } 獨立每個測試案例
  15. class AppTest extends TestCase { public function testInjectConfigToAppShouldWork() { //

    $config = Mockery::mock(Config::class); // $config->shouldRecive('toArray') // ->andReturn(['debug' => true]); // $app = new App($config); // assertThat($app->debugMode, isTrue()); } } 被註解的測試
  16. class ImageApiWrapperTest extends TestCase { public function testDevelopmentMode() { $dev

    = true; $app = new ImageApiWrapper($dev); } } 永不失敗的測試
  17. /** @test */ public function it_should_get_empty_array_when_get_urls_with_invalid_id() { $albumId = 'invalid_id';

    $albumIds = [$albumId]; $wrapper = new ImageApiWrapper(); $actual = $wrapper->getAlbumUrls($albumIds); $expected = [ $albumId => 'noimg/300x300.jpg', ]; $this->assertEquals($expected, $actual); } 虛假的測試
  18. public function testRunListCommandAtHomeDirectory() { $expectedFile = 'hello.txt'; $targetDirectory = '/home/jaceju';

    $this->createFile($expectedFile, $targetDirectory); $command = new ListCommand($targetDirectory); $process = Process::run($command); if ($process->getExitCode() === 0) { $this->assertEquals($expectedFile, $process->getOutput()); } } 有條件的測試
  19. public function testRunListCommandAtHomeDirectoryShouldGetExpectedFile() { $expectedFile = 'hello.txt'; $targetDirectory = '/home/jaceju';

    $this->createFile($expectedFile, $targetDirectory); $command = new ListCommand($targetDirectory); $process = Process::run($command); $this->assertEquals(0, $process->getExitCode()); $this->assertEquals($expectedFile, $process->getOutput()); } 用斷言取代條件式