paloma blog

NWエンジニアやってます。主に自宅環境のお遊びを書きます。Pythonもちょっと。タイトルは好きなカクテルから。

スリーカードポーカーゲームを作りたい unittest編

最近ずっと触っているスリーカードポーカーゲームですが、スクリプトを動かし動かしで作成してきました。
そろそろテストコードも書いてみようということでunittest編です。

私はNW機器の構築が本業なので単体テストというものがずっとピンと来なかったんですよね。 NW機器の単体テストはコマンドで機器のパラメータ取って終わりというものが多いと思います。
(私はteraterm macroでパパっと)

NWの試験はEnd間で疎通を取ってみるほうがバグ出しがし易いですからね。

でもスクリプトやプログラムだと関数の部品ごとに動くかどうか確認することを単体テストと言うんですね。
なるほどねー。

どこまで試験するかはよく課題に上がると思いますが、どこまでやりましょう。
一応動かしてはいるのでとりあえず関数の所だけにしましょう。

単体テストは組み込みのunittestを使ってみます。

unittestの使い方

いろんなサイト見たところ、テストしたい関数を作って戻り値を書いておけばいい様です。
assertEqualでチェックしてるものが多いですね。

でも私のコードでは問題が。

  • returnがない処理
    • カードデッキを作ってシャッフルする関数があるんですがこの場合は何が正になるんでしょうか。
      type classって何が戻ってるのかよくわからないです。
>>> card = pokerapp.Deck()
>>> type(card)
<class 'pokerapp.Deck'>
>>> card.shuffle()
>>> type(card)
<class 'pokerapp.Deck'>
  • 戻り値が変わる場合
    • ポーカーなのでカードを配る関数があります。
      もちろん毎回手札が変わるのですが、これはどうしたら…。
      と思ったら正規表現で判定できそうです。

試験範囲

とりあえず

  • デッキ作成
  • カード配る
  • 手役判定
  • 手札勝負判定
  • ボーナス払い戻し

くらいのテストを作っておきましょう。

デッキ作成

トランプを作ってシャッフルするだけなので戻り値がありません。(たぶん)
どう試験するんだと思ったらassert使わなくてもokになりました。

これは正常に処理されたからOKということになるのかな。

masashi@PC-ubuntu:~/Three-card-poker$ cat test_pokerapp.py 
import unittest
import pokerapp

class TestPokerApp(unittest.TestCase):

    def setUp(self):
        self.card = pokerapp.Deck()
        
    def test_createdeck(self):
        self.card.shuffle()

if __name__ == '__main__':
    unittest.main() 

setUpは各テストごとに呼ばれるので各テストでデッキを作る様に作成してます。
これも継承というか、変数使いまわせるんでしょうか。
でもインスタンス作ってるわけじゃないしね。

とりあえずOKが返ってきました。

masashi@PC-ubuntu:~/Three-card-poker$ python3 test_pokerapp.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
カード配り、役判定

配りから役判定まで書きます。判定の戻り値はタプルで役ランク、手札の数字が返ります。

([1], [2, 5, 11])みたいな感じね。

戻り値がこの形になるよう正規表現を書けばいいですね。
assertRegexという関数で判定できるみたいです。

また、get_roleで役名が返るのでこれも判定しましょう。

役名は全部"One pair!"みたいな形なので".+¥!"でいいでしょう。

同じ形でディーラーの判定も作ります。
ディーラーは手札によっては降りる処理が入ります。

   def test_player(self):
        player = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()
        p_role = pokerapp.Handcheck(player).get_role()

        self.assertRegex(str(p_hand), '\(\[\d\], \[(\d*, ){2}\d*\]\)')
        self.assertRegex(str(p_role), '.+\!')

    def test_dealer(self):
        dealer = pokerapp.Handout(self.card)

        d_hand = pokerapp.Handcheck(dealer, flag=True).result()
        d_role = pokerapp.Handcheck(dealer, flag=True).get_role()

        self.assertRegex(str(d_hand), '\(\[\d\], \[(\d*, ){2}\d*\]\)')
        self.assertRegex(str(d_role), '.+\!|.+')
手札勝負

プレイヤー、ディーラーの手札を比較します。 戻り値は

  • 0 : プレイヤー勝利
  • 1 : ディーラー勝利
  • 2 : 引き分け

です。

orが使えて助かりました。

   def test_match(self):
        player = pokerapp.Handout(self.card)
        dealer = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()
        d_hand = pokerapp.Handcheck(dealer, flag=True).result()

        match = pokerapp.Match(p_hand, d_hand)
        self.assertEqual(match, 0 or 1 or 2)
ボーナス払い戻し

役から賭け金 * n倍の払い戻しをします。
これも数字が返るだけなので"¥d+"でいいですね。

   def test_payoff(self):
        player = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()

        pay_ante = pokerapp.Payoff(10, p_hand[0]).ante_bonus()
        pay_pairplus = pokerapp.Payoff(10, p_hand[0]).pairplus_bonus()

        self.assertRegex(str(pay_ante), '\d+')
        self.assertRegex(str(pay_pairplus), '\d+')

動かしてみる

全体はこんな感じです。
重複コードが多いですが、こんなもんなんでしょうか。

  • test_poketapp.py
import unittest
import pokerapp

class TestPokerApp(unittest.TestCase):

    def setUp(self):
        self.card = pokerapp.Deck()
        
    def test_createdeck(self):
        self.card.shuffle()

    def test_player(self):
        player = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()
        p_role = pokerapp.Handcheck(player).get_role()

        self.assertRegex(str(p_hand), '\(\[\d\], \[(\d*, ){2}\d*\]\)')
        self.assertRegex(str(p_role), '.+\!')

    def test_dealer(self):
        dealer = pokerapp.Handout(self.card)

        d_hand = pokerapp.Handcheck(dealer, flag=True).result()
        d_role = pokerapp.Handcheck(dealer, flag=True).get_role()

        self.assertRegex(str(d_hand), '\(\[\d\], \[(\d*, ){2}\d*\]\)')
        self.assertRegex(str(d_role), '.+\!|.+')

    def test_match(self):
        player = pokerapp.Handout(self.card)
        dealer = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()
        d_hand = pokerapp.Handcheck(dealer, flag=True).result()

        match = pokerapp.Match(p_hand, d_hand)
        self.assertEqual(match, 0 or 1 or 2)

    def test_payoff(self):
        player = pokerapp.Handout(self.card)

        p_hand = pokerapp.Handcheck(player).result()

        pay_ante = pokerapp.Payoff(10, p_hand[0]).ante_bonus()
        pay_pairplus = pokerapp.Payoff(10, p_hand[0]).pairplus_bonus()

        self.assertRegex(str(pay_ante), '\d+')
        self.assertRegex(str(pay_pairplus), '\d+')


if __name__ == '__main__':
    unittest.main() 

詳細がわかりませんが一応エラーなく完了しました!

masashi@PC-ubuntu:~/Three-card-poker$ python3 test_pokerapp.py 
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK

テストコード書いてみて思ったこと

関数が動くだけでOKにはなりますが、より精度の高いテストをしようとすると
期待する戻り値を正確に理解していないといけませんね。

コードも本体プログラムと似たものになりましたが、プログラムとテストは表裏一体なんですかねぇ。

今後について

一応OKは出ましたが、もともと本体スクリプトで動いていたものをテストコードとして置き換えただけなので、
これが正しいものなのかはわかりません。

また、エラー判定等を考慮していないのでこの辺の対策を進めたいですね。

  • エラー時の処理のしかた
  • 対話型プログラムの試験方法

など。

でも本体プログラムを書き換えてもテストコードが通ればOKという点では1つ手間が減った気がします。

参考サイト

unittest --- ユニットテストフレームワーク — Python 3.8.3 ドキュメント

リポジトリ

github.com