Chapter 19.
xUnit Basic
 Patterns
●   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
Test Method
How It Works(1)
●   テストコードってどこに書くの?
    ●   ひとつひとつのテスト毎にメソッド (Test Method) にし
        てクラスに配置しましょう
●   How It Works
    ●   各テストをメソッド/手続き/関数のかたちで Four-
        Phase(358) の実装を行い、Fully Automated Test と
        する。
    ●   大事なのは、 assertion を書いて自己テストコード
        (Self-Checking Test:26) とすること
How It Works(2)
●   Test Method には標準 Template がある
    ●   Simple Success Test
        –   正常系のテスト。 Fixture setup から result verification ま
            で一本道
    ●   Expected Exception Test
        –   例外系のテスト
    ●   Constructor Test
        –   オブジェクトを作成し属性をテストするだけのテスト
Why We Do This
●   手続き型言語の場合
    ●   Test Method をファイルやモジュールに書く
●   オブジェクト指向言語の場合
    ●   Test Method を Testcase Class(373) の中に書
        き、Test Discovery(393) や Test Enumeration(399)
        を使って Test Method を Testcase Object(382) とし
        てインスタンス化する
    ●   標準 template に従うことでテストを読みやすくシンプ
        ルにし、 SUT の動くドキュメントとすることができる
Implementation Notes
●   どう仕組みを実装する?
    ●   Static method として実装し呼び出しを列挙する
        –   テスト結果を集めたりする共通化がやりにくい
    ●   Test Method 一つ一つに対応する Test Suite
        Object(387)をつくる
        –   Test Discovery や Test Enumaration でインスタンス化する
            場合に便利
    ●   静的型付け言語の場合はメソッドに “throws
        Exception” などを書かなければならない
        –   コンパイラに対して「この例外は Test Runner が処理するよ」
            という意思表示になる
    ●   殆どの Test Method は3パターンに分類できる
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 が例外発生行を特定しやすくなる
            こと
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
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 しておけばよい
Expected Exception Test (1)
●   多くの不具合は正常系以外のパスに潜む。特に例
    外系のシナリオ。それは、
    ●   Untested Requirements (268) や、
    ●   Untested Code (268) であったりするため
●   Expected Exception Test はわざと SUT が例外
    を出すようなテストを書き、きちんと例外が出ること
    を調べる
    ●   例外の中身も調べたいときは Equality Assertion で調
        べる
    ●   例外が出なかったときは fail メソッドなどでテストを失
        敗させる
Expected Exception Test (2)
●   想定される (expected) 例外には、テストを書いた
    ほうがよい
●   再現が難しいが、出るかもしれない (might raise)
    例外には、テストを書かなくてよい
    ●   (★ ネットワーク障害とか、 Disk full とか)
    ●   そういう例外は Simple Success Test の失敗として現
        れるため
    ●   もしそういう例外もテストしたいなら、 Test Stub から例
        外を発生させてテストする
Expected Exception Test (3)
●   例外をテストするときの仕組み
    ●   JUnit 3.x
        –   ExpectedException クラスを継承させる (?)
             ●   小さいテストクラスが沢山できるし、あまり旨味は無い
    ●   JUnit 4.x, NUnit
        –   Test Method の annotation/attribute に書く
    ●   Block のある言語 (Smalltalk, Ruby, …)
        –   Block で例外が発生するか調べるテストを書ける
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?
}




                                      想定された例外なのにテストが失敗してしまう
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 させる
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 が投げる場合にはこう書くしかない !?
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);
}
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
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
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 の形をと
        る場合もある
Dependency Initialization Test
●   Constructor Test の亜種
●   置き換え可能な依存があるオブジェクトがある場
    合、本番環境では本物の依存オブジェクトが参照さ
    れることをテストする
●   ふつうの Constructor Test と分けて管理した方
    がよい
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
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 );
}
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());
  }
}
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 は不要
Four-Phase Test
●   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
Four-Phase Test
●   良いテストには4つの Phase がある
    ●   Setup
    ●   Exercise
    ●   Verify
    ●   Teardown
●   ワンパターン = テストの読みやすさ = Tests as
    Documentation
    ●   Test Method の中身はテスト内容に集中すべし
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 メソッド
例: Four-Phase Test (In-line)

public void testGetFlightsByOriginAirport_NoFlights_inline() throws Exception {
  //Fixture setup
  NonTxFlightMngtFacade facade =new NonTxFlightMngtFacade();
  BigDecimal airportId = facade.createTestAirport("1OF");
  try {
    //Exercise system
    List flightsAtDestination1 =
      facade.getFlightsByOriginAirport(airportId);
    //Verify outcome
    assertEquals( 0, flightsAtDestination1.size() );
  } finally {
    //Fixture teardown
    facade.removeAirport(airportId );
  }
}
例: 4PT (Implicit Setup/Teardown)
NonTxFlightMngtFacade facade = new NonTxFlightMngtFacade();
private BigDecimal airportId;

protected void setUp() throws Exception {
  //Fixture setup
  super.setUp();
  airportId = facade.createTestAirport("1OF");
}

public void testGetFlightsByOriginAirport_NoFlights_implicit() throws Exception {
  //Exercise SUT
  List flightsAtDestination1 = facade.getFlightsByOriginAirport(airportId);
  //Verify outcome
  assertEquals( 0, flightsAtDestination1.size() );
}

protected void tearDown() throws Exception {
  //Fixture teardown
  facade.removeAirport(airportId);
  super.tearDown();
}
Assertion Method
●   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
Assertion Method
●   どうやってテストに自己チェックさせるか
    ●   ユーティリティメソッドを呼ぶことで望んだ結果になった
        かどうかの評価をすればいい
●   Fully Automated Tests(26)の肝は、テストをSelf-
    Checking Tests(26)にすること
    ●   そのためには期待する結果を表現し、自動でチェックす
        ることが必要
●   Assertion Method は期待する結果を表現し、
    ●   コンピュータにとっては実行可能に
    ●   人間には Tests as Documentation(23) になる。
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)でテス
        トすることも可能
まずはひどい例から
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
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 ); // 呼び出し側はこれだけ!!
Implementation Notes
●   全ての xUnit ファミリーは Assetion Method を備
    えているが、考えるべきことはある
    ●   Assertion Method をどうやって呼ぶか
    ●   最適な Assertion Method をどう選ぶか
    ●   Assertion Message(370) に何を書くか
Calling Built-in Assertion Methods
●   Test Method(348) からテストフレームワーク組み
    込みで提供されている Assertion Method を呼ぶ
    には…
    ●   フレームワークが提供する Testcase Superclass(638)
        を継承する (JUnit タイプ)
    ●   グローバルクラス/モジュールを完全修飾名で呼び出す
        (NUnit タイプ)
    ●   Mixin (Test::Unit タイプ)
    ●   マクロ (CppUnit タイプ)
Assertion Messages
●   テスト失敗時の出力に含めるメッセージ
    ●   どのテストが失敗したかをわかりやすくする
        –   Assertion Roulette 参照
    ●   省略可能な引数として Assertion Method に渡すかた
        ちが多い
●   テスト失敗時に「なぜ失敗したか」の情報が多けれ
    ばデバッグは容易になる
    ●   正しい Assertion Method を選ぶことはエラー時のメッ
        セージ出力を適切にする意味でも重要
●   問題なのは、 Assertion Message の引数の順番
    が xUnit 毎にブレていること
Choosing the Right Assertion
●   Assertion Method には二つのゴールがある
    ●   期待しない結果のときにはテストを失敗させること
    ●   SUT がどう振る舞うかのドキュメントになること
●   これらのゴールを満たすため、最適な Assertion
    Method を選ぶことが重要になる
●   Assertion Method には以下のカテゴリがある
    ●   Single-Outcome Assertions
    ●   Stated Outcome Assertions
    ●   Expected Exception Assertions
    ●   Equality Assertions
    ●   Fuzzy Equality Assertions
Equality Assertion
●   結果が期待値と等価かどうかを調べる
    ●   もっとも使われる Assertion Method
●   引数の順番は規約としては expected, actual の
    順番
    ●   順番は失敗時のメッセージに関係するので重要
    ●   ★この順番でない xUnit もある。(NUnit とか)
    ●   ★自分の使う Equality Assertion の順を覚えよう
●   内部では等価性を調べるメソッドが呼ばれる
    ●   Java では equals とか
    ●   SUT ごと調べたい場合は Test-Specific Subclass
Equality Assertion いろいろ
assertEquals( expected, actual ); // since JUnit3.x

assertThat( actual, is(expected) ); // since JUnit 4.4

Assert.AreEqual( actual, expected ); // NUnit

is( actual, expected );   // Test::Simple (Perl)

equals( actual,    
                expected ); // QUnit

actual.should == expected // Rspec
Fuzzy Equality Assertion
●   結果と期待値との完全な一致が難しい場合
    ●   浮動小数点をあつかうとき
    ●   期待値と完全一致させるには結果に本質的でない不要
        なゴミが多いとき (XML の空白ノードとか)




assertEquals( 3.1415, diameter/2/radius, 0.001);

assertEquals( expectedXml, actualXml, elementsToCompare );
Stated Outcome Assertion
●   期待値を渡す必要がないとき
●   Conditional Test Logic を避けるためのガード節
    としても使える



assertNotNull(a );

assertTrue(b > c );

assertNonZero(b );
Expected Exception Assertion
●   ブロックやクロージャを備えている言語は発生する
    であろう例外をパラメータとして渡す Assertion
    Method が使える

self
  should: [Flight new mileage: -1122]
  raise: RuntimeError new 'Should have raised error'


assert_raises( RuntimeError,
            "Should have raised error")
            { flight.setMileage(-1122) }


assert_raises( RuntimeError, "Should have raised error")do
  flight.setMileage(-1122)
end
Single-Outcome Assertion
●   常に同じ振る舞いをする Assertion Method
    ●   例えば fail メソッド
●   使われる状況
    ●   まだ完成していないテスト Unfinished Test Assertion を示す
    ●   Expected Exception Test の中の try/catch ブロックで使う



fail( "Expected an exception" );


unfinishedTest();
Single-Outcome Assertion の例

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 させる
Assertion Message
●   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
Assertion Message
●   どの Assertion Method が落ちたか知りたい
    ●   Assertion Method 毎にメッセージ引数を渡す
●   テスト失敗時の出力に含めるメッセージ
    ●   どのテストが失敗したかをわかりやすくする
        –   Assertion Roulette 参照
    ●   省略可能な引数として Assertion Method に渡すかた
        ちが多い
When to Use It
●   二つの学派(School)がある
    ●   テスト駆動派 (Test drivers) と、その他派
●   テスト駆動派
    ●   “single assertion per Test Method”
    ●   Test Method に Assertion Method がひとつしかな
        いので、どの Assertion Method が落ちたかは自明。
        故に Assertion Message は不要。
●   その他派
    ●   ひとつの Test Method に複数 Assertion Method が
        あるので、 Assertion Message を使いたくなる
Implementation Notes
●   Assertion Message に何を書くべきか
    ●   Assertion-Identifying Message
    ●   Expectation-Describing Message
    ●   Argument-Describing Message
Assertion-Identifying Message
●   同種の Assertion Method が複数ある場合にどれ
    が失敗したか分かりにくい
    ●   Assertion Method 毎に違う文字列を渡して識別する
●   識別に使う文字列の例
    ●   Assertion Method に使う変数名
        –   名前に悩む必要がないので便利かも
    ●   単なる連番
        –   テストコードを読まないと結局どこが失敗したかわからなかっ
            たりする
Expectation-Describing Message
●   テストが失敗した時に「実際何が起こったか」はわ
    かる。だが「何が起こるべきだったか」はわからな
    い。
    ●   テストコード内にコメントを書く手もある
●   もっと良いのは Assertion Message に期待値の
    説明を書くこと
    ●   Equality Assertion の場合は自動でやってくれるので
        必要無し
    ●   Stated Outcome Assertion の場合は入れなければ
        わからない
Argument-Describing Message
●   いくつかの Assertion Method は失敗時の出力が
    不親切
    ●   特に Stated Outcome Assertion
        –   assertTrue(式) とか
        –   失敗したのはわかるが、どんな式が失敗したのかわからない
        –   式を Assertion Message に含めてしまう手がある
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
ご清聴
 ありがとう
ございました

xUnit Test Patterns - Chapter19

  • 1.
  • 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
  • 3.
  • 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 DoThis ● 手続き型言語の場合 ● 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 しておけばよい
  • 11.
    Expected Exception Test(1) ● 多くの不具合は正常系以外のパスに潜む。特に例 外系のシナリオ。それは、 ● Untested Requirements (268) や、 ● Untested Code (268) であったりするため ● Expected Exception Test はわざと SUT が例外 を出すようなテストを書き、きちんと例外が出ること を調べる ● 例外の中身も調べたいときは Equality Assertion で調 べる ● 例外が出なかったときは fail メソッドなどでテストを失 敗させる
  • 12.
    Expected Exception Test(2) ● 想定される (expected) 例外には、テストを書いた ほうがよい ● 再現が難しいが、出るかもしれない (might raise) 例外には、テストを書かなくてよい ● (★ ネットワーク障害とか、 Disk full とか) ● そういう例外は Simple Success Test の失敗として現 れるため ● もしそういう例外もテストしたいなら、 Test Stub から例 外を発生させてテストする
  • 13.
    Expected Exception Test(3) ● 例外をテストするときの仕組み ● JUnit 3.x – ExpectedException クラスを継承させる (?) ● 小さいテストクラスが沢山できるし、あまり旨味は無い ● JUnit 4.x, NUnit – Test Method の annotation/attribute に書く ● Block のある言語 (Smalltalk, Ruby, …) – Block で例外が発生するか調べるテストを書ける
  • 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 Flightdo 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 のダメな例 publicvoid 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 は不要
  • 26.
  • 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 メソッド
  • 30.
    例: Four-Phase Test(In-line) public void testGetFlightsByOriginAirport_NoFlights_inline() throws Exception { //Fixture setup NonTxFlightMngtFacade facade =new NonTxFlightMngtFacade(); BigDecimal airportId = facade.createTestAirport("1OF"); try { //Exercise system List flightsAtDestination1 = facade.getFlightsByOriginAirport(airportId); //Verify outcome assertEquals( 0, flightsAtDestination1.size() ); } finally { //Fixture teardown facade.removeAirport(airportId ); } }
  • 31.
    例: 4PT (ImplicitSetup/Teardown) NonTxFlightMngtFacade facade = new NonTxFlightMngtFacade(); private BigDecimal airportId; protected void setUp() throws Exception { //Fixture setup super.setUp(); airportId = facade.createTestAirport("1OF"); } public void testGetFlightsByOriginAirport_NoFlights_implicit() throws Exception { //Exercise SUT List flightsAtDestination1 = facade.getFlightsByOriginAirport(airportId); //Verify outcome assertEquals( 0, flightsAtDestination1.size() ); } protected void tearDown() throws Exception { //Fixture teardown facade.removeAirport(airportId); super.tearDown(); }
  • 32.
  • 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
  • 34.
    Assertion Method ● どうやってテストに自己チェックさせるか ● ユーティリティメソッドを呼ぶことで望んだ結果になった かどうかの評価をすればいい ● Fully Automated Tests(26)の肝は、テストをSelf- Checking Tests(26)にすること ● そのためには期待する結果を表現し、自動でチェックす ることが必要 ● Assertion Method は期待する結果を表現し、 ● コンピュータにとっては実行可能に ● 人間には Tests as Documentation(23) になる。
  • 35.
    Why We DoThis ● 期待する結果を 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 ); // 呼び出し側はこれだけ!!
  • 38.
    Implementation Notes ● 全ての xUnit ファミリーは Assetion Method を備 えているが、考えるべきことはある ● Assertion Method をどうやって呼ぶか ● 最適な Assertion Method をどう選ぶか ● Assertion Message(370) に何を書くか
  • 39.
    Calling Built-in AssertionMethods ● Test Method(348) からテストフレームワーク組み 込みで提供されている Assertion Method を呼ぶ には… ● フレームワークが提供する Testcase Superclass(638) を継承する (JUnit タイプ) ● グローバルクラス/モジュールを完全修飾名で呼び出す (NUnit タイプ) ● Mixin (Test::Unit タイプ) ● マクロ (CppUnit タイプ)
  • 40.
    Assertion Messages ● テスト失敗時の出力に含めるメッセージ ● どのテストが失敗したかをわかりやすくする – Assertion Roulette 参照 ● 省略可能な引数として Assertion Method に渡すかた ちが多い ● テスト失敗時に「なぜ失敗したか」の情報が多けれ ばデバッグは容易になる ● 正しい Assertion Method を選ぶことはエラー時のメッ セージ出力を適切にする意味でも重要 ● 問題なのは、 Assertion Message の引数の順番 が xUnit 毎にブレていること
  • 41.
    Choosing the RightAssertion ● Assertion Method には二つのゴールがある ● 期待しない結果のときにはテストを失敗させること ● SUT がどう振る舞うかのドキュメントになること ● これらのゴールを満たすため、最適な Assertion Method を選ぶことが重要になる ● Assertion Method には以下のカテゴリがある ● Single-Outcome Assertions ● Stated Outcome Assertions ● Expected Exception Assertions ● Equality Assertions ● Fuzzy Equality Assertions
  • 42.
    Equality Assertion ● 結果が期待値と等価かどうかを調べる ● もっとも使われる Assertion Method ● 引数の順番は規約としては expected, actual の 順番 ● 順番は失敗時のメッセージに関係するので重要 ● ★この順番でない xUnit もある。(NUnit とか) ● ★自分の使う Equality Assertion の順を覚えよう ● 内部では等価性を調べるメソッドが呼ばれる ● Java では equals とか ● SUT ごと調べたい場合は Test-Specific Subclass
  • 43.
    Equality Assertion いろいろ assertEquals(expected, actual ); // since JUnit3.x assertThat( actual, is(expected) ); // since JUnit 4.4 Assert.AreEqual( actual, expected ); // NUnit is( actual, expected ); // Test::Simple (Perl) equals( actual,     expected ); // QUnit actual.should == expected // Rspec
  • 44.
    Fuzzy Equality Assertion ● 結果と期待値との完全な一致が難しい場合 ● 浮動小数点をあつかうとき ● 期待値と完全一致させるには結果に本質的でない不要 なゴミが多いとき (XML の空白ノードとか) assertEquals( 3.1415, diameter/2/radius, 0.001); assertEquals( expectedXml, actualXml, elementsToCompare );
  • 45.
    Stated Outcome Assertion ● 期待値を渡す必要がないとき ● Conditional Test Logic を避けるためのガード節 としても使える assertNotNull(a ); assertTrue(b > c ); assertNonZero(b );
  • 46.
    Expected Exception Assertion ● ブロックやクロージャを備えている言語は発生する であろう例外をパラメータとして渡す Assertion Method が使える self should: [Flight new mileage: -1122] raise: RuntimeError new 'Should have raised error' assert_raises( RuntimeError, "Should have raised error") { flight.setMileage(-1122) } assert_raises( RuntimeError, "Should have raised error")do flight.setMileage(-1122) end
  • 47.
    Single-Outcome Assertion ● 常に同じ振る舞いをする Assertion Method ● 例えば fail メソッド ● 使われる状況 ● まだ完成していないテスト Unfinished Test Assertion を示す ● Expected Exception Test の中の try/catch ブロックで使う fail( "Expected an exception" ); unfinishedTest();
  • 48.
    Single-Outcome Assertion の例 publicvoid 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 させる
  • 49.
  • 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
  • 51.
    Assertion Message ● どの Assertion Method が落ちたか知りたい ● Assertion Method 毎にメッセージ引数を渡す ● テスト失敗時の出力に含めるメッセージ ● どのテストが失敗したかをわかりやすくする – Assertion Roulette 参照 ● 省略可能な引数として Assertion Method に渡すかた ちが多い
  • 52.
    When to UseIt ● 二つの学派(School)がある ● テスト駆動派 (Test drivers) と、その他派 ● テスト駆動派 ● “single assertion per Test Method” ● Test Method に Assertion Method がひとつしかな いので、どの Assertion Method が落ちたかは自明。 故に Assertion Message は不要。 ● その他派 ● ひとつの Test Method に複数 Assertion Method が あるので、 Assertion Message を使いたくなる
  • 53.
    Implementation Notes ● Assertion Message に何を書くべきか ● Assertion-Identifying Message ● Expectation-Describing Message ● Argument-Describing Message
  • 54.
    Assertion-Identifying Message ● 同種の Assertion Method が複数ある場合にどれ が失敗したか分かりにくい ● Assertion Method 毎に違う文字列を渡して識別する ● 識別に使う文字列の例 ● Assertion Method に使う変数名 – 名前に悩む必要がないので便利かも ● 単なる連番 – テストコードを読まないと結局どこが失敗したかわからなかっ たりする
  • 55.
    Expectation-Describing Message ● テストが失敗した時に「実際何が起こったか」はわ かる。だが「何が起こるべきだったか」はわからな い。 ● テストコード内にコメントを書く手もある ● もっと良いのは Assertion Message に期待値の 説明を書くこと ● Equality Assertion の場合は自動でやってくれるので 必要無し ● Stated Outcome Assertion の場合は入れなければ わからない
  • 56.
    Argument-Describing Message ● いくつかの Assertion Method は失敗時の出力が 不親切 ● 特に Stated Outcome Assertion – assertTrue(式) とか – 失敗したのはわかるが、どんな式が失敗したのかわからない – 式を Assertion Message に含めてしまう手がある
  • 57.
    Argument-Describing Messageの例 assertTrue( "Expecteda > 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
  • 58.