Diceクラスの不具合を改修するよ（追加テスト編）

# 概要および趣旨

## 概要

前回実装したDiceクラス↓には大きな欠陥があるらしいよ（記事コメント参照）。

https://qiita.com/gametsukurou/items/b8b006fcecd3742a333d

Javaに慣れているとインスタンス変数にしか見えないものが、Pythonの世界ではクラス変数の扱いになるんだって。怖い怖い。

## どうまずいの？

不具合を放っておくと、別インスタンスを生成するたびにシード値が勝手に（いや実装通りなんだけど）書き変わり、複数のDiceインスタンスで同じシード値を共有してしまう。
「呼び出し側がシード値Aのつもりで使ってたダイスが実はシード値Bで動いていた」
ってのは確かにまずい。
「プログラムは思った通りに動かない。書いた通りに動く」っていう古の賢人の言葉を思い出したよ。

# 今回やること

というわけで、前回の実装のダメな点を追加テストで明らかにしたいんだけど、その前段階としてテストコードをDiceクラスのモジュールから分離しようと思う。

なので、今回やることは以下の3つ。

- テストコードを別モジュールに分離
- 別モジュールに新たなテストコードを追加
- 追加したテストコードを動かして不具合を明らかにする

ボリューム満点だよ。

## 既存テストコードの分離

前回作った「Dice.py」と同じ階層にモジュール「DiceTest.py」を新規作成。

IDEを使ってるなら、ソースフォルダを「src」と「test」に分けてDice.pyをsrc配下、DiceTest.pyをtest配下に置くと見通しが良くなると思う。

DiceTest.pyが作成出来たら、前回記事の

```Python
if __name__ == "__main__":
```

以下の記述をカット＆ペースト。
そのままだとビルドエラー（この用語が適切かわからんけど）が発生するので、冒頭に以下のインポート宣言を追加。

```Python
from Dice import Dice
import time
```

結果、以下のコードになっているはず。

```DiceTest.py
'''
Created on 2022/10/09

@author: 芸夢 作郎
'''

import time
from Dice import Dice

if __name__ == "__main__":

    # シード値に350を指定する
    diceExplicit1 = Dice(350)
    print('① シード値指定1回目／シード値: [{}]'.format(diceExplicit1.seedValue))
    for i in  range(0, 100, 1):
        # 1000面ダイスを100回転がす
        print(diceExplicit1.roll(1000))
    print()

    time.sleep(2)

    # シード値を指定しない
    diceTimestamp1 = Dice()
    print('② シード値にタイムスタンプを使用1回目／シード値: [{}]'.format(diceTimestamp1.seedValue))
    for i in  range(0, 100, 1):
        # 1000面ダイスを100回転がす
        print(diceTimestamp1.roll(1000))
    print()

    time.sleep(2)

    # シード値に350を指定する
    diceExplicit2 = Dice(350)
    print('③ シード値指定2回目／シード値: [{}]'.format(diceExplicit2.seedValue))
    for i in  range(0, 100, 1):
        # 1000面ダイスを100回転がす
        print(diceExplicit2.roll(1000))
    print()

    time.sleep(2)

    # シード値を指定しない
    diceTimestamp2 = Dice()
    print('④ シード値にタイムスタンプを使用2回目／シード値: [{}]'.format(diceTimestamp2.seedValue))
    for i in  range(0, 100, 1):
        # 1000面ダイスを100回転がす
        print(diceTimestamp2.roll(1000))
    print()

    time.sleep(2)

    # シード値に350を指定する
    diceExplicitTheOtherSide = Dice(350)
    print('⑤ シード値指定3回目（面数を変更）／シード値: [{}]'.format(diceExplicitTheOtherSide.seedValue))
    for i in  range(0, 100, 1):
        # 2000面ダイスを100回転がす
        print(diceExplicitTheOtherSide.roll(2000))
    print()
```

念のため実行すると、昨日と同様の結果になる。
本体もテストコードもロジックには手を入れていないから当たり前。

昨日は実行結果を貼るのを端折ったけど、今日は貼っておくよ。
後々の比較で必要になるし。

```
テスト1実行開始
① シード値指定1回目／シード値: [350]
912
372
822
185
412
867
687
321
668
364
（中略）

② シード値にタイムスタンプを使用1回目／シード値: [1665323375739]
496
524
799
570
591
758
754
912
90
807
（中略）

③ シード値指定2回目／シード値: [350]
912
372
822
185
412
867
687
321
668
364
（中略）

④ シード値にタイムスタンプを使用2回目／シード値: [1665323379759]
14
908
172
942
431
861
449
142
29
379
（中略）

⑤ シード値指定3回目（面数を変更）／シード値: [350]
1823
744
1644
369
823
1733
1374
642
1335
727
（以下略）
```

## 新たなテストコードを追加

複数のダイスを同時に転がして出目を横並びで確認するテストケースを追加するよ。
期待値は「シード値と出目の最大値が同じであれば、必ず先日と同様の結果が返ってくること（＝各ダイスのシード値が相互に干渉していないこと）」。
確認するためのコードは以下の通り。

```Python
	# シード値に350を指定する
	dice350A = Dice(350)
	time.sleep(2)
	# シード値を指定しない
	diceTimestamp1 = Dice()
	time.sleep(2)
	# シード値に350を指定する
	dice350B = Dice(350)
	time.sleep(2)
	# シード値を指定しない
	diceTimestamp2 = Dice()
	time.sleep(2)
	# シード値に700を指定する
	dice700 = Dice(700)

	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print('ロール{}回目'.format(i + 1))
		print('シード値350のダイスロール1／シード値: [{}] 出目: [{}]'.format(dice350A.seedValue, dice350A.roll(1000)))
		print('シード値350のダイスロール2／シード値: [{}] 出目: [{}]'.format(dice350B.seedValue, dice350B.roll(1000)))
		print('シード値にタイムスタンプを使用したダイスロール1／シード値: [{}] 出目: [{}]'.format(diceTimestamp1.seedValue, diceTimestamp1.roll(1000)))
		print('シード値にタイムスタンプを使用したダイスロール2／シード値: [{}] 出目: [{}]'.format(diceTimestamp2.seedValue, diceTimestamp2.roll(1000)))
		print('シード値700のダイスロー2／シード値: [{}] 出目: [{}]'.format(dice700.seedValue, dice700.roll(1000)))
		print()
	print()
```

何も考えずにDiceTest.pyの末尾に追記してもいいんだけど、確認観点が違うので関数化して別管理したいよね。

というわけで、2つのテストコードを関数化したDiceTest.pyは以下の通り。

```DiceTest.py
'''
Created on 2022/10/09

@author: 芸夢 作郎
'''

import time
from Dice import Dice

def test1():
	print('テスト1実行開始')
	# シード値に350を指定する
	diceExplicit1 = Dice(350)
	print('① シード値指定1回目／シード値: [{}]'.format(diceExplicit1.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceExplicit1.roll(1000))
	print()

	time.sleep(2)

	# シード値を指定しない
	diceTimestamp1 = Dice()
	print('② シード値にタイムスタンプを使用1回目／シード値: [{}]'.format(diceTimestamp1.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceTimestamp1.roll(1000))
	print()

	time.sleep(2)

	# シード値に350を指定する
	diceExplicit2 = Dice(350)
	print('③ シード値指定2回目／シード値: [{}]'.format(diceExplicit2.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceExplicit2.roll(1000))
	print()

	time.sleep(2)

	# シード値を指定しない
	diceTimestamp2 = Dice()
	print('④ シード値にタイムスタンプを使用2回目／シード値: [{}]'.format(diceTimestamp2.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceTimestamp2.roll(1000))
	print()

	time.sleep(2)

	# シード値に350を指定する
	diceExplicitTheOtherSide = Dice(350)
	print('⑤ シード値指定3回目（面数を変更）／シード値: [{}]'.format(diceExplicitTheOtherSide.seedValue))
	for i in  range(0, 100, 1):
		# 2000面ダイスを100回転がす
		print(diceExplicitTheOtherSide.roll(2000))
	print()
	return

def test2():
	print('テスト2実行開始')
	# シード値に350を指定する
	dice350A = Dice(350)
	time.sleep(2)
	# シード値を指定しない
	diceTimestamp1 = Dice()
	time.sleep(2)
	# シード値に350を指定する
	dice350B = Dice(350)
	time.sleep(2)
	# シード値を指定しない
	diceTimestamp2 = Dice()
	time.sleep(2)
	# シード値に700を指定する
	dice700 = Dice(700)

	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print('ロール{}回目'.format(i + 1))
		print('シード値350のダイスロール1／シード値: [{}] 出目: [{}]'.format(dice350A.seedValue, dice350A.roll(1000)))
		print('シード値350のダイスロール2／シード値: [{}] 出目: [{}]'.format(dice350B.seedValue, dice350B.roll(1000)))
		print('シード値にタイムスタンプを使用したダイスロール1／シード値: [{}] 出目: [{}]'.format(diceTimestamp1.seedValue, diceTimestamp1.roll(1000)))
		print('シード値にタイムスタンプを使用したダイスロール2／シード値: [{}] 出目: [{}]'.format(diceTimestamp2.seedValue, diceTimestamp2.roll(1000)))
		print('シード値700のダイスロー2／シード値: [{}] 出目: [{}]'.format(dice700.seedValue, dice700.roll(1000)))
		print()
	print()
	return

if __name__ == "__main__":
	test1()
	test2()

```

## テスト

確認したいのはテスト2の実行結果。
コンソールログを確認すると、結果は以下の通り。

```
テスト2実行開始
ロール1回目
シード値350のダイスロール1／シード値: [350] 出目: [171]
シード値350のダイスロール2／シード値: [350] 出目: [925]
シード値にタイムスタンプを使用したダイスロール1／シード値: [1665318474352] 出目: [197]
シード値にタイムスタンプを使用したダイスロール2／シード値: [1665318478380] 出目: [459]
シード値700のダイスロー2／シード値: [700] 出目: [717]

ロール2回目
シード値350のダイスロール1／シード値: [350] 出目: [221]
シード値350のダイスロール2／シード値: [350] 出目: [855]
シード値にタイムスタンプを使用したダイスロール1／シード値: [1665318474352] 出目: [544]
シード値にタイムスタンプを使用したダイスロール2／シード値: [1665318478380] 出目: [630]
シード値700のダイスロー2／シード値: [700] 出目: [811]

ロール3回目
シード値350のダイスロール1／シード値: [350] 出目: [332]
シード値350のダイスロール2／シード値: [350] 出目: [871]
シード値にタイムスタンプを使用したダイスロール1／シード値: [1665318474352] 出目: [6]
シード値にタイムスタンプを使用したダイスロール2／シード値: [1665318478380] 出目: [622]
シード値700のダイスロー2／シード値: [700] 出目: [862]

（中略）

ロール18回目
シード値350のダイスロール1／シード値: [350] 出目: [504]
シード値350のダイスロール2／シード値: [350] 出目: [501]
シード値にタイムスタンプを使用したダイスロール1／シード値: [1665318474352] 出目: [168]
シード値にタイムスタンプを使用したダイスロール2／シード値: [1665318478380] 出目: [159]
シード値700のダイスロー2／シード値: [700] 出目: [605]

ロール19回目
シード値350のダイスロール1／シード値: [350] 出目: [625]
シード値350のダイスロール2／シード値: [350] 出目: [666]
シード値にタイムスタンプを使用したダイスロール1／シード値: [1665318474352] 出目: [407]
シード値にタイムスタンプを使用したダイスロール2／シード値: [1665318478380] 出目: [187]
シード値700のダイスロー2／シード値: [700] 出目: [567]

ロール20回目
シード値350のダイスロール1／シード値: [350] 出目: [154]
シード値350のダイスロール2／シード値: [350] 出目: [428]
シード値にタイムスタンプを使用したダイスロール1／シード値: [1665318474352] 出目: [988]
シード値にタイムスタンプを使用したダイスロール2／シード値: [1665318478380] 出目: [444]
シード値700のダイスロー2／シード値: [700] 出目: [845]

（以下略）
```

あれあれあれ、テスト1の実行結果と全然違う。
この乱数列は一体何だろう。

前稿の指摘通り、Diceクラス内のクラス変数が悪さをしているのかな。

というわけで、以下のコードをtest1に追加して実行してみる。

```Python
	time.sleep(2)

	# シード値に700を指定する
	diceSeedVal700 = Dice(700)
	print('⑥ シード値700／シード値: [{}]'.format(diceSeedVal700.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceSeedVal700.roll(1000))
	print()
```

結果は以下の通り。

```
⑥ シード値700／シード値: [700]
171
925
197
459
717
221
855
544
630
811
332
871
6
622
862
（中略）
504
501
168
159
605
625
666
407
187
567
154
428
988
444
845
```

較べれば判るとおり、テスト2のロール1回目～20回目の出目の並びと、テスト1（追加）の出目の並びが完全に一致している。

シード値の異なる複数のダイスを並列で転がしていたつもりが、シード値700（最後に指定したシード値）のダイス1個（見た目複数に見える）を一生懸命転がしていたことになる。

確かにこれはおかしい。

# おわりに

というわけで、次稿でこの不具合を改修するよ。

今回コードの改修が多かったので、各モジュールの最終形を共有するよ。

```Dice.py
'''
Created on 2022/10/08

@author: 芸夢 作郎
'''

import random
import time

class Dice():
    # TODO インスタンス変数を使用するよう改修
    seedValue = None

    def __init__(self, aSeedValue = None):
        if (aSeedValue == None):
            self.seedValue = int(time.time() * 1000)
        else:
            self.seedValue = aSeedValue
        random.seed(self.seedValue)
        return

    # TODO 最大面数（aMaxSidesNum）に整数値以外が指定された場合の対処法も考慮
    def roll(self, aMaxSidesNum):
        ret = random.randint(1, aMaxSidesNum)
        return ret

```

```DiceTest.py
'''
Created on 2022/10/09

@author: 芸夢 作郎
'''

import time
from Dice import Dice

def test1():
	print('テスト1実行開始')
	# シード値に350を指定する
	diceExplicit1 = Dice(350)
	print('① シード値指定1回目／シード値: [{}]'.format(diceExplicit1.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceExplicit1.roll(1000))
	print()

	time.sleep(2)

	# シード値を指定しない
	diceTimestamp1 = Dice()
	print('② シード値にタイムスタンプを使用1回目／シード値: [{}]'.format(diceTimestamp1.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceTimestamp1.roll(1000))
	print()

	time.sleep(2)

	# シード値に350を指定する
	diceExplicit2 = Dice(350)
	print('③ シード値指定2回目／シード値: [{}]'.format(diceExplicit2.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceExplicit2.roll(1000))
	print()

	time.sleep(2)

	# シード値を指定しない
	diceTimestamp2 = Dice()
	print('④ シード値にタイムスタンプを使用2回目／シード値: [{}]'.format(diceTimestamp2.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceTimestamp2.roll(1000))
	print()

	time.sleep(2)

	# シード値に350を指定する
	diceExplicitTheOtherSide = Dice(350)
	print('⑤ シード値指定3回目（面数を変更）／シード値: [{}]'.format(diceExplicitTheOtherSide.seedValue))
	for i in  range(0, 100, 1):
		# 2000面ダイスを100回転がす
		print(diceExplicitTheOtherSide.roll(2000))
	print()

	time.sleep(2)

	# シード値に700を指定する
	diceSeedVal700 = Dice(700)
	print('⑥ シード値700／シード値: [{}]'.format(diceSeedVal700.seedValue))
	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print(diceSeedVal700.roll(1000))
	print()
	return

def test2():
	print('テスト2実行開始')
	# シード値に350を指定する
	dice350A = Dice(350)
	time.sleep(2)
	# シード値を指定しない
	diceTimestamp1 = Dice()
	time.sleep(2)
	# シード値に350を指定する
	dice350B = Dice(350)
	time.sleep(2)
	# シード値を指定しない
	diceTimestamp2 = Dice()
	time.sleep(2)
	# シード値に700を指定する
	dice700 = Dice(700)

	for i in  range(0, 100, 1):
		# 1000面ダイスを100回転がす
		print('ロール{}回目'.format(i + 1))
		print('シード値350のダイスロール1／シード値: [{}] 出目: [{}]'.format(dice350A.seedValue, dice350A.roll(1000)))
		print('シード値350のダイスロール2／シード値: [{}] 出目: [{}]'.format(dice350B.seedValue, dice350B.roll(1000)))
		print('シード値にタイムスタンプを使用したダイスロール1／シード値: [{}] 出目: [{}]'.format(diceTimestamp1.seedValue, diceTimestamp1.roll(1000)))
		print('シード値にタイムスタンプを使用したダイスロール2／シード値: [{}] 出目: [{}]'.format(diceTimestamp2.seedValue, diceTimestamp2.roll(1000)))
		print('シード値700のダイスロー2／シード値: [{}] 出目: [{}]'.format(dice700.seedValue, dice700.roll(1000)))
		print()
	print()

	return

if __name__ == "__main__":
	test1()
	test2()


```



ではまた。