1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

# TDD Practice

Last updated at Posted at 2019-12-18

TDD Practice

この投稿はTDDの遂行過程を練習のためまとめたのです。使った言語はpython, フレームワークはpytestです。

1. 開発環境

TDDでログイン・モジュールを完成するのが目標です。モジュールの機能はIDとPWを受けてDBにチェックした後、一致すればTRUEを返還します。

  1. DBを別に構築しなかったのでJSON形式でセーブされたファイル(User.txt)がDBの役割をする。
  2. コードの簡潔さのため、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入力

これをマップで表示すれば以下のようになります。

tree.PNG

GivenとWhen-ID, When-PWの組み合わせを求めます。ここにはPICTMasterを用いました。

캡처.PNG

結果は

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になります。

Fail0.PNG

これから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を遂行すると

Fail1.PNG

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を遂行すると

Fail2.PNG

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の結果は以下のようです。

Fail3.PNG

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の結果は以下のようです。

Fail4.PNG

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で結果を確認すると以下のようになります。

AllPass.PNG

失敗なく全部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でテスト結果の確認すると

AllPass.PNG

100%PASSEDです。

まとめ

とても簡単なログイン機能をTDDで作成してみました。TDDの一番良い長所は

テストを漏れなく作成できる

のです。

TDD作業の流れを整理すると以下のようになります。

Storyなどの要求仕様ゲット→仕様のGiven,When分析→分析結果のテスト・ケース化→実行コード作成→リファクタリング

結果になるコードの品質はGiven-When分析結果次第です。明確な分析ができるとテスト・ケースの質も上がるし実行コードももっと安全になります。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?