test double patterns>test stub

29
Test Stub xUnit Test Patterns>Test Doube Patterns> goyoki

Upload: h-iseri

Post on 16-Jun-2015

1.438 views

Category:

Documents


0 download

DESCRIPTION

xutp読書会

TRANSCRIPT

Page 1: Test Double Patterns>Test Stub

Test StubxUnit Test Patterns>Test Doube Patterns>

goyoki

Page 2: Test Double Patterns>Test Stub

Test Stub

• テストを実行する際、SUTの間接入力を操作するために、実際のオブジェクトと置換されるもの

Page 3: Test Double Patterns>Test Stub

How It Works

1. SUTの依存インターフェースに対して、テストに特化した実装を行う– 例えばSUT内のUntested Codeを実行するような実装を行う

2. SUTの実行前に、本物のDOCと同じ手順でTest StubをSUTに組み込む

3. テスト実行時は、Test Stubは指定値を間接入力としてSUTに渡す

4. 結果は直接出力を通常検証する

Page 4: Test Double Patterns>Test Stub

When to Use It

• 間接入力の存在によりUntested Codeが発生しているとき

• 使用不可なテスト環境を置換したいとき

Page 5: Test Double Patterns>Test Stub

留意

• Test DoubleをSUTに組み込む手段がないと話にならない

• 間接出力の検証が必要なときはMock ObjectやTest Spyを使う

Page 6: Test Double Patterns>Test Stub

Variation

• 紹介するVariation–Responder–Saboter–Temporary Test Stub–Procedural Test Stub

•他と異なる

–Entity Chain Snipping•コード改善テクニック

Page 7: Test Double Patterns>Test Stub

Variation:Responder

• 正常系の値を渡すTest Stub

• 主に“Happy Path” (正常系の実行パス)をテストしたいときに使用

• テストはSimple Success Testsになる

Page 8: Test Double Patterns>Test Stub

Variation:Saboter

• 異常系の値を渡すTest Stub

• 異常状態下でSUTがどのように問題に対処するかを検査する

• 実現方法

–想定外の値を返す/例外を投げる/ランタイムエラーを起こす

• 形態は複数– Simple Success Test/Expected Exception Test

Page 9: Test Double Patterns>Test Stub

Variation:Temporary Test Stub

• まだ利用不能なDOCの代替となるTest Stub

• 一般的な形:

–空の製品コードのクラスに特定値を返す実装をHard-Codeする

–DOCが確保されるとすぐに置き換えられる

Page 10: Test Double Patterns>Test Stub

Temporary Test StubとTDD

• TDDでOutside-In開発を行うときに使用

–空のクラスにコードを継ぎ足していき、最終的にTest Stubを製品コードに置き換えてしまう

–need-driven development(要求駆動開発/

ニーズ駆動開発)では、製品コードで置換しても、テストの保持のため、Mock ObjectとしてTest Doubleを残すことがある

Page 11: Test Double Patterns>Test Stub

Variation:Procedural Test Stub

• 手続き型プログラミング言語で使われるTest Stub

• 関数ポインタをサポートしていない開発言語では、主要なTest Doubleの実現手段となる

• しばしばTest Logic In Productionの状態となる

Page 12: Test Double Patterns>Test Stub

Variation:Entity Chain Snipping

•複雑な関係を持つオブジェクト群を置き換えてしまうもの

• Responderの一種

•一般的な利用目的:

–Fixuture Setupを早くしたいとき

–テストを理解しやすくしたいとき

Page 13: Test Double Patterns>Test Stub

Test Stubの注意点

• 製品とは異なる構成でSUTをテストしている–最低1回はTest Stubなしのテストを実行すべき

• 多用するとOver specified Softwareに陥る– Fragile Test等の問題につながる

• よくあるミス:–テストすべきSUTの一部をTest Stubに置き換えてしまう

–特にSUTの役割とテストフィクスチャの役割は明確に区別するよう注意しましょう

Page 14: Test Double Patterns>Test Stub

Implementation Notes

• ここでは2種類

–Hard-Coded Test Stub

–Configurable Test Stub

Page 15: Test Double Patterns>Test Stub

Hard-Coded Test Stub

•戻り値・例外等がプログラムロジックとしてHard-Codeされる

• 1つ、あるいはごく少数のテストに限定して使うときに使用

Page 16: Test Double Patterns>Test Stub

Configurable Test Stub

•複数のテストで別々にHard-Codeしたくないときに使う

• Fixture Setupでの作業の1つとして挙動を設定可能

• xUnitファミリーはツールを多く提供している

Page 17: Test Double Patterns>Test Stub

実装例

Page 18: Test Double Patterns>Test Stub

元のコード(だめな例)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);

}

Page 19: Test Double Patterns>Test Stub

元のコード(だめな例)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);

}

テストに失敗するか成功するかはシステム時間依存。稀にしかテストをパスしない

Page 20: Test Double Patterns>Test Stub

修正(だめな例)

• とりあえずテスト対象が正しいならテストをパスするようにしたい!

Page 21: Test Double Patterns>Test Stub

修正例(更にだめな例)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);}

Page 22: Test Double Patterns>Test Stub

修正例(更にだめな例)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);}

・時間によって実行パスが変わる

・テストを書いているのではなく、SUTのコピーを実装している

Page 23: Test Double Patterns>Test Stub

修正

• 時間の間接入力を望ましいものに操作可能にする

–まずシステムクロック依存部分:TimeProviderをTest Doubleに置換できるようリファクタリングする

Page 24: Test Double Patterns>Test Stub

例1 Hand-Coded Test StubによるResponderpublic void testDisplayCurrentTime_AtMidnight() throws Exception {

// Fixture setup:// Test Double configurationTimeProviderTestStub tpStub = new TimeProviderTestStub();tpStub.setHours(0);tpStub.setMinutes(0);// Instantiate SUT:TimeDisplay sut = new TimeDisplay();// Test Double installationsut.setTimeProvider(tpStub);// exercise sutString result = sut.getCurrentTimeAsHtmlFragment();// verify outcomeString expectedTimeString = "<span class=¥"tinyBoldText¥">Midnight</span>";assertEquals("Midnight", expectedTimeString, result);

}

Page 25: Test Double Patterns>Test Stub

例2 Dynamically GeneratedによるResponderpublic void testDisplayCurrentTime_AtMidnight_JM() throws Exception {

// Fixture setup:TimeDisplay sut = new TimeDisplay();// Test Double configurationMock tpStub = mock(TimeProvider.class);Calendar midnight = makeTime(0,0);

tpStub.stubs().method("getTime").withNoArguments().will(returnValue(midnight));// Test Double installationsut.setTimeProvider((TimeProvider) tpStub);// exercise sutString result = sut.getCurrentTimeAsHtmlFragment();// verify outcomeString expectedTimeString = "<span class=¥"tinyBoldText¥">Midnight</span>";assertEquals("Midnight", expectedTimeString, result);

}

Page 26: Test Double Patterns>Test Stub

例3 Anonymous Inner ClassによるSaboteurpublic void testDisplayCurrentTime_exception() throws Exception {

// fixture setup// Define and instantiate Test StubTimeProvider testStub = new TimeProvider()

{ // anonymous inner Test Stubpublic Calendar getTime() throws TimeProviderEx {

throw new TimeProviderEx("Sample");}

};// Instantiate SUT:TimeDisplay sut = new TimeDisplay();sut.setTimeProvider(testStub);// exercise sutString result = sut.getCurrentTimeAsHtmlFragment();// verify direct outputString expectedTimeString = "<span class=¥"error¥">Invalid Time</span>";assertEquals("Exception", expectedTimeString, result);

}

Page 27: Test Double Patterns>Test Stub

番外: Entity Chain Snipping

• こんがらがったオブジェクトの連なりを置換する

Page 28: Test Double Patterns>Test Stub

適用前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);// Exerciseinv.addItemQuantity(product, QUANTITY);// VerifyList 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);

}

Page 29: Test Double Patterns>Test Stub

適用後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());// Exerciseinv.addItemQuantity(product, QUANTITY);// VerifyList 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);

}