簡體   English   中英

如何避免大型多步單元測試?

[英]How can I avoid large multi-step unit tests?

我正在嘗試對執行相當復雜的操作的方法進行單元測試,但是我已經能夠將這個操作分解為可模擬接口上的許多步驟,如下所示:

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個子步驟)。 這是一種一體化的東西,因此嘗試使用多個較小的測試是行不通的。 它開始接近未來維護者的只寫代碼,下個月當我忘記它是如何工作的時候,我自己也會這樣做。 一定有更好的方法! 對?

單獨測試IDependencyX的每個實現。 然后,您將知道該過程的每個步驟都是正確的。 單獨測試時,測試每個可能的輸入和特殊條件。

然后使用IDependencyX的實際實現進行Foo的集成測試。 然后您就會知道所有單個部件都已正確插入。 通常只需要測試一個輸入,因為您只測試簡單的膠水代碼。

許多依賴關系表明代碼中存在隱含的中間概念,因此可能會打包一些依賴關系並使這些代碼變得更簡單。

或者,也許你所擁有的是某種處理器鏈。 在這種情況下,您為鏈中的每個鏈接編寫單元測試,並進行集成測試以確保它們全部組合在一起。

BDD試圖通過繼承來解決這個問題。 如果你習慣它,那么編寫單元測試真的是一種更簡潔的方法。

一對好的鏈接:

問題是BDD需要一段時間才能掌握。

從最后一個鏈接竊取的一個快速示例( 史蒂夫哈曼 )。 注意每個測試方法只有一個斷言。

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>();
    }
}

依賴關系是否也依賴於彼此,必須按照確切的順序調用它們? 如果是這種情況,您實際上是在測試控制器流程,這不是單元測試的實際目的。

例如,如果您的代碼示例是用於GPS的軟件,則您不會測試實際功能,例如導航,計算正確的路線等,而是用戶可以打開它,輸入一些數據,顯示路線並關閉它再次。 看到不同?

專注於測試模塊功能,讓更高級別的程序或質量保證測試執行您在此示例中嘗試執行的操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM