2013-05-28 46 views
1

當使用Moq來模擬稱爲大量次數的依賴項時,我遇到了一個障礙。當我撥打Verify時,Moq需要很長時間(幾分鐘)才能響應,有時會以NullReferenceException崩潰(我想這是可以理解的,因爲Moq必須積累起來才能從「冷啓動」開始執行Verify 「)。Moq在大量調用後很難驗證依賴關係

所以我的問題是,是否有另一種策略,我可以使用Moq來做到這一點,或者我應該回到這個相當不尋常的情況手工製作的存根。具體來說,有沒有辦法先告訴Moq,我只想驗證特定過濾器的參數,並忽略所有其他值?

以下兩種方法都不令人滿意。

鑑於CUT和DEP

public interface ISomeInterface 
{ 
    void SomeMethod(int someValue); 
} 

public class ClassUnderTest 
{ 
    private readonly ISomeInterface _dep; 
    public ClassUnderTest(ISomeInterface dep) 
    { 
     _dep = dep; 
    } 

    public void DoWork() 
    { 
     for (var i = 0; i < 1000000; i++) // Large number of calls to dep 
     { 
      _dep.SomeMethod(i); 
     } 
    } 
} 

Moq的策略1 - 驗證

 var mockSF = new Mock<ISomeInterface>(); 
     var cut = new ClassUnderTest(mockSF.Object); 
     cut.DoWork(); 
     mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345)), 
         Times.Once()); 
     mockSF.Verify(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1)), 
         Times.Never()); 

Moq的策略2 - 回調

 var mockSF = new Mock<ISomeInterface>(); 
     var cut = new ClassUnderTest(mockSF.Object); 
     bool isGoodValueAlreadyUsed = false; 
     mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == 12345))) 
       .Callback(() => 
      { 
       if (isGoodValueAlreadyUsed) 
       { 
        throw new InvalidOperationException(); 
       } 
       isGoodValueAlreadyUsed = true; 
      }); 
     mockSF.Setup(mockInt => mockInt.SomeMethod(It.Is<int>(i => i == -1))) 
       .Callback(() => 
       { throw new InvalidOperationException(); }); 

     cut.DoWork(); 
     Assert.IsTrue(isGoodValueAlreadyUsed); 

回答

3

通常當這種限制達到時,我會重新考慮我的設計(沒有冒犯,我看到你的代表)。看起來被測方法做了太多的工作,這違反了單一職責原則。它首先生成一個大的項目列表,然後驗證每個項目都會調用一個工作人員,同時還驗證該項目是否包含正確的元素。

我的功能分成序列發生器,並驗證該序列有正確的元件,並且其作用在序列的另一種方法,並驗證它執行工人的每個元素:

namespace StackOverflowExample.Moq 
{ 
    public interface ISequenceGenerator 
    { 
     IEnumerable<int> GetSequence(); 
    } 

    public class SequenceGenrator : ISequenceGenerator 
    { 
     public IEnumerable<int> GetSequence() 
     { 
      var list = new List<int>(); 
      for (var i = 0; i < 1000000; i++) // Large number of calls to dep 
      { 
       list.Add(i); 
      } 
      return list; 
     } 
    } 

    public interface ISomeInterface 
    { 
     void SomeMethod(int someValue); 
    } 

    public class ClassUnderTest 
    { 
     private readonly ISequenceGenerator _generator; 
     private readonly ISomeInterface _dep; 

     public ClassUnderTest(ISomeInterface dep, ISequenceGenerator generator) 
     { 
      _dep = dep; 
      _generator = generator; 
     } 

     public void DoWork() 
     { 
      foreach (var i in _generator.GetSequence()) 
      { 
       _dep.SomeMethod(i); 
      } 
     } 
    } 

    [TestFixture] 
    public class LargeSequence 
    { 
     [Test] 
     public void SequenceGenerator_should_() 
     { 
      //arrange 
      var generator = new SequenceGenrator(); 

      //act 
      var list = generator.GetSequence(); 

      //assert 
      list.Should().Not.Contain(-1); 
      Executing.This(() => list.Single(i => i == 12345)).Should().NotThrow(); 
      //any other assertions 
     } 

     [Test] 
     public void DoWork_should_perform_action_on_each_element_from_generator() 
     { 
      //arrange 
      var items = new List<int> {1, 2, 3}; //can use autofixture to generate random lists 
      var generator = Mock.Of<ISequenceGenerator>(g => g.GetSequence() == items); 
      var mockSF = new Mock<ISomeInterface>(); 

      var classUnderTest = new ClassUnderTest(mockSF.Object, generator); 

      //act 
      classUnderTest.DoWork(); 

      //assert 
      foreach (var item in items) 
      { 
       mockSF.Verify(c=>c.SomeMethod(item), Times.Once()); 
      } 
     } 
    } 
} 

編輯: 不同的方法可以混合定義一個特定的期望,包括。 When(),已過時AtMost(),MockBehavior.Strict,Callback

此外,Moq不是設計用於大型集合,因此存在性能損失。您仍然可以使用其他措施來驗證將傳遞給模擬的數據。

對於在OP的例子,這裏是一個簡化的設置:

var mockSF = new Mock<ISomeInterface>(MockBehavior.Strict); 
var cnt = 0; 

mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i != -1))); 
mockSF.Setup(m => m.SomeMethod(It.Is<int>(i => i == 12345))).Callback(() =>cnt++).AtMostOnce(); 

這將拋出爲-1,對多於一個的調用與12,和斷言可以cnt != 0製成。

+0

+1 - 你說得很好。實際上,生成,投影和存儲方面的問題是分開的(CUT是NoSQL ETL過程的一部分),但我很懶惰,試圖在單個單元測試中做太多事情。我將通過對較小數據集的單獨測試驗證投影和存儲依賴性的使用情況,並檢查CUT在更大集合上使用生成器的性能。不過,我仍然有興趣瞭解Moq是否可以選擇配置爲僅跟蹤特定參數值。 – StuartLC

+1

Moq並非設計用於大量可驗證的調用。在一小組調用中,任何組合設置都可以輕鬆完成。閱讀我編輯的簡化設置,瞭解您提供的示例。請注意,它在大型設備上仍然非常慢,但它可以爲您提供可能使用的不同類型設置的想法。 –