まるごとPHP vol2のテスト駆動開発を写経してみました。2008年末にインプレスジャパンから発売されたので、書店では見つけにくいかもしれません。
piece frameworkに関わっている久保敦啓さんと松藤秀治さんが書いています。私がテスト駆動開発の概要を知った記事です。3年前は読んだだけでした。今回は実際に記事にしたがってコードを書いていきました。
題材はトランプゲーム「カップル」です。書いてみる(写経する)と、読むだけでは分からなかったいろいろな発見がありました。特に、リファクタリングは参考になりました。他の人がどのようにリファクタリングするのか?の一例を知ったのは大きな収穫でした。私はペアプログラミングをしたことがないのですが、他の人がプログラミングする過程を追うことが非常に学習になるのだろうな、と思いました。
また、今更ですが phpunit の機能の豊富さを再認識しました。(lime と比較すると)環境を整えるのが多少手間がかかりますが、それを差し引いても phpunit のメリットは大きいですね。
PHPUnit 3.6.7 を使ったのですが、書籍に記載されていた assertType
は deprecated になっていました。assertInstanceOf
を使うようにしました。
require('couple.php'); class Games_Couple_CardDeckTest extends PHPUnit_Framework_TestCase { private $_suits = array( Games_Couple_Suit::SPADES, Games_Couple_Suit::HEARTS, Games_Couple_Suit::DIAMONDS, Games_Couple_Suit::CLUBS ); private $_ranks = array(1,2,3,4,5,6,7,8,9,10,11,12,13); public function test52cardsOnStartingUpGame() { $carddeck = new Games_Couple_CardDeck(); $this->assertEquals(52,$carddeck->count()); } public function testDrawACard() { $carddeck = new Games_Couple_CardDeck(); $this->assertInstanceOf('Games_Couple_Card',$carddeck->draw()); } public function testPutACardAfterDrawingIt() { $couple = new Games_Couple(); $couple->draw(); $card = $couple->viewLastCard(); $this->assertContains($card->suit,$this->_suits); $this->assertContains($card->rank,$this->_ranks); } public function testPlaceCardProperlly() { $couple = new Games_Couple(); $couple->draw(); $this->assertInstanceOf('Games_Couple_Card',$couple->viewLastCard()); $this->assertInstanceOf('Games_Couple_Card',$couple->view(0,0)); $this->assertEquals($couple->viewLastCard(),$couple->view(0,0)); $couple->draw(); $this->assertInstanceOf('Games_Couple_Card',$couple->viewLastCard()); $this->assertInstanceOf('Games_Couple_Card',$couple->view(0,1)); $this->assertEquals($couple->viewLastCard(),$couple->view(0,1)); $couple->draw(); $this->assertInstanceOf('Games_Couple_Card',$couple->viewLastCard()); $this->assertInstanceOf('Games_Couple_Card',$couple->view(0,2)); $this->assertEquals($couple->viewLastCard(),$couple->view(0,2)); $couple->draw(); $this->assertInstanceOf('Games_Couple_Card',$couple->viewLastCard()); $this->assertInstanceOf('Games_Couple_Card',$couple->view(0,3)); $this->assertEquals($couple->viewLastCard(),$couple->view(0,3)); $couple->draw(); $this->assertInstanceOf('Games_Couple_Card',$couple->viewLastCard()); $this->assertInstanceOf('Games_Couple_Card',$couple->view(1,0)); $this->assertEquals($couple->viewLastCard(),$couple->view(1,0)); } public function testRemoveCardsIfPairsHorizontally() { $carddeck = new Games_Couple_CardDeck(); $carddeck->reset(); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,1)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::HEARTS,1)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,2)); $couple = new Games_Couple(); $couple->setCardDeck($carddeck); $card1 = $couple->draw(); $card2 = $couple->draw(); $card3 = $couple->draw(); $couple->remove(0,0,0,1); $this->assertSame($card3,$couple->view(0,0)); $this->assertNull($couple->view(0,1)); $this->assertNull($couple->view(0,2)); } public function testRemoveCardsIfPairsVertically() { $carddeck = new Games_Couple_CardDeck(); $carddeck->reset(); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,1)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,2)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,3)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,4)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::HEARTS,1)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,5)); $couple = new Games_Couple(); $couple->setCardDeck($carddeck); $card1 = $couple->draw(); $card2 = $couple->draw(); $card3 = $couple->draw(); $card4 = $couple->draw(); $card5 = $couple->draw(); $card6 = $couple->draw(); $this->assertSame($card6,$couple->view(1,1)); $couple->remove(0,0,1,0); $this->assertSame($card2,$couple->view(0,0)); $this->assertSame($card6,$couple->view(0,3)); $this->assertNull($couple->view(1,0)); $this->assertNull($couple->view(1,1)); } public function testRemoveCardsIfPairsDiagonally() { $carddeck = new Games_Couple_CardDeck(); $carddeck->reset(); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,1)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,2)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,3)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,4)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::HEARTS,2)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,5)); $couple = new Games_Couple(); $couple->setCardDeck($carddeck); $card1 = $couple->draw(); $card2 = $couple->draw(); $card3 = $couple->draw(); $card4 = $couple->draw(); $card5 = $couple->draw(); $card6 = $couple->draw(); $this->assertSame($card6,$couple->view(1,1)); $couple->remove(0,1,1,0); $this->assertSame($card1,$couple->view(0,0)); $this->assertSame($card6,$couple->view(0,3)); $this->assertNull($couple->view(1,0)); $this->assertNull($couple->view(1,1)); } /** * @expectedException Games_Couple_NotSameRankException */ public function testCannotRemoveCardsIfNotSameRank() { $carddeck = new Games_Couple_CardDeck(); $carddeck->reset(); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,1)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,2)); $couple = new Games_Couple(); $couple->setCardDeck($carddeck); $card1 = $couple->draw(); $card2 = $couple->draw(); $couple->remove(0,0,0,1); $this->assertSame($card1,$couple->view(0,0)); $this->assertSame($card2,$couple->view(0,1)); } /** * @expectedException Games_Couple_SameRankNotAdjecentException */ public function testCannotRemoveCardsIfNotAdjacent1() { $carddeck = new Games_Couple_CardDeck(); $carddeck->reset(); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,1)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,2)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::HEARTS,1)); $couple = new Games_Couple(); $couple->setCardDeck($carddeck); $card1 = $couple->draw(); $card2 = $couple->draw(); $card3 = $couple->draw(); $couple->remove(0,0,0,2); $this->assertSame($card1,$couple->view(0,0)); $this->assertSame($card2,$couple->view(0,1)); $this->assertSame($card3,$couple->view(0,2)); } /** * @expectedException Games_Couple_SameRankNotAdjecentException */ public function testCannotRemoveCardsIfNotAdjacent2() { $carddeck = new Games_Couple_CardDeck(); $carddeck->reset(); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,1)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,2)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,3)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,4)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,5)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,6)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,7)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,8)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::HEARTS,1)); $couple = new Games_Couple(); $couple->setCardDeck($carddeck); for ($i=1;$i<=9;$i++) { $card{$i} = $couple->draw(); } $couple->remove(0,0,2,0); $this->assertSame($card1,$couple->view(0,0)); $this->assertSame($card9,$couple->view(2,0)); } public function testNotDrawACardAfter52Drawn() { $couple = new Games_Couple(); for ($i=1;$i<=52;$i++) { $this->assertNotNull($couple->draw()); } $this->assertNull($couple->draw()); } public function testGameIsOverAfter52Drawn() { $carddeck = new Games_Couple_CardDeck(); $carddeck->reset(); for($rank=1;$rank<=13;$rank++) { $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::SPADES,$rank)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::HEARTS,$rank)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::DIAMONDS,$rank)); $carddeck->add(new Games_Couple_Card(Games_Couple_Suit::CLUBS,$rank)); } $couple = new Games_Couple(); $couple->setCardDeck($carddeck); for ($i=1;$i<=52;$i++) { $couple->draw(); } for ($i=1;$i<=26;$i++) { $this->assertfalse($couple->isGameOver()); $couple->remove(0,0,0,1); } $this->assertTrue($couple->isGameOver()); } }