2009-02-26 3 views
3

私はユニットテストにかなり複雑な操作を実行するメソッドをしようとしているが、私はそうのようなmockableのインターフェイスでダウンステップの数にその操作を破ることができました:大きな多段階ユニットテストを避けるにはどうすればよいですか?

public class Foo 
{ 
    public Foo(IDependency1 dp1, IDependency2 dp2, IDependency3 dp3, IDependency4 dp4) 
    { 
     ... 
    } 

    public IEnumerable<int> Frobnicate(IInput input) 
    { 
     var step1 = _dependency1.DoSomeWork(input); 
     var step2 = _dependency2.DoAdditionalWork(step1); 
     var step3 = _dependency3.DoEvenMoreWork(step2); 
     return _dependency4.DoFinalWork(step3); 
    } 

    private IDependency1 _dependency1; 
    private IDependency2 _dependency2; 
    private IDependency3 _dependency3; 
    private IDependency4 _dependency4; 
} 

私はテスト目的でモックを生成するために模擬フレームワーク(Rhino.Mocks)を使用し、ここに示す方法でコードを構造化することはこれまでのところ非常に効果的でした。しかし、どのように私は毎回設定されたすべてのモックオブジェクトとすべての期待を必要とする大きなテストをせずに、このメソッドを単体テストしますか?例:

[Test] 
public void FrobnicateDoesSomeWorkAndAdditionalWorkAndEvenMoreWorkAndFinalWorkAndReturnsResult() 
{ 
    var fakeInput = ...; 
    var step1 = ...; 
    var step2 = ...; 
    var step3 = ...; 
    var fakeOutput = ...; 

    MockRepository mocks = new MockRepository(); 

    var mockDependency1 = mocks.CreateMock<IDependency1>(); 
    Expect.Call(mockDependency1.DoSomeWork(fakeInput)).Return(step1); 

    var mockDependency2 = mocks.CreateMock<IDependency2>(); 
    Expect.Call(mockDependency2.DoAdditionalWork(step1)).Return(step2); 

    var mockDependency3 = mocks.CreateMock<IDependency3>(); 
    Expect.Call(mockDependency3.DoEvenMoreWork(step2)).Return(step3); 

    var mockDependency4 = mocks.CreateMock<IDependency4>(); 
    Expect.Call(mockDependency4.DoFinalWork(step3)).Return(fakeOutput); 

    mocks.ReplayAll(); 

    Foo foo = new Foo(mockDependency1, mockDependency2, mockDependency3, mockDependency4); 
    Assert.AreSame(fakeOutput, foo.Frobnicate(fakeInput)); 

    mocks.VerifyAll(); 
} 

これは非常に脆弱です。 Frobnicateの実装を変更すると、このテストは失敗します(ステップ3を2つのサブステップに分解する)。これはオールインワンのものなので、複数の小さなテストを試してみることはできません。それは、将来のメンテナーのための書き込み専用のコードに近づきます。自分自身が来月にどのように動作するのか忘れてしまいました。より良い方法が必要です!右?

答えて

5

IDependencyXの各実装を単独でテストします。そのプロセスの個々のステップが正しいことがわかります。個別にテストするときは、可能なすべての入力と特殊条件をテストします。

次に、IDependencyXの実際の実装を使用してFooの統合テストを行います。次に、すべての個々の部品が正しく接続されていることがわかります。単純なグルーコードをテストしているだけなので、1つの入力でテストするだけで十分です。

0

BDDは、この問題を継承で解決しようとします。あなたがそれに慣れていれば、本当に単体テストを書くよりきれいな方法です。

カップル良いリンク:

問題は、BDDが習得に時間がかかることです。

最後のリンク(Steve Harman)から簡単な例を盗まれました。テストメソッドごとにアサーションが1つしかないことに注意してください。

using Skynet.Core 

public class when_initializing_core_module 
{ 
    ISkynetMasterController _skynet; 

    public void establish_context() 
    { 
     //we'll stub it...you know...just in case 
     _skynet = new MockRepository.GenerateStub<ISkynetMasterController>(); 
     _skynet.Initialize(); 
    } 

    public void it_should_not_become_self_aware() 
    { 
     _skynet.AssertWasNotCalled(x => x.InitializeAutonomousExecutionMode()); 
    } 

    public void it_should_default_to_human_friendly_mode() 
    { 
     _skynet.AssessHumans().ShouldEqual(RelationshipTypes.Friendly); 
    } 
} 

public class when_attempting_to_wage_war_on_humans 
{ 
    ISkynetMasterController _skynet; 
    public void establish_context() 
    { 
     _skynet = new MockRepository.GenerateStub<ISkynetMasterController>(); 
     _skynet.Stub(x => 
      x.DeployRobotArmy(TargetTypes.Humans)).Throws<OperationInvalidException>(); 
    } 

    public void because() 
    { 
     _skynet.DeployRobotArmy(TargetTypes.Humans); 
    } 

    public void it_should_not_allow_the_operation_to_succeed() 
    { 
     _skynet.AssertWasThrown<OperationInvalidException>(); 
    } 
} 
+0

この例では、スタブのみをテストしているようですか? –

+1

例は楽しいです。 :)私は、BDDの本質を、詳細を圧倒することなく捉えていると思います。 BDDを学ぶことの問題は、一気にすべてを突き詰めることですから、BDDのすべての良さを伝えるだけではあまり複雑ではない、簡単な例を見つけるのは本当に難しいことです。 –

+0

マイケル、BDDへのポインタありがとう!偉大なもの、私は既にテストスイートを変換しました。 – ciscoheat

0

依存関係も、正確な順序で呼び出す必要があるため、相互に依存していますか?この場合、実際にユニットフローの目的ではないコントローラフローをテストしています。

たとえば、コード例がGPS用のソフトウェアであった場合、ナビゲート、正しいルートの計算などの実際の機能をテストするのではなく、ユーザーがオンにしたり、ルートを表示したり、もう一度オフにしてください。違いを見ます?

モジュールの機能をテストし、この例で実行しようとしていたことをより高いレベルのプログラムまたは品質保証テストで行うようにします。

1

多くの依存関係は、中間的な概念が暗黙的にコード内にあることを示唆しています。したがって、おそらく依存関係の一部がパッケージ化され、このコードが簡単になります。

また、おそらくあなたが持っているものは何らかの種類のハンドラチェーンです。その場合は、チェーン内の各リンクの単体テストを作成し、それらがすべて合っていることを確認するための統合テストを行います。

関連する問題