導入
ソフトウェア開発を専門とするカリキュラムで学んでいるのでない限り、
テスト駆動開発は、
展開
テスト駆動開発とは
これから紹介するテスト駆動開発は、
- 作成したいソフトウェアの機能を明確にする。
- 目的の機能を、
評価可能な状態まで細分化する。 - まず、
評価のためのコードAを書く。このAをテストコードと呼ぶ。 - Aが問題なく実行されるように目的のソフトウエアのコードを作成する。
ここで1.や2.の
確実に目的の結果を出力するコードを書き、
大切なのは、
ユニットテストを積み重ねて行くテスト駆動開発は、
テスト駆動開発の例
概略設計
次のようなソフトウェアをテスト駆動開発してみましょう。
健康管理アプリを作ろう。
個人情報を入力すると、BMIや肥満度を教えてくれる。
まずはこんな大雑把なところからスタートしましょう。 せっかくですから、
このアプリケーションに登場するオブジェクトを次のように考えてみます。
- ディスプレイウインドウ:人形を表示し、
どこかに入力されたデータと、 計算したデータを表示する。 - 個人情報コンテナ:入力されたデータを保持し、
そのデータをもとにBMIや肥満度を返す。 - コントローラ:マウスやキーボードからの入力を受け取り、
適切な形式で個人情報コンテナにセットする。またディスプレイウインドウへ必要な情報をセットする。
UMLで表現すると、



作成しようとするソフトウェアがかなり具体的になってきましたね。
それでは、

BMI | 肥満度 |
---|---|
18. | 低体重 |
18. | 普通体重 |
25以上、30未満 | 肥満 |
30以上、35未満 | 肥満 |
35以上、40未満 | 肥満 |
40以上 | 肥満 |
こうして必要な情報を調べてみるとBMIや肥満度に性別や年齢は必要ないのが分かります。そのとき、
テストコードの作成と、目的のコードの作成
それでは、
ユニットテストで使用するのがassert文です。メソッドが目的の値を返すかどうかをチェックする命令です。
私の流儀ですが、
はじめに、HealthApp.
から書きます。
HealthApp.pde
//HealthApp
boolean DEBUG_MODE = true;
void setup(){
if (DEBUG_MODE == true){
println("<<Debug Mode>> setup");
noLoop();
size(200,100);
testPersonalData();
} else {
println("<<Run Mode>> setup");
}
}
void draw(){
if (DEBUG_MODE == true){
println("<<Debug Mode>> draw");
} else {
println("<<Run Mode>> draw");
}
}
void testPersonalData(){
PersonalData pd = new PersonalData("FEMALE",30, 50,165,true);
assert pd.getSex().equals("FEMALE") : "Error";
assert pd.getAge() == 30 : "Error";
assert pd.getYourWeight() == 50 : "Error";
assert pd.getYourHeight() == 165 : "Error";
assert pd.isAthlete() == false : "Error";
// assert pd.getBMI == 0 : "Error";
// assert pd.getCategory == 0 : "Error";
}
HealthApp.
に書かれたテストコードを満たすように、PersonalData.
にコードを書きます。スケルトンコードではなく最低限動作する状態のコードを書いています。
PersonalData.pde
class PersonalData{
String sex = "MALE";// 性別 MALE,FEMALE
int age = 17; // 年齢
double your_weight = 70.0; // [kg]
double your_height = 170.0; // [cm]
boolean isAthlete = false; // アスリートかどうか
double BMI = 0; // BMI
double category = 0; // 肥満指数
PersonalData(String _sex, int _age, double _weight, double _height,
boolean _isAthlete) {
sex = _sex;
age = _age;
your_weight = _weight;
your_height = _height;
isAthlete = _isAthlete;
//BMI = getBMI();
//category = getCategory();
}
public String getSex(){
return sex;
}
public int getAge(){
return age;
}
public double getYourWeight(){
return your_weight;
}
public double getYourHeight(){
return your_height;
}
public boolean isAthlete(){
return isAthlete;
}
public double getBMI(){
return BMI;
}
public double getCategory(){
return category;
}
}
誤った入力や、想定外の入力があった場合のテスト
・誤った引数を与えてみる
メソッドは常に想定される入力を受けるとは限りません。先ほどのHealthApp.
のテストコードにわざと誤ったデータを仕込みました。このような誤りは、

実際には、
そこで、
こうして、
・想定外の引数を与えてみる
例えば今回のコードで言えば、MALE
かFEMALE
どちらかを必ず入力してくれるとは限りません。この入力を保証するための工夫はいくつもありますが、MALE
でもFEMALE
でも無かった場合はNONE
がセットされるものとしました。あわせて他の要素についても想定される出力があるか確認しましょう。
次に示すコードがその例です。先ずテストで想定外の性別を入力された場合のテストコードを記述します。そしてそれを満足するようにPersonalData.
にコードを追加していきました。
HealthApp.pde
//HealthApp
boolean DEBUG_MODE = true;
void setup(){
if (DEBUG_MODE == true){
println("<<Debug Mode>> setup");
noLoop();
size(200,100);
testPersonalData();
} else {
println("<<Run Mode>> setup");
}
}
void draw(){
if (DEBUG_MODE == true){
println("<<Debug Mode>> draw");
} else {
println("<<Run Mode>> draw");
}
}
void testPersonalData(){
PersonalData pd = new PersonalData("FEMALE",30, 50,165,true);
assert pd.getSex().equals("FEMALE") : "Error";
assert pd.getAge() == 30 : "Error";
assert pd.getYourWeight() == 50 : "Error";
assert pd.getYourHeight() == 165 : "Error";
//assert pd.isAthlete() == false : "Error";
assert pd.isAthlete() == true : "Error";
// assert pd.getBMI == 0 : "Error";
// assert pd.getCategory == 0 : "Error";
pd = new PersonalData("HOGE",25,80,170,false);
assert pd.getSex().equals("NONE") : "Error";
}
PersonalData.pde
class PersonalData{
// (略)
PersonalData(String _sex, int _age, double _weight, double _height,
boolean _isAthlete) {
sex = checkSex(_sex);
// (略)
}
// (略)
private String checkSex(String val){
if (val.equals("MALE") || val.equals("FEMALE")) {
sex = val;
} else {
sex = "NONE";
}
return sex;
}
}
アルゴリズムのテスト
先ほどのコードでは実装していなかった、
50kg、

18.
PersonalData
クラスのgetBMIメソッドの戻り値が、
例として次のHealthApp.
のようにコードを書いたとします。変更のある部分のみ示します。
HealthApp.pde
にコードを追加した部分void testPersonalData(){
PersonalData pd = new PersonalData("FEMALE",30, 50,165,true);
assert pd.getSex().equals("FEMALE") : "Error";
assert pd.getAge() == 30 : "Error";
assert pd.getYourWeight() == 50 : "Error";
assert pd.getYourHeight() == 165 : "Error";
//assert pd.isAthlete() == false : "Error";
assert pd.isAthlete() == true : "Error";
assert (18.3 < pd.getBMI()) & (pd.getBMI() < 18.4) : "Error";
assert pd.getCategory() == -1 : "Error";
pd = new PersonalData("HOGE",25,80,170,false);
assert pd.getSex().equals("NONE") : "Error";
}
次にPersonalData.
への追加コードを示します。先のテストコードを満足するように書きます。
PersonalData.pde
への追加コード public double getBMI(){
BMI = your_weight / (your_height * your_height / 100 / 100);
return BMI;
}
public double getCategory(){
BMI = getBMI();
if ( BMI < 18.5 ) {
category = -1;
} else if ( BMI < 25 ) {
category = 0;
} else if ( BMI < 30 ) {
category = 1;
} else if ( BMI < 35 ) {
category = 2;
} else if ( BMI < 40 ) {
category = 3;
} else {
category = 4;
}
return category;
}
このsketchを実行すると、
さて、
このgetBMI
やgetCategory
メソッドは、
参考文献
入力フィールド用のテストマトリクスを作成する方法
単純な整数フィールドに対しては、
どのような入力値をテストすると面白い結果が得られるだろうか。以下に、 筆者達がよく使用するテスト項目を披露しよう。
- なにもしない
- 空っぽ
(デフォルト値をクリア) - 桁数や文字数の上限
(UB:Upper Bound) を超えたもの - 0(ゼロ)
- 有効な値
- 上限値
- 下限値
- (中略)
- マイナス値
『ソフトウェアテスト293の鉄則』
HealthApp.
に、PersonalData.
に書きましょう。テストコードは上の
- コンストラクタに何も引数を与えなかった場合に、
デフォルトの値がセットされるようにする。デフォルト値は 「男性、 17歳、 70kg、 170cm、 アスリートではない」 とします。 - 各値の範囲は、
年齢は16歳から110歳、 体重は30kgから150kgまで、 身長は100cmから200cmまでとします。
次に示すのがテストコードの一部です。演習にてこの作業の要求を満たしてください。
テスト項目の
//なにもしない
pd = new PersonalData();
このようにHealthApp.
のメソッドtestPersonalData
に書いて実行すると、
//なにもしない
pd = new PersonalData();
assert pd.getSex().equals("MALE") : "Error";
assert pd.getAge() == 17 : "Error";
assert pd.getYourWeight() == 70 : "Error";
assert pd.getYourHeight() == 170 : "Error";
assert pd.isAthlete() == false : "Error";
//引数無しコンスラクタ
PersonalData(){
BMI = getBMI();
category = getCategory();
}
残りの要件を満たすためのコーディングは演習とします。
演習
演習1(難易度:mediam)
引数無しコンストラクタを呼んだ場合にデフォルトの値がセットされること、
まとめ
- テスト駆動開発とは何かを学びました。
- テスト駆動開発を用いて小さなアプリケーションを作りはじめました。
学習の確認
それぞれの項目で、
- テスト駆動開発の目的とメリットが理解できましたか?
- 理解できた。気持ちよく納得した。
- 理解できた。しかし、
今ひとつスッキリしない。 - 理解できない。
- テスト駆動開発を使えるようになりましたか?
- 使えるようになった。自分のプログラミングにも活用できそうだ。
- 本文の例を理解することはできたが、
自分のプログラミングに活用できる気がしない。 - 本文の例が理解できない。
参考文献
- 『ソフトウェアテスト293の鉄則』
(Cem Kaner, James Bach, Bret Pettichord 著、 テスト技術者交流会 翻訳、 日経BP社) - 日本と異なり米国においてソフトウェアテストは1つの独立した
「職種」 のようです。その世界で長年現場経験を積んだ著者らがまとめた各鉄則は、 ソフトウエアを開発する私たちにとって大変有益です。
- 日本と異なり米国においてソフトウェアテストは1つの独立した
演習解答
- コード例を以下に示します。テストの練習としてテストの項目をごく一部に絞っています。ぜひ参考文献を参照して、
必要充分なテストを記述し実行してみてください。