単体テストでありがちな落とし穴
では、ランダムデータを利用した単体テストの話に進んでいきましょう。テスト駆動開発(TDD:test-driven development)の基本原則の1つに、「コードより先にテストを作成する」というものがあります。そこで、まずはAddressオブジェクトから考えてみましょう。ごく基本的なテストの1つが、getterとsetterのテストです。通常こうしたテストでは、オブジェクトに設定した値がそのまま保持されてgetterで取り出せることを確認する必要があります。リスト1は、Addressオブジェクトのsetterとgetterを調べる単体テストの例です。
/**
* This tests asserts that values stick to the
* Address object, via setters, and that the same
* values are accessible via the getters
*/
@Test
public void addressSetterGetterTest(){
//Create some test data
String firstName="Bob";
String lastName="Reselman";
String address1="The Waldorff-Astoria Hotel";
String address2="301 Park Avenue";
String city="New York";
String state="NY";
String zip="10022";
//Apply the data to the Address object
Address address = new Address();
address.setFirstName(firstName);
address.setLastName(lastName);
address.setAddress1(address1);
address.setAddress2(address2);
address.setCity(city);
address.setState(state);
address.setZip(zip);
//Make sure that the data stuck
Assert.assertEquals(address.getFirstName(), firstName);
Assert.assertEquals(address.getLastName(), lastName);
Assert.assertEquals(address.getAddress1(), address1);
Assert.assertEquals(address.getAddress2(), address2);
Assert.assertEquals(address.getCity(), city);
Assert.assertEquals(address.getState(), state);
Assert.assertEquals(address.getZip(), zip);
}
ごく単純な例ながら、リスト1の単体テストには、ありがちな落とし穴が潜んでいます。リスト1と次に示すコードから分かるように、ここではハードコーディングした値をAddressオブジェクトに代入しており、実際に作成したコードがこのテストにパスすることを確認しています。
//Create some test data String firstName="Bob"; String lastName="Reselman"; String address1="The Waldorff-Astoria Hotel"; String address2="301 Park Avenue"; String city="New York"; String state="NY"; String zip="10022"; //Apply the data to the Address object Address address = new Address(); address.setFirstName(firstName); address.setLastName(lastName); address.setAddress1(address1); address.setAddress2(address2); address.setCity(city); address.setState(state); address.setZip(zip);
しかし結局のところ、このテストにパスするかどうかは、代入した値のみによる判断であり、他の値では確認していません。つまり、実装したコードがこのテストにパスしたとしても、"Bob"、"Reselman"、"The Waldorf-Astoria Hotel"、"301 Park Avenue"、"New York"、"NY"、"10022"という、ハードコーディングした少数のサンプルデータで正しく動作することが保証されたに過ぎないのです。
開発者は、このテストは実際に作成するコードの動作を正確に反映したモデルだと想定しています。しかし、その想定には疑問の余地があります。例えば、今から1年後に別の開発者が現れて、AddressオブジェクトのsetFirstName(String name)メソッドでは6文字以上の名前をサポートしないことにするという奇妙な決定を下したとしたらどうなるでしょうか。回帰テストのとき、実際にはエラーとなる条件が存在するにもかかわらず、"Bob"というデータは問題なくパスしてしまうのです。
もちろん、長期的に起こり得るあらゆる動作状況を1つ残らず網羅した単体テストを作成するのは困難ですし、現実に叶うものではありません。しかし、ハードコーディングのデータを使ったテストを作成するのと同じくらいの労力があれば、ランダム性を生かしたデータを使って、期待を裏切らないテストを実現することは可能です。
リスト2は、リスト1の単体テストを改良したものです。QaRandomizerを使って、複数回の反復の中でランダムデータを生成しています。
public void randomAddressSetterGetterTest() throws QaRandomizerException {
//Set the number of times to assign random data and
//run the assertions
int numberOfTimesToTest = 10;
for (int i = 0; i < numberOfTimesToTest; i++) {
//Use the QaRandomizer to get some random address data
RandomAddress randomAddress = QaRandomizer.getInstance().getAddress();
// Apply some random test data
String firstName = QaRandomizer.getInstance().getFirstName();
String lastName = QaRandomizer.getInstance().getLastName();
String address1 = randomAddress.getAddressOne();
String address2 = randomAddress.getAddressTwo();
String city = randomAddress.getCity();
String state = randomAddress.getStateProvince();
String zip = randomAddress.getPostalCode();
// Apply the data to the Address object
Address address = new Address();
address.setFirstName(firstName);
address.setLastName(lastName);
address.setAddress1(address1);
address.setAddress2(address2);
address.setCity(city);
address.setState(state);
address.setZip(zip);
// Make sure that the data stuck
Assert.assertEquals(address.getFirstName(), firstName);
Assert.assertEquals(address.getLastName(), lastName);
Assert.assertEquals(address.getAddress1(), address1);
Assert.assertEquals(address.getAddress2(), address2);
Assert.assertEquals(address.getCity(), city);
Assert.assertEquals(address.getState(), state);
Assert.assertEquals(address.getZip(), zip);
// Print out some human readable data just for
// visual satisfaction
System.out.println(String.format("%s %s,
%s %s %s, %s %s ", firstName, lastName, address1, address2, city, state, zip));
}
}
QaRandomizerには、ファーストネーム(名)とラストネーム(姓)のデータをランダムに生成できる機能があります。また、標準的な形式のランダムな住所データを格納したRandomAddressオブジェクトも生成できます(RandomAddressオブジェクトの詳細については、QaRandomizerが持つメソッドの解説の中で説明します)。
リスト2では、ファーストネーム、ラストネーム、住所の各データをランダムに生成するのみならず、テストを複数回(10回)繰り返しています。つまりこのコードでは、10組のランダムデータを生成してテストを実行し、10回パスしたということになります。当然ながら、確実性はその分高まります。
