Slide 1

Slide 1 text

リバーシを作って学ぶ テスト駆動開発 あかつか PHP カンファレンス香川 2024 前日祭 5/10

Slide 2

Slide 2 text

自己紹介 あかつか / @aki_artisan 神戸でPHP を書いています レガシーシステムのLaravel への移行プロジェクト中

Slide 3

Slide 3 text

つくったもの

Slide 4

Slide 4 text

つくったもの コマンドライン上で動くリバーシ CPU と対戦できる とはいえCPU はランダムに手を打つだけで弱い CPU 対CPU 、人対人も可能 PHP8.2.7 で動作確認 https://github.com/akinoriakatsuka/reversi-php2 デモします

Slide 5

Slide 5 text

つくったもの(デモが動かなかった時用) 0:00

Slide 6

Slide 6 text

モチベーション 『テスト駆動開発』という本を写経 した 自分でも何か作ってみようと思った

Slide 7

Slide 7 text

テスト駆動開発とは

Slide 8

Slide 8 text

テスト駆動開発とは テストを先に書いてから実装を行う開発方法 今から何を作るかが明確になるので、開発がスムーズに進む リグレッションからの保護があるので、コード変更を比較的気軽にでき る レッド - グリーン - リファクタリング レッド:失敗するテストを書く グリーン:テストを通るように(愚直に)実装する リファクタリング:テストが通る状態のままでコードを整理する(重複 を排除するなど)

Slide 9

Slide 9 text

テスト駆動開発の例

Slide 10

Slide 10 text

テスト駆動開発の例 石を置く処理をテスト駆動開発で作ってみます 指定した位置に石を置く 挟んだ石をひっくり返す ( 石を置けるかどうかを判別する)

Slide 11

Slide 11 text

テスト駆動開発の例 初期状態のコード抜粋

Slide 12

Slide 12 text

テスト駆動開発の例

Slide 13

Slide 13 text

テスト final class GameTest extends TestCase { public function testProcess(): void { $board = new Board(1, 8); $board->setStone(0, 0, new Stone(Color::BLACK)); $board->setStone(0, 1, new Stone(Color::WHITE)); $game = new Game($board); $game->process(0, 2); $this->assertSame($board->cell_list[0][1]?->getColor(), Color::BLACK); } }

Slide 14

Slide 14 text

テスト結果(レッド) 1) Tests\GameTest::testProcess Failed asserting that two variables reference the same object. --- Expected +++ Actual @@ @@ -App\Game\Color Enum #473 (WHITE) +App\Game\Color Enum #211 (BLACK)

Slide 15

Slide 15 text

仮実装 置かれた場所と、ひっくり返す場所を決め打ちでとりあえず実装 public function process(int $x, int $y): void { $stone = new Stone($this->turn); $this->board->setStone($x, $y, $stone); if($x === 0 && $y === 2) { $this->board->cell_list[0][1]->flip(); } $this->toggleTurn(); }

Slide 16

Slide 16 text

テスト結果(グリーン)🎉 .... 4 / 4 (100%) Time: 00:00.024, Memory: 8.00 MB OK (4 tests, 4 assertions)

Slide 17

Slide 17 text

これでいいのか? その感覚は全く正しいです 少し辛抱して先に進みましょう

Slide 18

Slide 18 text

テスト駆動開発の例

Slide 19

Slide 19 text

テスト final class GameTest extends TestCase { public function testProcess2(): void { $board = new Board(1, 8); $board->setStone(0, 0, new Stone(Color::BLACK)); $board->setStone(0, 1, new Stone(Color::WHITE)); $board->setStone(0, 2, new Stone(Color::WHITE)); $game = new Game($board); $game->process(0, 3); $this->assertSame($board->cell_list[0][1]?->getColor(), Color::BLACK); $this->assertSame($board->cell_list[0][2]?->getColor(), Color::BLACK); } }

Slide 20

Slide 20 text

テスト結果(レッド) 1) Tests\GameTest::testProcess2 Failed asserting that two variables reference the same object. --- Expected +++ Actual @@ @@ -App\Game\Color Enum #484 (WHITE) +App\Game\Color Enum #204 (BLACK) /Users/akinori/development/reversi-php2/tests/GameTest.php:66 FAILURES! Tests: 5, Assertions: 5, Failures: 1.

Slide 21

Slide 21 text

仮実装 置かれた場所と、ひっくり返す場所を決め打ちでとりあえず実装 public function process(int $x, int $y): void { $stone = new Stone($this->turn); $this->board->setStone($x, $y, $stone); if($x === 0 && $y === 2) { $this->board->cell_list[0][1]->flip(); } if($x === 0 && $y === 3) { $this->board->cell_list[0][1]->flip(); $this->board->cell_list[0][2]->flip(); } $this->toggleTurn(); }

Slide 22

Slide 22 text

テスト結果(グリーン)🎉 ..... 5 / 5 (100%) Time: 00:00.024, Memory: 8.00 MB OK (5 tests, 6 assertions)

Slide 23

Slide 23 text

リファクタリング コードに重複がある そもそも特定条件下でしか動かない →一般化する とりあえず一方向でやってみる

Slide 24

Slide 24 text

石をひっくり返す時のルール 1 マスずつ進んでいく 相手の石がある時は進む 自分の石に当たったら止まる 盤の外に出たら止まる 石のないマスに当たったら止まる 自分の石で止まった場合は、途中の相手の石をひっくり返す

Slide 25

Slide 25 text

リファクタリング リファクタリングではこれまでに書いたテストが依然として通ることを確認し ながら進める while (true) { $y -= 1; if ($y <= 0) { break; } $cell = $this->board->cell_list[$x][$y]; if ($cell === null) { break; } if ($cell->getColor() === $this->turn) { break; } $cell->flip(); }

Slide 26

Slide 26 text

テスト結果(グリーン)🎉 ..... 5 / 5 (100%) Time: 00:00.014, Memory: 8.00 MB OK (5 tests, 6 assertions)

Slide 27

Slide 27 text

続きは… その後もテストケースを追加して、実装を進めていく red - green - red - green - refactoring みたいなペース

Slide 28

Slide 28 text

作って得られた学び

Slide 29

Slide 29 text

作って得られた学び 手動テストが大変な場合に特に効果を発揮する リバーシは手動で色々なパターンをテストするのが大変 リグレッションに対する保護があると自信を持って開発が進められる

Slide 30

Slide 30 text

作って得られた学び テストがかけるくらい疎結合になっていると、仕様追加も楽 CPU との対戦を追加するのは簡単だった

Slide 31

Slide 31 text

作って得られた学び テストしづらい時はスーパーセットになるような機能を作っておき、 それを使ってテストすると楽 例えば8x8 の盤面だけでは、デカくてテストしづらいので、小さい盤面も 作れるようにした とはいえ、実装がテストに影響を受けている感は否めない 皆さんの意見や経験を聞きたい!

Slide 32

Slide 32 text

おすすめ書籍

Slide 33

Slide 33 text

『テスト駆動開発』 Java で書いてあるので、PHP で書く 場合は少し違うところがある Java でまずは写経してみるのがおす すめ とにかく、写経してくれ!

Slide 34

Slide 34 text

『単体テストの考え方/ 使 い方』 単体テストに関する体系的な知識が 得られる テストだけでなく、設計にも役立つ コマンドクエリ分離について学べ たのが特に良かった

Slide 35

Slide 35 text

『ちょうぜつソフトウェ ア設計入門』 PHP でテスト駆動開発を勉強できま す PHPUnit のインストールから autoload の設定まであり、 初心者に優しい 今回のゲストスピーカー 田中ひさてるさんの著書です!

Slide 36

Slide 36 text

みなさんもMy リバーシつくって遊びましょう

Slide 37

Slide 37 text

ご清聴ありがとうございました