はじめに
こんにちは、3年の阿左見です。
来年の卒業研究が怖い今日この頃。
昨日は@nobu_wt君のAWSのお話でした。僕にとってAWSはちんぷんかんぷんです。
さてMYJLab Advent Calendar 2021 6日目、書いていこうと思います。TDDです。
詳細は『テスト駆動開発入門』 | Kent Beck 著 | 和田 卓人 訳を読んでもらうと詳しくわかるかなって思います。
TDDとは
Test Driven Developmentの略称でTDDです。
日本語で言うところのテスト駆動開発ですね。BDDとか言われることもあるみたいです。
Kent Beckによって提唱された概念らしいです。
「Test-Driven Development: By Example」が2003年に書かれているので僕の方が年上ですね
TDDの目的
「動作するきれいなコード」、ロン・ジェフリーズのこの簡潔な言葉は、TDD(テスト駆動開発)の目標である。動作するきれいなコードは、あらゆる理由で価値がある。*1
がTDDの目的らしいです。綺麗なコードってほんと大事ですよね。
僕の友人は汚いコードはプログラミングできないと同義とか過激なこと言ってました。言い過ぎでは。
レッド・グリーン・リファクタリング
TDDの基本となる概念がこちらになります。
1.レッド:動作しない、おそらく最初のうちはコンパイルも通らないテストを1つ書く。
2.グリーン:そのテストを迅速に動作させる。このステップでは罪を犯してもよい。
3.リファクタリング:テストを通すために発生した重複をすべて除去する。 *2
TDDでは最初に必ず失敗するコードを書きます。(テストする機能を書く前にテストを書きます)テストツールを使っている場合にテストの失敗は赤色で表示されるのでレッドと呼びます。
その後テストが通る(グリーンになる様に)処理を記述していきます。上記の「このステップでは罪を犯しても良い」の罪とは「コードの重複」です。以降「罪=重複」とします。
最後に重複を解消していきます。
この作業を経ることで綺麗でかつ重複の少ないコードが生まれるのです。
実際にやってみる。
今回は2分探索でやってみましょう。
最初にテストコードを書きます。
import unittest
class TestCase(unittest.TestCase):
def test_binary(self):
pass
現状は実行すれば成功するテストコードです。
今回はサンプルなのでテストは一つだけにします。
python -m unittest test_binary.py
or
python3 -m unittest test_binary.py
環境によりますがどちらかのコードを実行してみましょう。(ファイルの指定をしなければ作業ディレクトリの全テストを実行できます。)
python3 -m unittest test_binary.py
..
----------------------------------------------------------------------
Ran 1 tests in 0.000s
OK
こんな感じの画面が出ると思います。一旦は成功です。
通らないテストを書く前に空の関数を用意します。
def binary_search():
pass
準備が出来たら通らないテストを書いていきます。
import unittest
from binarysearch import binary_search
class TestCase(unittest.TestCase):
def test_binary(self):
#探索用のテストデータ
test_data = [1,2,3,4,5,6,7,8,9]
#9を探索する時、正しいインデックスが表示されるか
self.assertEqual(8,binary_search(test_data,9))
実行してみましょう
E
======================================================================
ERROR: test_binary (test_binary.TestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/ren/myjlab/my-note/python/test_binary.py", line 7, in test_binary
self.assertEqual(8,binary_search(test_data,9))
TypeError: binary_search() takes 0 positional arguments but 2 were given
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
上記のような結果が出てきました。
しっかりと失敗してくれています。
ソートされていない配列が渡された時と値が存在しなかった時のテストを加えましょう。(記事書いてて気持ち悪くなった)
class TestCase(unittest.TestCase):
class TestCase(unittest.TestCase):
def test_binary(self):
#探索用のテストデータ
test_data = [1,2,3,4,5,6,7,8,9]
#9を探索する時、正しいインデックスが表示されるか
self.assertEqual(8,binary_search(test_data,9))
def test_not_exists(self):
#テストデータ②
test_data = [1,2,3,4,5,6,7,8,9]
#存在しないときは-1を返す仕様にする。
self.assertEqual(-1,binary_search(test_data,20))
def test_unsorted_binary_search(self):
#テストデータ③
test_data = [1,9,3,4,5,2,7]
#二分探索の仕様にそぐわないので-1を返します。
self.assertEqual(-1,binary_search(test_data,9))
こんな感じです。
次はbinar_ysearch.pyを編集してテストが通るようにしていきます。
def binary_search(data:list,value):
#探索する範囲の左右を設定
left = 0
right = len(data) - 1
while left <= right:
#探索範囲の中央値を計算
mid = left + right
if data[mid] == value:
#中央値と等しければ値を返す。
return mid
elif data[mid] < value:
#大きい場合は左端を変える
left = mid + 1
else:
#小さい場合は右端を変える
right = mid - 1
#見つからなかったら-1を返す。
return -1
この実装で1つ目のテストと2つ目のテストは成功するはずなので実行してみましょう。
% python3 -m unittest test_binary.py
..F
======================================================================
FAIL: test_unsorted_binary_search (test_binary.TestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/ren/myjlab/my-note/python/test_binary.py", line 21, in test_unsorted_binary_search
self.assertEqual(1,binary_search(test_data,9))
AssertionError: 1 != -1
----------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (failures=1)
失敗しました。最高ですね。
残りのテストも通るようにコードを書き換えていきたいと思います。
def binary_search(data,value):
sorted_data = sorted(data)
#エラーハンドリングの追加
if sorted_data != data:
return -1
#探索する範囲の左右を設定
left = 0
right = len(data) - 1
while left <= right:
#探索範囲の中央値を計算
mid = left + right
if data[mid] == value:
#中央値と等しければ値を返す。
return mid
elif data[mid] < value:
#大きい場合は左端を変える
left = mid + 1
else:
#小さい場合は右端を変える
right = mid - 1
#見つからなかったら-1を返す。
return -1
テストを実行してみましょう。
python3 -m unittest test_binary.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
ren@ren-pro python % python3 -m unittest test_binary.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
成功です。テストが通りました。
以上で二分探索アルゴリズムのテスト駆動開発が終わりました。
サンプルなのでこのくらいにしましょう。
##まとめ
- テスト駆動開発は動作する綺麗なコードを書くための開発手法である。
- レッド・グリーン・リファクタリングの順番に開発を進めていく。
- 重複のないコードが書ける(らしい)
もう少し詳しくTDDについて理解を深めていきたいときは「実践テスト駆動開発」を読んでみるのもいいかもしれません。
今日はこれで終わりです。ありがとうございました。次回は少し毛色を変えた記事を書きたいと思います。
参考文献及び記事
『テスト駆動開発入門』 | Kent Beck 著 | 和田 卓人 訳 [1][2]