Test Double Patterns>Test Stub

1,697 views

Published on

xutp読書会

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,697
On SlideShare
0
From Embeds
0
Number of Embeds
1
Actions
Shares
0
Downloads
6
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Test Double Patterns>Test Stub

  1. 1. Test StubxUnit Test Patterns>Test Doube Patterns> goyoki
  2. 2. Test Stub• テストを実行する際、 SUTの間接入力を操作するために、 実際のオブジェクトと置換されるもの
  3. 3. How It Works1. SUTの依存インターフェースに対して、 テストに特化した実装を行う – 例えばSUT内のUntested Codeを実行する ような実装を行う2. SUTの実行前に、本物のDOCと同じ手 順でTest StubをSUTに組み込む3. テスト実行時は、Test Stubは指定値を 間接入力としてSUTに渡す4. 結果は直接出力を通常検証する
  4. 4. When to Use It• 間接入力の存在により Untested Codeが発生しているとき• 使用不可なテスト環境を置換したい とき
  5. 5. 留意• Test DoubleをSUTに組み込む手段が ないと話にならない• 間接出力の検証が必要なときは Mock ObjectやTest Spyを使う
  6. 6. Variation• 紹介するVariation –Responder –Saboter –Temporary Test Stub –Procedural Test Stub • 他と異なる –Entity Chain Snipping • コード改善テクニック
  7. 7. Variation:Responder• 正常系の値を渡すTest Stub• 主に“Happy Path” (正常系の実行パ ス)をテストしたいときに使用• テストはSimple Success Tests になる
  8. 8. Variation:Saboter• 異常系の値を渡すTest Stub• 異常状態下でSUTがどのように問題に対 処するかを検査する• 実現方法 – 想定外の値を返す/例外を投げる/ランタイ ムエラーを起こす• 形態は複数 – Simple Success Test/Expected Exception Test
  9. 9. Variation:Temporary Test Stub• まだ利用不能なDOCの代替となる Test Stub• 一般的な形: – 空の製品コードのクラスに特定値を返す実 装をHard-Codeする – DOCが確保されるとすぐに置き換えられる
  10. 10. Temporary Test StubとTDD• TDDでOutside-In開発を行うときに使用 – 空のクラスにコードを継ぎ足していき、最終 的にTest Stubを製品コードに置き換えてし まう – need-driven development(要求駆動開発/ ニーズ駆動開発)では、製品コードで置換 しても、テストの保持のため、Mock Object としてTest Doubleを残すことがある
  11. 11. Variation:Procedural Test Stub• 手続き型プログラミング言語で使わ れるTest Stub• 関数ポインタをサポートしていない 開発言語では、主要なTest Double の実現手段となる• しばしばTest Logic In Productionの状 態となる
  12. 12. Variation:Entity Chain Snipping• 複雑な関係を持つオブジェクト群 を置き換えてしまうもの• Responderの一種• 一般的な利用目的:–Fixuture Setupを早くしたいとき–テストを理解しやすくしたいとき
  13. 13. Test Stubの注意点• 製品とは異なる構成でSUTをテストしている – 最低1回はTest Stubなしのテストを実行すべき• 多用するとOver specified Softwareに陥る – Fragile Test等の問題につながる• よくあるミス: – テストすべきSUTの一部をTest Stubに置き換えて しまう – 特にSUTの役割とテストフィクスチャの役割は明 確に区別するよう注意しましょう
  14. 14. Implementation Notes• ここでは2種類 –Hard-Coded Test Stub –Configurable Test Stub
  15. 15. Hard-Coded Test Stub• 戻り値・例外等がプログラムロ ジックとしてHard-Codeされる• 1つ、あるいはごく少数のテスト に限定して使うときに使用
  16. 16. Configurable Test Stub• 複数のテストで別々にHard-Code したくないときに使う• Fixture Setupでの作業の1つとし て挙動を設定可能• xUnitファミリーはツールを多く提 供している
  17. 17. 実装例
  18. 18. 元のコード(だめな例)public void testDisplayCurrentTime_AtMidnight() { // fixture setup TimeDisplay sut = new TimeDisplay(); // exercise sut String result = sut.getCurrentTimeAsHtmlFragment(); // verify direct output String expectedTimeString = "<span class=¥"tinyBoldText¥">Midnight</span>"; assertEquals( expectedTimeString, result);}
  19. 19. 元のコード(だめな例)public void testDisplayCurrentTime_AtMidnight() { // fixture setup テストに失敗するか成功するか TimeDisplay sut = new TimeDisplay(); はシステム時間依存。稀にしか // exercise sut テストをパスしない String result = sut.getCurrentTimeAsHtmlFragment(); // verify direct output String expectedTimeString = "<span class=¥"tinyBoldText¥">Midnight</span>"; assertEquals( expectedTimeString, result);}
  20. 20. 修正(だめな例)• とりあえずテスト対象が正しいならテ ストをパスするようにしたい!
  21. 21. 修正例(更にだめな例)public void testDisplayCurrentTime_whenever() { // fixture setup TimeDisplay sut = new TimeDisplay(); // exercise sut String result = sut.getCurrentTimeAsHtmlFragment(); // verify outcome Calendar time = new DefaultTimeProvider().getTime(); StringBuffer expectedTime = new StringBuffer(); expectedTime.append("<span class=¥"tinyBoldText¥">"); if ((time.get(Calendar.HOUR_OF_DAY) == 0) && (time.get(Calendar.MINUTE) <= 1)) { expectedTime.append( "Midnight"); } else if ((time.get(Calendar.HOUR_OF_DAY) == 12) && (time.get(Calendar.MINUTE) == 0)) { // noon expectedTime.append("Noon"); } else { SimpleDateFormat fr = new SimpleDateFormat("h:mm a"); expectedTime.append(fr.format(time.getTime())); } expectedTime.append("</span>"); assertEquals( expectedTime, result);}
  22. 22. 修正例(更にだめな例)public void testDisplayCurrentTime_whenever() { // fixture setup TimeDisplay sut = new TimeDisplay(); ・時間によって実行パスが変わる // exercise sut String result = sut.getCurrentTimeAsHtmlFragment(); ・テストを書いているのではなく、 // verify outcome Calendar time = new DefaultTimeProvider().getTime(); SUTのコピーを実装している StringBuffer expectedTime = new StringBuffer(); expectedTime.append("<span class=¥"tinyBoldText¥">"); if ((time.get(Calendar.HOUR_OF_DAY) == 0) && (time.get(Calendar.MINUTE) <= 1)) { expectedTime.append( "Midnight"); } else if ((time.get(Calendar.HOUR_OF_DAY) == 12) && (time.get(Calendar.MINUTE) == 0)) { // noon expectedTime.append("Noon"); } else { SimpleDateFormat fr = new SimpleDateFormat("h:mm a"); expectedTime.append(fr.format(time.getTime())); } expectedTime.append("</span>"); assertEquals( expectedTime, result);}
  23. 23. 修正• 時間の間接入力を望ましいものに 操作可能にする –まずシステムクロック依存部分: TimeProviderをTest Doubleに置換でき るようリファクタリングする
  24. 24. 例1 Hand-Coded Test StubによるResponderpublic void testDisplayCurrentTime_AtMidnight() throws Exception { // Fixture setup: // Test Double configuration TimeProviderTestStub tpStub = new TimeProviderTestStub(); tpStub.setHours(0); tpStub.setMinutes(0); // Instantiate SUT: TimeDisplay sut = new TimeDisplay(); // Test Double installation sut.setTimeProvider(tpStub); // exercise sut String result = sut.getCurrentTimeAsHtmlFragment(); // verify outcome String expectedTimeString = "<span class=¥"tinyBoldText¥">Midnight</span>"; assertEquals("Midnight", expectedTimeString, result); }
  25. 25. 例2 Dynamically GeneratedによるResponderpublic void testDisplayCurrentTime_AtMidnight_JM() throws Exception { // Fixture setup: TimeDisplay sut = new TimeDisplay(); // Test Double configuration Mock tpStub = mock(TimeProvider.class); Calendar midnight = makeTime(0,0); tpStub.stubs().method("getTime").withNoArguments().will(returnValu e(midnight)); // Test Double installation sut.setTimeProvider((TimeProvider) tpStub); // exercise sut String result = sut.getCurrentTimeAsHtmlFragment(); // verify outcome String expectedTimeString = "<span class=¥"tinyBoldText¥">Midnight</span>"; assertEquals("Midnight", expectedTimeString, result); }
  26. 26. 例3 Anonymous Inner ClassによるSaboteurpublic void testDisplayCurrentTime_exception() throws Exception { // fixture setup // Define and instantiate Test Stub TimeProvider testStub = new TimeProvider() { // anonymous inner Test Stub public Calendar getTime() throws TimeProviderEx { throw new TimeProviderEx("Sample"); } }; // Instantiate SUT: TimeDisplay sut = new TimeDisplay(); sut.setTimeProvider(testStub); // exercise sut String result = sut.getCurrentTimeAsHtmlFragment(); // verify direct output String expectedTimeString = "<span class=¥"error¥">Invalid Time</span>"; assertEquals("Exception", expectedTimeString, result);}
  27. 27. 番外: Entity Chain Snipping• こんがらがったオブジェクトの連なり を置換する
  28. 28. 適用前public void testInvoice_addLineItem_noECS() { final int QUANTITY = 1; Product product = new Product(getUniqueNumberAsString(), getUniqueNumber()); State state = new State("West Dakota", "WD"); City city = new City("Centreville", state); Address address = new Address("123 Blake St.", city, "12345"); Customer customer= new Customer(getUniqueNumberAsString(), getUniqueNumberAsString(), address); Invoice inv = new Invoice(customer); // Exercise inv.addItemQuantity(product, QUANTITY); // Verify List lineItems = inv.getLineItems(); assertEquals("number of items", lineItems.size(), 1); LineItem actual = (LineItem)lineItems.get(0); LineItem expItem = new LineItem(inv, product, QUANTITY); assertLineItemsEqual("",expItem, actual); }
  29. 29. 適用後public void testInvoice_addLineItem_ECS() { final int QUANTITY = 1; Product product = new Product(getUniqueNumberAsString(), getUniqueNumber()); Mock customerStub = mock(ICustomer.class); customerStub.stubs().method("getZone").will(returnValue(ZONE_3)); Invoice inv = new Invoice((ICustomer)customerStub.proxy()); // Exercise inv.addItemQuantity(product, QUANTITY); // Verify List lineItems = inv.getLineItems(); assertEquals("number of items", lineItems.size(), 1); LineItem actual = (LineItem)lineItems.get(0); LineItem expItem = new LineItem(inv, product, QUANTITY); assertLineItemsEqual("", expItem, actual);}

×