2011-07-12 72 views
12

我有一大堆的在我的應用服務層的方法正在做這樣的事情:應用服務層:單元測試,集成測試還是兩者?

public void Execute(PlaceOrderOnHoldCommand command) 
{ 
    var order = _repository.Load(command.OrderId); 
    order.PlaceOnHold(); 
    _repository.Save(order); 
} 

而目前,我有這樣一串單元測試:

[Test] 
public void PlaceOrderOnHold_LoadsOrderFromRepository() 
{ 
    var repository = new Mock<IOrderRepository>(); 
    const int orderId = 1; 
    var order = new Mock<IOrder>(); 
    repository.Setup(r => r.Load(orderId)).Returns(order.Object); 

    var command = new PlaceOrderOnHoldCommand(orderId); 
    var service = new OrderService(repository.Object); 
    service.Execute(command); 

    repository.Verify(r => r.Load(It.Is<int>(x => x == orderId)), Times.Exactly(1)); 
} 

[Test] 
public void PlaceOrderOnHold_CallsPlaceOnHold() 
{ 
      /* blah blah */ 
} 

[Test] 
public void PlaceOrderOnHold_SavesOrderToRepository() 
{ 
      /* blah blah */ 
} 

這些單元測試是否增加值得付出努力的價值似乎是有爭議的。不過,我確信應用服務層應該進行集成測試。

應該測試應用程序服務層到這個粒度級別還是集成測試足夠?

回答

8

確切的說:這是值得商榷的!確實很重要的一點是,你正在考慮編寫和維護測試的費用/努力與它將帶給你的價值 - 這正是你爲每個測試寫的考慮因素。通常我會看到爲了測試而編寫的測試,因此僅向代碼庫添加了鎮流器。

作爲一個指導方針,我通常認爲我需要對每個重要的成功案例/用例進行完整的集成測試。我會寫的其他測試是針對的部分代碼可能會與未來的更改相沖突,或者在過去的中破解。這絕對不是全部代碼。這就是您對系統和要求的判斷和洞察力發揮作用的地方。

假設您有一個service.Execute(placeOrderOnHoldCommand)的(集成)測試,我不確定它是否增加了測試值是否會增加測試服務器是否從存儲庫中僅加載一次訂單的值。但它可能是!例如,如果您的服務之前有一個令人討厭的錯誤,會導致存儲庫十次出現單個訂單,從而導致性能問題(只是補充)。在那種情況下,我會將測試重命名爲PlaceOrderOnHold_LoadsOrderFromRepositoryExactlyOnce()

因此,對於每一個測試,你必須爲自己決定...希望有所幫助。

注:

  1. 測試顯示您可以完全有效的,並期待寫得很好。

  2. 您的測試序列方法似乎受到當前實施方法Execute(...)的啓發。當你用這種方式構建你的測試時,可能是你正在將自己綁定到特定的實現。通過這種方式,測試實際上可以讓測試變得更加困難 - 確保您只測試班級的重要外部行爲。

+1

這些都是真的你提出的好點。我想我會先編寫集成測試*,然後查看單元測試可能帶來的附加值(例如,在提供錯誤訂單ID時拋出InvalidOperationException)。而且你也提出了一個關於測試實現的好點 - 關鍵是輸入和輸出,而不是實現。 –

4

我通常會編寫主要場景的單個集成測試。通過主要場景,我的意思是所有正在測試的代碼的成功路徑。然後我編寫所有其他場景的單元測試,例如檢查交換機中的所有情況,測試異常等等。

我認爲這兩者都是非常重要的,而且可以僅用集成測試來測試它,但這會讓您的測試長時間運行並且難以調試。平均而言,我認爲每個集成測試有10個單元測試。

我不打擾用單線測試方法,除非在那一行發生類似邏輯的事情。

更新:只是爲了說清楚,因爲我在做測試驅動開發,我總是先寫單元測試,最後通常會進行集成測試。

10

儘管還有一個集成測試,我還是寫一個單元測試。但是,通過消除模擬框架,編寫我自己的簡單模擬,然後結合所有這些測試來檢查模擬存儲庫中的順序是否處於保持狀態,我可能會使測試更簡單。

[Test] 
public void PlaceOrderOnHold_LoadsOrderFromRepository() 
{ 
    const int orderId = 1; 
    var repository = new MyMockRepository(); 
    repository.save(new MyMockOrder(orderId));  
    var command = new PlaceOrderOnHoldCommand(orderId); 
    var service = new OrderService(repository); 
    service.Execute(command); 
    Assert.IsTrue(repository.getOrder(orderId).isOnHold()); 
} 

確實沒有必要檢查以確保負載和/或保存被調用。相反,我只是確保MyMockRepository返回更新順序的唯一方法是調用load和save。

這種簡化是我通常不使用模擬框架的原因之一。在我看來,如果你寫自己的模擬,你對測試有更好的控制,並且寫出它們更容易。

+1

有趣的拿鮑勃叔叔。看起來它突然結束了,雖然:) –

+0

但也有可能測試'Order.PlaceOnHold()'。因此,如果現在您決定使用ID 1的訂單不能被擱置,您有2個測試來修復 – driushkin

+0

@unclebob謝謝!我將考慮這個問題。 @driushkin確實有'Order.PlaceOnHold()'的測試。但是,是否可以暫停訂單完全是實施真正訂單類的關注 - 模擬不會對服務方法中發生的情況產生任何影響。 –