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

Effective Unit Testing

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Effective Unit Testing

KKBOX 內訓簡報

Avatar for 大澤木小鐵

大澤木小鐵

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()); } 用斷言取代條件式