TDD Practice
この投稿はTDDの遂行過程を練習のためまとめたのです。使った言語はpython, フレームワークはpytestです。
1. 開発環境
TDDでログイン・モジュールを完成するのが目標です。モジュールの機能はIDとPWを受けてDBにチェックした後、一致すればTRUEを返還します。
- DBを別に構築しなかったのでJSON形式でセーブされたファイル(User.txt)がDBの役割をする。
- コードの簡潔さのため、PWの暗号化とかハッシングはしない。
User.txtの内容は以下のようです。
{
"test1":{
"name" : "Sam",
"pw" : "test1234"
},
"testa":{
"name" : "Kim",
"pw" : "test5678"
}
}
二つのid(test1, testa)があって各のパスワードは"pw"で記録されています。
2. 開始
2.1 テスト・ケース作成
ログイン機能をGiven,When の方式で整理してみましょう。
一応Given(コンテキスト)は入力したIDが
- 存在する
- 存在しない
When(ユーザの行動)は
- ID入力
- blank
- 値入力
- PW入力
- blank
- 正しいPW入力
- 間違いPW入力
これをマップで表示すれば以下のようになります。
GivenとWhen-ID, When-PWの組み合わせを求めます。ここにはPICTMasterを用いました。
結果は
No. | Given | When_ID | When_PW |
---|---|---|---|
1 | ID未存在 | blank | blank |
2 | ID未存在 | blank | 間違いPW入力 |
3 | ID未存在 | blank | 正しいPW入力 |
4 | ID未存在 | 値入力 | blank |
5 | ID未存在 | 値入力 | 間違いPW入力 |
6 | ID未存在 | 値入力 | 正しいPW入力 |
7 | ID存在 | 値入力 | blank |
8 | ID存在 | 値入力 | 間違いPW入力 |
9 | ID存在 | 値入力 | 正しいPW入力 |
2,3番ケースはIDがblankなのでPWの正しさを判断できません。5,6番はIDが存在しないので前のようにPWの正しさを判断できません。なので五つのケースでまとめられます。
No. | Given | When_ID | When_PW |
---|---|---|---|
1 | ID未存在 | blank | blank |
4 | ID未存在 | 値入力 | blank |
7 | ID存在 | 値入力 | blank |
8 | ID存在 | 値入力 | 間違いPW入力 |
9 | ID存在 | 値入力 | 正しいPW入力 |
2.2 コードの用意
通過すべきの論理ケースができたらその後はコードを用意します。コードは大まかでLoginモジュールが入ってるmain.pyとテスト・コードが入ってるtest_sample_1.pyがあるんです。pytestとjsonがインストールされなきゃいけません。
main.py
def Login(ID, PW):
return False
test_sample_1.py
import main
def test_Case0():
pass
これからテスト・コードを作成します。
上の五つのケースをコードですれば以下のようになります。
import main
def test_Case0():
#IDはblank + PWはblank
assert main.Login(' ', ' ') == True
def test_Case1():
#未存在ID + PWはblank
assert main.Login('test2', ' ') == True
def test_Case2():
#存在ID + PWはblank
assert main.Login('test1', ' ') == True
def test_Case3():
#存在ID + 間違いPW
assert main.Login('test1', 'a1234') == True
def test_Case4():
#存在ID + 正しいPW
assert main.Login('test1', 'test1234') == True
pytestでテストを実行すると全部FAILになります。
これからCase0からTRUEで変更して行きましょう。
3. テスト・ケースからメイン・コードの完成する。
3.1 一番目のケースをPASSさせる。
一番目のケースはIDとPWが全部blankの場合です。
main.pyは下のようになってます。
import json
FIRST_CASE_RESULT = 2
SECOND_CASE_RESULT = 3
THIRD_CASE_RESULT = 4
FOURTH_CASE_RESULT = 5
with open("User.txt", "r") as f:
json_data = json.load(f)
def Login(ID, PW):
if (len(ID.strip())==0 and len(PW.strip())==0) :
return FIRST_CASE_RESULT
return False
ここでFIRST_CASE_RESULT、SECOND_CASE_RESULT、THIRD_CASE_RESULT、FOURTH_CASE_RESULTはFALSEの種類を区分するための常数です。
test_sample_1.pyは以下のように修正します。
import main
def test_Case0():
#IDはblank + PWはblank
assert main.Login(' ', ' ') == FIRST_CASE_RESULT
def test_Case1():
#未存在ID + PWはblank
assert main.Login('test2', ' ') == True
def test_Case2():
#存在ID + PWはblank
assert main.Login('test1', ' ') == True
def test_Case3():
#存在ID + 間違いPW
assert main.Login('test1', 'a1234') == True
def test_Case4():
#存在ID + 正しいPW
assert main.Login('test1', 'test1234') == True
pytestを遂行すると
Passedが1で増加しました。
3.2 二番目のケースをPASSさせる。
二番目のケースじゃ未存在IDとPWはblankの場合です。
main.pyは
import json
FIRST_CASE_RESULT = 2
SECOND_CASE_RESULT = 3
THIRD_CASE_RESULT = 4
FOURTH_CASE_RESULT = 5
with open("User.txt", "r") as f:
json_data = json.load(f)
def Login(ID, PW):
if (len(ID.strip())==0 and len(PW.strip())==0) :
return FIRST_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())==0) :
if (ID in json_data):
pass
else:
return SECOND_CASE_RESULT
return False
test_sample_1.pyは以下のように修正します。
import main
def test_Case0():
#IDはblank + PWはblank
assert main.Login(' ', ' ') == FIRST_CASE_RESULT
def test_Case1():
#未存在ID + PWはblank
assert main.Login('test2', ' ') == SECOND_CASE_RESULT
def test_Case2():
#存在ID + PWはblank
assert main.Login('test1', ' ') == True
def test_Case3():
#存在ID + 間違いPW
assert main.Login('test1', 'a1234') == True
def test_Case4():
#存在ID + 正しいPW
assert main.Login('test1', 'test1234') == True
pytestを遂行すると
Passedが2で増加しました。
3.3 三番目のケースをPASSさせる。
三番目は存在IDとPWはblankの場合です。
main.pyは
import json
FIRST_CASE_RESULT = 2
SECOND_CASE_RESULT = 3
THIRD_CASE_RESULT = 4
FOURTH_CASE_RESULT = 5
with open("User.txt", "r") as f:
json_data = json.load(f)
def Login(ID, PW):
if (len(ID.strip())==0 and len(PW.strip())==0) :
return FIRST_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())==0) :
if (ID in json_data):
pass
else:
return SECOND_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())==0) :
if (ID in json_data):
return THIRD_CASE_RESULT
return False
ちなみに三番目のif文は二番目と統合できますがここでは可読性のためにこのままのこります。
test_sample_1.py
import main
def test_Case0():
#IDはblank + PWはblank
assert main.Login(' ', ' ') == FIRST_CASE_RESULT
def test_Case1():
#未存在ID + PWはblank
assert main.Login('test2', ' ') == SECOND_CASE_RESULT
def test_Case2():
#存在ID + PWはblank
assert main.Login('test1', ' ') == THIRD_CASE_RESULT
def test_Case3():
#存在ID + 間違いPW
assert main.Login('test1', 'a1234') == True
def test_Case4():
#存在ID + 正しいPW
assert main.Login('test1', 'test1234') == True
pytestの結果は以下のようです。
Passedが3で増加しました。
3.4 四番目のケースをPASSさせる。
四番目は存在IDと間違いPWを入力する場合です。
main.py
import json
FIRST_CASE_RESULT = 2
SECOND_CASE_RESULT = 3
THIRD_CASE_RESULT = 4
FOURTH_CASE_RESULT = 5
with open("User.txt", "r") as f:
json_data = json.load(f)
def Login(ID, PW):
if (len(ID.strip())==0 and len(PW.strip())==0) :
return FIRST_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())==0) :
if (ID in json_data):
pass
else:
return SECOND_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())==0) :
if (ID in json_data):
return THIRD_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())>0) :
if (ID in json_data):
if (json_data[ID]['pw'] != PW):
return FOURTH_CASE_RESULT
return False
test_sample_1.py
import main
def test_Case0():
#IDはblank + PWはblank
assert main.Login(' ', ' ') == FIRST_CASE_RESULT
def test_Case1():
#未存在ID + PWはblank
assert main.Login('test2', ' ') == SECOND_CASE_RESULT
def test_Case2():
#存在ID + PWはblank
assert main.Login('test1', ' ') == THIRD_CASE_RESULT
def test_Case3():
#存在ID + 間違いPW
assert main.Login('test1', 'a1234') == FOURTH_CASE_RESULT
def test_Case4():
#存在ID + 正しいPW
assert main.Login('test1', 'test1234') == True
pytestの結果は以下のようです。
Passedが4で増加しました。
3.5 五番目のケースをPASSさせる。
五番目は存在IDと正しいPWを入力する場合です。
main.pyは
import json
FIRST_CASE_RESULT = 2
SECOND_CASE_RESULT = 3
THIRD_CASE_RESULT = 4
FOURTH_CASE_RESULT = 5
with open("User.txt", "r") as f:
json_data = json.load(f)
def Login(ID, PW):
if (len(ID.strip())==0 and len(PW.strip())==0) :
return FIRST_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())==0) :
if (ID in json_data):
pass
else:
return SECOND_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())==0) :
if (ID in json_data):
return THIRD_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())>0) :
if (ID in json_data):
if (json_data[ID]['pw'] != PW):
return FOURTH_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())>0) :
if (ID in json_data):
if (json_data[ID]['pw'] == PW):
return True
return False
ここで四番目のIF文と最後のIF文を統合するのができますが、可読性のためこのまま残ります。
test_sample_1.pyは変更する必要がありません。
pytestで結果を確認すると以下のようになります。
失敗なく全部PASSEDになりました。
4. リファクタリング
結果になったmain.pyを見たら2番目と3番目、4番目と5番目のIF文は統合しなきゃなりません。このようにコードを片付ける作業がリファクタリングです。リファクタリングをしてもすべてのテスト・ケースがPASSED状態にならなきゃなりません。
main.pyは以下のようになります。
import json
FIRST_CASE_RESULT = 2
SECOND_CASE_RESULT = 3
THIRD_CASE_RESULT = 4
FOURTH_CASE_RESULT = 5
with open("User.txt", "r") as f:
json_data = json.load(f)
def Login(ID, PW):
if (len(ID.strip())==0 and len(PW.strip())==0) :
return FIRST_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())==0) :
if (ID in json_data):
return THIRD_CASE_RESULT
else:
return SECOND_CASE_RESULT
if (len(ID.strip())>0 and len(PW.strip())>0) :
if (ID in json_data):
if (json_data[ID]['pw'] != PW):
return FOURTH_CASE_RESULT
else:
return True
return False
pytestでテスト結果の確認すると
100%PASSEDです。
まとめ
とても簡単なログイン機能をTDDで作成してみました。TDDの一番良い長所は
テストを漏れなく作成できる
のです。
TDD作業の流れを整理すると以下のようになります。
Storyなどの要求仕様ゲット→仕様のGiven,When分析→分析結果のテスト・ケース化→実行コード作成→リファクタリング
結果になるコードの品質はGiven-When分析結果次第です。明確な分析ができるとテスト・ケースの質も上がるし実行コードももっと安全になります。