xunit test patterns - chapter16
Post on 28-Nov-2014
3.337 Views
Preview:
DESCRIPTION
TRANSCRIPT
Chapter 16.Behavior Smells
Smells in This Chapter
● Assertion Roulette● Erratic Test● Fragile Test● Frequent Debugging● Manual Intervention● Slow Tests
Assertion Roulette
Assertion Roulette (1)
● 初出● XP2001 “Refactoring Test Code”
● Symptoms● テスト失敗時に、どの assertion が失敗したのかよ
く分からない● Impact
● CI 中にテストが Assertion Roulette が発現した場合には解析が難しい。手元のマシンで再現できない時はさらに時間を食うことになる
Assertion Roulette (2)
● Causes● Eager Test
– ひとつのテストで機能を検証しすぎている● Missing Assertion Message
– アサーションメッセージが無い
Smells in This Chapter
● Assertion Roulette● Eager Test● Missing Assertion Message
● Erratic Test● Fragile Test● Frequent Debugging● Manual Intervention● Slow Tests
Eager Test (1)
● Symptoms● ひとつのテストが SUT の複数のメソッドをテスト
している● ひとつのテストが SUT の同じメソッドを fixture
setup logic や assertion などバラバラな場所から複数回呼んでいる
● テスト技術者がテストフレームワークを改造して assertion が失敗してもその行以降を実行させようとする
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 (2)
● Root Cause● テストの中で多くの検証を行って (意図的であるに
しろ無いにしろ) テストの数を最小にしようとすること– 手動テストにおいては一度にたくさんの要素をテストす
るのは良いこととされているが、完全自動化されたテストにおいては悪手
● 多くのステップを必要とする Customer Test を xUnit を使って行おうとすること
Eager Test (3)
● Possible Solution● Single-Condition Test (p45) に分解する
– Extract Method [Fowler]– ひとつひとつのテスト要素毎にコピーして不要な部分を
削除● Creation Method (p415)
– Fixture を setup したり SUT を特定の状態にする部分をメソッドに切り出す
– Lean on Compiler [WEwLC] 使おう
Eager Test (4)
● Customer Test を xUnit で行っている場合– テストシナリオが進むに応じて複雑な状態がテスト内で
作り出されていく– テスト後半部分を切り出すためには複雑な状態の setup
が必要なので、なんらかの手段を講じる必要がある● Back Door Setup (p327) を使ってテスト後半部分
の setup を行えればテストを分割できる– Defect Localization につながる– テストを短くしていけば Communicate Intent (p41) へ
Smells in This Chapter
● Assertion Roulette● Eager Test● Missing Assertion Message
● Erratic Test● Fragile Test● Frequent Debugging● Manual Intervention● Slow Tests
Missing Assertion Message (1)
● Symptoms● テスト失敗時に、どの assertion が失敗したのか
よく分からない
Missing Assertion Message (2)
● Root Cause● Assertion Method (p362) が
– 同じ Assertion Method が複数回使われている– Assertion Message (p370) 無しで書かれている
● コマンドラインからテストを実行したり、開発環境とテストコードが統合されていない時にどこで失敗したか分からず困る
public void testInvoice_addLineItem7() { LineItem expItem = new LineItem(inv, product, QUANTITY); //Exercise inv.addItemQuantity(product, QUANTITY); //Verify List lineItems = inv.getLineItems(); LineItem actual = (LineItem)lineItems.get(0); assertEquals(expItem.getInv(), actual.getInv()); assertEquals(expItem.getProd(), actual.getProd()); assertEquals(expItem.getQuantity(), actual.getQuantity()); }
失敗した時に見分けはつくか?
Meszaros says:同じタイプの Assertion Method を使うときは
Assertion Message を書いておくことt-wada says:
Assertion Message も Smell では?
Missing Assertion Message (3)
● Possible Solution● GUI のある IDE の場合はスタックトレースをクリックして
飛んで、ブレークポイントつけて再実行できたりする ● コマンドラインで実行している場合には GUI ランナーや
IDE で実行して落ちている assertion を見つけよう● それらもダメな場合は行番号を追ったり、もっと深追いし
なければならない● そんな頑張らずに、数字でもいいから Assertion Message 入れよう (そういえばひがさんはそうしてた)
Smells in This Chapter
● Assertion Roulette● Erratic Test● Fragile Test● Frequent Debugging● Manual Intervention● Slow Tests
Erratic Test
erratic【名】
1. 変人、奇人
2. 《地学》迷子石◆【同】erratic boulder
【形】
1. 〔人の言動が〕とっぴな、風変わりな、異常な、常軌を逸した
2. 一貫性のない、不安定な、一定しない、出来不出来がある、不規則な
3. 《地学》〔石などが氷河で〕漂移した
Erratic Test (1)
● 成功したり失敗したり、動きが一定でない● Symptoms
● いつ実行したかや誰が実行したかによってテストの結果が異なる
● 同じ Test Runner で実行しても結果がバラバラなことがある
Erratic Test (2)
● Impact● いつも Test Suite を緑にしたいので Erratic Test
を suite から外し、Lost Test (p268) につながる● Suite に入れたままにしたらしたで、他のテストで赤が出たときに気づかなかったりする
Erratic Test (3)
● Troubleshooting Advice● 原因がいろいろあるので手強い● 簡単に見つからない場合にはデータを定期的に採っ
て調べる必要がある– どこで成功し、どこで失敗したか– 全部のテストを実行したのか、一部を実行したのか– 同じ行を何度も実行した時に振る舞いが変わったか– 複数の Test Runner を同時に実行した時に振る舞いが変わったか
Erratic Test (4)
● Troubleshooting Advice● 原因がいろいろあるので手強い● 簡単に見つからない場合にはデータを定期的に採っ
て調べる必要がある– どこで成功し、どこで失敗したか– 全部のテストを実行したのか、一部を実行したのか– 同じ行を何度も実行した時に振る舞いが変わったか– 複数の Test Runner を同時に実行した時に振る舞いが変わったか
● データが採れたら次のページの切り分け図へ
Erratic Test (5)
Erratic Test Causes
● Assertion Roulette
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
● Fragile Test
● Frequent Debugging
● Manual Intervention
● Slow Tests
Erratic Test Causes
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
Interacting Tests (1)
● テストが他のテストに何らかの形で依存している
● バリエーション● Interacting Test Suite● Lonely Test
Interacting Tests (2)
● Symptoms● 独立して動かすときには成功しているテストが以下
の状況下で失敗する– 他のテストが suite に追加 / 削除されたとき– Suite の他のテストが失敗 / 成功したとき– テスト自身、もしくは他のテストのファイル名が変更さ
れたとき– Test Runner の新バージョンがインストールされたとき
Interacting Tests (3)
● Root Cause● 多くの場合、 Shared Fixture (p317) を使ってい
てかつ他のテストの (Shared Fixture への) 出力に依存しているために発生する
● 二つの側面から説明できる– The mechanism of interaction– The reason of interaction
Interacting Tests (4)
● The Mechanism of interaction● テスト (インスタンス) の寿命より長いものを使う
ときに起こる– データベース– static 変数
● やめておいたほうがいいもの– Singleton [GoF]– Registry [PofEAA]
Interacting Tests (5)
● The Reason of interaction● 他のテストの fixture setup phase で作られる
fixture に依存している● 他のテストが SUT に与える変化に依存している● 相互排他に行うべきテストを同時に行うことによる競合
Interacting Tests (5)
● 依存関係が終わるとき● 依存している他のテストが
– suite からいなくなる– SUT を変化させなくなる– SUT を変化させる際に失敗するようになる– 改名などにより、テスト実行の順番が後ろになる
● 競合が始まるとき● 競合しているテストが
– suite に加わる– 初めて成功した時点– テスト実行の順番が前になったとき
Interacting Tests (6)
● テスト対象自体は正しいのに失敗するようになるので、 “false-positive” になる。
● 一般論としてテスト実行順に依存するのは良くない● xUnit ファミリーは実行順を保証しない● TestNG は保証する
Interacting Tests (7)
● Possible Solution● Fresh Fixture (p311)
– もっとも望ましい● Shared Fixture を使いたい場合は
– Immutable Shared Fixture● 他のテストが作成するオブジェクトに依存している
時は– 両方のテストに Lazy Setup (p435) を書く
● コードが重複する場合は– Creation Method (p415)– Test Helper (p643)
Interacting Tests (8)
● Possible Solution (Cont'd)● テストで作成されたものが残っていることによる競
合には– Automated Fixture Teardown (p503)
● テストの依存を見つける方法● いつもと違う順番で実行してみる
– すべてのテストを逆順に実行してみる● 普段から常にバラバラの順番でテストを実行するよ
うにしておくと事故を防げて良い
Erratic Test Causes
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
Interacting Test Suites (1)
● Symptoms● Suite 単体では成功するが、 Suite の Suite とい
う構成にすると失敗する
● Root Cause● よくあるのは、同じリソースを Suite が作成しよ
うとし、後から実行された Suite のほうが失敗するケース
Interacting Test Suites (2)
● 探すには● 失敗したテストを見る● それでも分からない時には
– 成功している方の Suite からテストを一つずつ取り除く● 失敗しなくなったときに取り除いたテストが依存元
● 場所はどこか– Shared Fixture– Test Method 内– Setup method– Test Utility
Interacting Test Suites (3)
● Root Cause (Cont'd)● 依存しあっているのは一つのテストとは限らない● テストケースではなく Suite Fixture Setup や
Setup Decorator が引き金となっているかもしれない
● NUnit についてまたいろいろ書いているが割愛
Interacting Test Suites (4)
● Possible Solution● 一番いいのは全部 Fresh Fixture にすること● Fresh Fixture にするのが難しければ Immutable
Shared Fixture● データベースに残っている残骸が問題なら
Automated Teardown
Erratic Test Causes
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
Lonely Test (1)
● Interacting Tests の特殊例● Suite の一部としては実行成功するが、単独で
は動かない● 他のテストの触った Shared Fixture に依存してい
る– Chained Tests– Suite-level fixture setup
Erratic Test Causes
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
Resource Leakage (1)
● Symptoms● テストがだんだん遅くなり、ついには失敗しだすように
なる● Test Runner / SUT / Database Sandbox(p650) をク
リーンアップするとテストが通るようになる● Root Cause
● 有限リソースをテストが消費し、それの開放に失敗している
● テストがどんどん遅くなる● テストを連続して続けるとリソースが使い尽くされ、後続のテストが失敗し始める
Resource Leakage (2)
● Root Cause (Cont'd)● SUT がリソースの開放に失敗している
– これが判明したら、直すのも難しくない● テストが fixture setup でリソースを消費し、開放に失敗している
Resource Leakage (3)
● Possible Solution● SUT がリソースを開放していないとき
– テストは仕事をきちんとしているので SUT のバグを直すべし
● テスト失敗時にリソース開放に失敗している場合 – Guaranteed In-line Teardown (p509)– Automated Teardown
● リソースプールのサイズを 1 にするのがいい● 問題がある場合にはテストがすぐ失敗するようにな
り、気づきやすい
Erratic Test Causes
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
Resource Optimism
● Symptoms● ある環境下で動くテストが他の環境下で失敗する
● Root Cause● ある環境に存在するリソースが他の環境で不在
● Possible Solution● 可能なら、 Fresh Fixture をテストから毎回作成す
るようにする– 相対パスを使ってファイルを生成するなど
● 外部リソースを使わなければならない場合には SCM に入れることも視野に入れる
Erratic Test Causes
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
Unrepeatable Test (1)
● Symptoms● あるテストがテスト群の先頭にある場合はテストが成功し、後続にある場合は失敗する。もしくはその逆。– Pass-Fail-Fail– Fail-Pass-Pass
Unrepeatable Test (2)
● Root Cause● (意図の有無に関わらず) Shared Fixture の使用
– Prebuilt Fixture を使っている場合にはDatabase ● Sandbox を使っていても、同一の開発者が同一の
テストランナーを使ったときは発生する● Lazy Setup を使っている場合には、 fixture が
class 変数を使いつづけたりする
Unrepeatable Test (3)
● Possible Solution● Fresh Fixture!
– Database Sandbox すら生ぬるい● Fake Database (p551)● 共有 Database を使わなければならない場合には
Distinct Generated Values (p723)● Automated Teardown
Erratic Test Causes
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
Test Run War (1)
● Symptoms● 一人でテストを実行する時はテストが成功するが、
複数人が同時にテストを実行すると失敗する● Impact
● カットオフ間近に発生しがちだからイライラする!● 「テストが落ちたら最後の変更を疑え」ルールが機
能しなくなる
Test Run War (2)
● Root Cause● Shared Fixture (Database)
– 行の UPDATE / DELETE / SELECT などの競合– 悲観ロック
● Shared Fixture (File)– 既に他の人がファイルを開いているなど
Test Run War (3)
● Possible Solution● Fresh Fixture (★何回目?)● Test Runner 毎に Database Sandbox を使う
– しかしこれではまだ Shared Fixture 経由の Interacting Test は解決しない
● Immutable Shared Fixture– Test の変更が若干必要かも
● データベースに残る残骸が問題なら Automated Teardown– Test Run War の根本解決にはならないが、確率が減る
Erratic Test Causes
● Erratic Test● Interacting Tests● Interacting Test Suites● Lonely Test● Resource Leakage● Resource Optimism● Unrepeatable Test● Test Run War● Nondeterministic Test
Nondeterministic Test (1)
● Symptoms● テストがランダムに失敗する
– 人や環境、同時実行にも関係なさそう● 複数の Nondeterministic Test が suite にあると問題判別が難しい
● Impact● デバッグに時間がかかりすぎるし、イライラする
Nondeterministic Test (2)
● Root Cause● テスト毎に異なる値を使うことによって発生する
– ただし、異なる値を使うこと自体は悪いことではない。(例 : Distinct Generated Value)
– テスト対象アルゴリズムへの入力にランダムな値を使うときに問題が発生する
● Integer の負数や臨界値など● 長さ限界の文字列など
● ランダム値を使うことは悪くないように思えるが、カバレッジへの信頼とテストの repeatabilty が下がる
Nondeterministic Test (3)
● Root Cause (Cont'd)● Conditional Test Logic の使用がテストにランダ
ムさをもたらす– テスト毎にテスト実行パスが異なる
Nondeterministic Test (4)
● Possible Solution● Conditional Test Logic を取り除いて Linear に実
行されるようにする● ランダム値の使用を止めて deterministic な値を
使うようにする● 臨界値や同値分割を使う● テストが重複し始めたら
– Parameterized Test (p607)– Data-Driven Test (p288)
Smells in This Chapter
● Assertion Roulette● Erratic Test● Fragile Test● Frequent Debugging● Manual Intervention● Slow Tests
Fragile Test
fragile
【形】
1. 壊れやすい、もろい、割れやすい、脆弱な、傷(が)付きやすい、駄目になりやすい、危うい、軽い、か弱い、はかない、きゃしゃな
2. 実質のない、不十分な
3. 束の間の、逼迫した
4. 虚弱な、元気がない、調子が悪い
Fragile Test (1)
● Symptoms● 全然関係ないと思っていた部分の修正で、テストが
コンパイル/実行に失敗し始める– ときには production code に手を触れていないのに失敗
し始めることも● テスト自動化に頑張っていればいるほど、”four
sensitivities” (4 つの過敏症) に引っかかりやすくなる
Fragile Test (2)
● Impact● 変更を行う際にあちこちのテストを見なければなら
ないため、テストのメンテナンスコストが上がる● Incremental delivery を行う XP のようなプロセ
スでは、テストのメンテナンスコスト増は致命傷になりうる
Fragile Test (3)
● Troubleshooting Advice● 「落ちたテストに共通点は無いか?」と自問しよう
– テストと SUT の結合点が見えてくる
● 4 sensitivities● Interface Sensitivity
– コンパイルに失敗する– 動的型付け言語では型非互換ランタイムエラー
● Behavior Sensitivity– 直前の変更を戻したらテストが通るようになる
Fragile Test (4)
● Troubleshooting Advice (Cont'd)● Data Sensitivity
– 直前のコード変更を戻してもテストが失敗する● Shared Fixture を使っている or Fixture setup code を変更し
た
● Context Sensitivity– 直前のコード変更を戻してもテストが失敗する
● テストコードもテストデータもいじってない
Fragile Test (5)
● Troubleshooting Advice (Cont'd)● Data Sensitivity
– 直前のコード変更を戻してもテストが失敗する● Shared Fixture を使っている or Fixture setup code を変更し
た
● Context Sensitivity– 直前のコード変更を戻してもテストが失敗する
● テストコードもテストデータもいじってない
Fragile Test (6)
Fragile Test (7)
● Causes● Indirect Testing
– SUT に他のオブジェクトを介してアクセスしている● Eager Tests
– テストで検証をしすぎている● SUT の結合度が強すぎることの現れ
– そもそも小さい単位でテストするのが難しい SUT なのかもしれない (Hard-to-Test Code)
– Test Doubles を使い慣れていないだけかもしれない
Fragile Test Causes
● Fragile Test● Interface Sensitivity● Behavior Sensitivity● Data Sensitivity● Context Sensitivity● Overspecified Software● Sensitive Equality● Fragile Fixture
Interface Sensitivity
● Symptoms● SUT が変更されるとテストのコンパイル/実行に失
敗する– 静的型付け言語の場合はテストコンパイル失敗– 動的型付け言語の場合はテスト実行失敗– (変種) GUI 経由で Recorded Test を行っている場合
は、 UI 変更でテスト実行に失敗するとか
Interface Sensitivity (2)
● Possible Solution● 原因はわかりやすいことが多い
– コンパイル/テスト失敗する場所が問題箇所– 結局変更点が引き金になっていることが多い
● 内部で使われる interface の場合には SUT API Encapsulation– テスト用の Higher-Level Language (p41) を組む
● Test Utility Methods● Creation Methods
● “公布済み interface” の場合にはきちんと管理するしかない
Fragile Test Causes
● Fragile Test● Interface Sensitivity● Behavior Sensitivity● Data Sensitivity● Context Sensitivity● Overspecified Software● Sensitive Equality● Fragile Fixture
Behavior Sensitivity
● Symptoms● SUT が変更されると関係ない部分のテストの実行に失
敗する● Root Cause
● テストが失敗する事自体は悪くない。自動テストはそのためにある。
● Behavior Sensitivity になるのは、関係ないと思っていたテストの以下の部分が失敗するとき– Pre-test state setup (fixture setup)– Post-test state verification– Tear down (fixture teardown)
Behavior Sensitivity (2)
● Possible Solution● Fixture setup に失敗しているとき
– Creation Method● State Verification に失敗しているとき
– Custom Assertion– Verification Method
● (fixture teardown が無いが…たぶん)– Test Utility Method– Delegated Teardown
Fragile Test Causes
● Fragile Test● Interface Sensitivity● Behavior Sensitivity● Data Sensitivity● Context Sensitivity● Overspecified Software● Sensitive Equality● Fragile Fixture
Data Sensitivity
● Symptoms● テストデータが変更されるとテストの実行に失敗す
る– 既存テストデータの追加、更新、削除– Standard Fixture のセットアップコードが変更された– テスト実行前に Shared Fixture が変更されている
● Root Cause● データが変わっている!
– 検索に余計に引っかかるとか– 無くなっているとか
Data Sensitivity (2)
● Possible Solution● 多くの場合 result verification で発生する
– Verification logic を見直す● 一番良いのは Fresh Fixture● Database Partitioning Scheme● Delta Assertions
Fragile Test Causes
● Fragile Test● Interface Sensitivity● Behavior Sensitivity● Data Sensitivity● Context Sensitivity● Overspecified Software● Sensitive Equality● Fragile Fixture
Context Sensitivity
● Symptoms● 成功していたテストが突然失敗し始める
– Erratic Test との違いは、再現性があること
● Root Cause● 時刻や日付に依存している● SUT が他システムや OS に依存している● 本当にユニットテストになっているか確認しよう。テス
ト範囲が広すぎて Context Sensitivity に陥っていないか
● 広すぎると誰かが DOC を変更しただけでもテストは失敗する
Context Sensitivity (2)
● Root Cause (Cont'd)● 時刻や日付に依存しているコードがあるか● 他システムからの入力に依存しているコードがある
か● 実は問題が発生したりしなかったりするようなら
Erratic Test の知識を生かすべし● Possible Solution
● Test Stub (p529) に置き換える● 時刻依存なら Virtual Clock [VCTP] に置き換える
– (Fowler の FakeClock もみてみよう)
Fragile Test Causes
● Fragile Test● Interface Sensitivity● Behavior Sensitivity● Data Sensitivity● Context Sensitivity● Overspecified Software● Sensitive Equality● Fragile Fixture
Overspecified Software
● テストが SUT がどう振る舞うべきかについて「過剰に」記述されている● Test Double を使った Behavior Verification でよ
く発生する● Mock Object の使いすぎ
● 原因は「What」ではなく「How」をテストで記述してしまっていること● 実装依存になりすぎている
● Use the Front Door First !! (p40)
Fragile Test Causes
● Fragile Test● Interface Sensitivity● Behavior Sensitivity● Data Sensitivity● Context Sensitivity● Overspecified Software● Sensitive Equality● Fragile Fixture
Sensitive Equality
● オブジェクトを文字列に変換して expected と actual を比較している● 関係ない部分の比較で失敗しやすい
● interface の semantics が変更されているという意味では Interface Sensitivity とも言える(?)
● オブジェクトの文字列変換による比較は災いの素 (Gerard)● まさーるさんは文字列比較を奨めていたが…
Fragile Test Causes
● Fragile Test● Interface Sensitivity● Behavior Sensitivity● Data Sensitivity● Context Sensitivity● Overspecified Software● Sensitive Equality● Fragile Fixture
Fragile Fixture
● 新しいテストのために Standard Fixture を編集したら、全然違うテストが失敗した● Data Sensitivity や Context Sensitivity の変種● Fixture の性質の違い
ご清聴ありがとう
ございました
top related