# TDD Practice

# 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/2f136ada-b34b-6b33-de51-96db7903802e.png)






GivenとWhen-ID, When-PWの組み合わせを求めます。ここには[PICTMaster](https://ko.osdn.net/projects/pictmaster/)を用いました。

![캡처.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/de3cda52-d77c-ed04-5d95-591870cce5ea.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/5ab45f3a-07de-6885-4e10-924b44b8c289.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/7e67c99a-7cc6-c5a1-e26d-f7b5be92c081.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/b33f22e2-ad1e-eca4-51f9-88e71550de11.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/efebceb0-2740-ac50-1981-45442836c05a.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/68e25e4f-c1e9-8213-a38f-5bb413a613cd.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/4263d26a-003b-7c47-89ac-52d86f80c870.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55902/4263d26a-003b-7c47-89ac-52d86f80c870.png)


100％PASSEDです。



## まとめ

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

#### テストを漏れなく作成できる
のです。

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

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



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