2. ● Test Definition
● Test Method
– Four-Phase Test
● Assertion Method
– Assertion Message
● Testcase Class
● Test Execution
● Test Runner
● Testcase Object
● Test Suite Object
● Test Discovery
● Test Enumeration
● Test Selection
4. How It Works(1)
● テストコードってどこに書くの?
● ひとつひとつのテスト毎にメソッド (Test Method) にし
てクラスに配置しましょう
● How It Works
● 各テストをメソッド/手続き/関数のかたちで Four-
Phase(358) の実装を行い、Fully Automated Test と
する。
● 大事なのは、 assertion を書いて自己テストコード
(Self-Checking Test:26) とすること
5. How It Works(2)
● Test Method には標準 Template がある
● Simple Success Test
– 正常系のテスト。 Fixture setup から result verification ま
で一本道
● Expected Exception Test
– 例外系のテスト
● Constructor Test
– オブジェクトを作成し属性をテストするだけのテスト
6. Why We Do This
● 手続き型言語の場合
● Test Method をファイルやモジュールに書く
● オブジェクト指向言語の場合
● Test Method を Testcase Class(373) の中に書
き、Test Discovery(393) や Test Enumeration(399)
を使って Test Method を Testcase Object(382) とし
てインスタンス化する
● 標準 template に従うことでテストを読みやすくシンプ
ルにし、 SUT の動くドキュメントとすることができる
7. Implementation Notes
● どう仕組みを実装する?
● Static method として実装し呼び出しを列挙する
– テスト結果を集めたりする共通化がやりにくい
● Test Method 一つ一つに対応する Test Suite
Object(387)をつくる
– Test Discovery や Test Enumaration でインスタンス化する
場合に便利
● 静的型付け言語の場合はメソッドに “throws
Exception” などを書かなければならない
– コンパイラに対して「この例外は Test Runner が処理するよ」
という意思表示になる
● 殆どの Test Method は3パターンに分類できる
8. Simple Success Test
● ソフトウェアには正常系 “happy path” があ
る。Simple Success Test はそれを書く
● SUT をインスタンス化して叩き、結果を assert
– 言い換えると、 Four-Phase に則ったテストを書く
● 例外はキャッチせず、 Test Automation Framework ま
で貫通させる
– テストの中で例外を扱うと Obscure Test や誤解のもと
– Tests as Documentation の原則を思い出そう
– Try-catch を書かない利点は他にもあって、 Test
Automation Framework が例外発生行を特定しやすくなる
こと
9. Simple Success Test のダメな例
public void testFlightMileage_asKm() throws Exception {
//set up fixture
Flight newFlight = new Flight(validFlightNumber);
try {
//exercise SUT
newFlight.setMileage(1122);
//verify results
int actualKilometres = newFlight.getMileageAsKm();
int expectedKilometres = 1810;
//verify results
assertEquals( expectedKilometres, actualKilometres);
} catch (InvalidArgumentException e) {
fail(e.getMessage());
} catch (ArrayStoreException e) {
fail(e.getMessage());
}
}
不要な try/catch
10. Simple Success Test の良い例
public void testFlightMileage_asKm() throws Exception {
//set up fixture
Flight newFlight = new Flight(validFlightNumber);
newFlight.setMileage(1122);
//exercise mileage translator
int actualKilometres = newFlight.getMileageAsKm();
//verify results
int expectedKilometres = 1810;
assertEquals( expectedKilometres, actualKilometres);
}
xUnit は unexpected exception を
失敗として扱えるので、
throws Exception しておけばよい
14. Expected Exception Test のダメな例
public void testSetMileage_invalidInput() throws Exception {
//set up fixture
Flight newFlight = new Flight(validFlightNumber);
//exercise SUT
newFlight.setMileage(-1122); //invalid
//how do we verify an exception was thrown?
}
想定された例外なのにテストが失敗してしまう
15. Expected Exception Test の良い例
public void testSetMileage_invalidInput()throws Exception {
//set up fixture
Flight newFlight = new Flight(validFlightNumber);
try {
//exercise SUT
newFlight.setMileage(-1122);
fail("Should have thrown InvalidInputException");
} catch(InvalidArgumentException e) {
//verify results
assertEquals( "Flight mileage must be positive", e.getMessage());
}
}
想定される例外を catch する
例外が出なかった場合は fail させる
16. Expected Exception Test の特殊例?
public void testSetMileage_invalidInput2() throws Exception {
//set up fixture
Flight newFlight = new Flight(validFlightNumber);
try {
//exercise SUT
newFlight.setMileage(-1122);
//cannot fail() here if SUT throws same kind of exception
} catch(AssertionFailedError e) {
//verify results
assertEquals( "Flight mileage must be positive", e.getMessage());
return;
}
fail("Should have thrown InvalidInputException");
}
Fail メソッドが投げる例外と同じ例外を SUT が投げる場合にはこう書くしかない !?
17. Method Attribute を使った EET
[Test]
[ExpectedException(typeof( InvalidArgumentException),
"Flight mileage must be > zero")]
public void testSetMileage_invalidInput_AttributeWithMessage() {
//set up fixture
Flight newFlight = new Flight(validFlightNumber);
//exercise SUT
newFlight.setMileage(-1122);
}
18. Block を使った EET
Smalltalk:
testSetMileageWithInvalidInput
self
should: [Flight new mileage: -1122]
raise: RuntimeError new 'Should have raised error'
Ruby:
def testSetMileage_invalidInput
flight = Flight.new()
assert_raises( RuntimeError, "Should have raised error") do
flight.setMileage(-1122)
end
end
19. Rspec でやってみる
describe Flight do
before do
@flight = Flight.new
end
it "Should have raised error" do
lambda {
flight.setMileage(-1122)
}.should_raise(RuntimeError)
end
end
20. Constructor Test
● Fixture Setup phase で作成されたオブジェクトが
正しく作成されているかを各 Test Method で調べ
ていると Test Code Duplication (213) がひどく
なる
● オブジェクト作成のテストだけ別の Test Method にす
ることで他のテストをシンプルにすることができる
● Defect Localication にもなる
– 各属性の Test Method を分けるとさらに Defect
Localization
● Constructor Test は Simple Success Test の形をと
る場合もあるし、 Expected Exception Test の形をと
る場合もある
21. Dependency Initialization Test
● Constructor Test の亜種
● 置き換え可能な依存があるオブジェクトがある場
合、本番環境では本物の依存オブジェクトが参照さ
れることをテストする
● ふつうの Constructor Test と分けて管理した方
がよい
22. Constructor Test のダメな例
public void testFlightMileage_asKm2() throws Exception {
//set up fixture
//exercise constructor
Flight newFlight = new Flight(validFlightNumber);
//verify constructed object
assertEquals(validFlightNumber, newFlight.number);
assertEquals("", newFlight.airlineCode);
assertNull(newFlight.airline);
//set up mileage
newFlight.setMileage(1122);
//exercise mileage translator
int actualKilometres = newFlight.getMileageAsKm();
//verify results
int expectedKilometres = 1810;
assertEquals( expectedKilometres, actualKilometres);
//now try it with a canceled flight
newFlight.cancel();
try {
newFlight.getMileageAsKm();
fail("Expected exception");
} catch (InvalidRequestException e) {
assertEquals( "Cannot get cancelled flight mileage", e.getMessage());
}
}
いろいろやりすぎでピントがあっていない
Eager Test
23. Constructor Test の良い例1 正常系
public void testFlightConstructor_OK() throws Exception {
//set up fixture
//exercise SUT
Flight newFlight = new Flight(validFlightNumber);
//verify results
assertEquals(
validFlightNumber, newFlight.number );
assertEquals( "", newFlight.airlineCode );
assertNull( newFlight.airline );
}
24. Constructor Test の良い例2 異常系
public void testFlightConstructor_badInput() {
//set up fixture
BigDecimal invalidFlightNumber = new BigDecimal(-1023);
//exercise SUT
try {
Flight newFlight = new Flight(invalidFlightNumber);
fail("Didn't catch negative flight number!");
} catch (InvalidArgumentException e) {
//verify results
assertEquals( "Flight numbers must be positive", e.getMessage());
}
}
25. Constructor Test があると他のテストの
ピントがはっきりする
public void testFlightMileage_asKm() throws Exception {
//set up fixture
Flight newFlight = new Flight(validFlightNumber);
newFlight.setMileage(1122);
//exercise mileage translator
int actualKilometres = newFlight.getMileageAsKm();
//verify results
int expectedKilometres = 1810;
assertEquals( expectedKilometres, actualKilometres);
}
インスタンス化直後の assertion は不要
27. ● Test Definition
● Test Method
– Four-Phase Test
● Assertion Method
– Assertion Message
● Testcase Class
● Test Execution
● Test Runner
● Testcase Object
● Test Suite Object
● Test Discovery
● Test Enumeration
● Test Selection
28. Four-Phase Test
● 良いテストには4つの Phase がある
● Setup
● Exercise
● Verify
● Teardown
● ワンパターン = テストの読みやすさ = Tests as
Documentation
● Test Method の中身はテスト内容に集中すべし
29. Four-Phase Test
● どう setup/teardown する?
● Testcase Class per Class または Testcase Class
per Feature の場合
– In-line Setup
– Garbase-Collected Teardown または In-line Teardown
● Testcase per Fixture の場合
– Implicit Setup
● 例えば setUp メソッド
– Implicit Teardown
● 例えば tearDown メソッド
33. ● Test Definition
● Test Method
– Four-Phase Test
● Assertion Method
– Assertion Message
● Testcase Class
● Test Execution
● Test Runner
● Testcase Object
● Test Suite Object
● Test Discovery
● Test Enumeration
● Test Selection
35. Why We Do This
● 期待する結果を Conditional Test Logic(200)で
表現すると…
● 饒舌に過ぎ、読むのも理解するのも難しい
● Test Code Duplication(213)が発生しやすい
● Buggy Test(260)も発生しやすい
● Assetion Method はこの問題を…
● 再利用性の高い Test Utility Methods(599)に複雑さ
を移すことにより解決する
● そのメソッドの正しさは Test Utility Tests(599)でテス
トすることも可能
36. まずはひどい例から
if (x.equals(y)) {
throw new AssertionFailedError(
"expected: <" + x.toString() +
"> but found: <" + y.toString() + ">");
} else { // Okay, continue
// ...
}
// 上の例では NPE が発生するのでガード節を入れてみると…
if (x == null) { //cannot do null.equals(null)
if (y == null ) { //they are both null so equal
return;
} else {
throw new AssertionFailedError(
"expected null but found: <" + y.toString() +">");
}
} else if (!x.equals(y)) { //comparable but not equal!
throw new AssertionFailedError(
"expected: <" + x.toString() +
"> but found: <" + y.toString() + ">");
} //equal
37. JUnit はこうリファクタリングした
/**
* Asserts that two objects are equal. If they are not,
* an AssertionFailedError is thrown with the given message.
*/
static public void assertEquals(String message,
Object expected,
Object actual) {
if (expected == null &&actual == null)
return;
if (expected != null && expected.equals(actual))
return;
failNotEquals(message, expected, actual);
}
---------------------------------
assertEquals( x, y ); // 呼び出し側はこれだけ!!
50. ● Test Definition
● Test Method
– Four-Phase Test
● Assertion Method
– Assertion Message
● Testcase Class
● Test Execution
● Test Runner
● Testcase Object
● Test Suite Object
● Test Discovery
● Test Enumeration
● Test Selection
57. Argument-Describing Messageの例
assertTrue( "Expected a > b but a was '" + a.toString() +
"' and b was '" + b.toString() + "'", a.gt(b) );
assertTrue( "Expected b > c but b was '" + b.toString() +
"' and '" + c.toString + "'", b > c ); }
c was