2012-03-23 25 views
4

我試圖測試一個需要工廠的類(Func<T>),我正在使用Moq和AutoFixture。如何測試需要工廠的班級?

設置「環境」以查看工廠是否已被使用以及在返回的實例上使用了多少次以及使用了哪些方法的最佳方式是什麼?

目前我Mock'ing的TInjecting一個Func<T>,保持所有的計數返回模擬實例:

public class SystemUnderTest { 
    public SystemUnderTest(Func<IMyClass> c) 
    { 
     try { 
      var c1 = c(); 
      c1.Name="n1"; 
      c1.Send(); 
     } 
     catch(Exception){ 
      var c2 = c(); 
      c2.Name="n2"; 
      c2.Send(); 
     } 
    } 
} 
private Mock<IMyClass> MockFactory() 
{ 
    var m = new Mock<IMyClass>(); 
    m.SetupProperty(mc=>mc.Name); 
    _returnedStubs.Add(m); 
    return m; 
} 
[Test] 
public void TestSomething() 
{ 
    var fixture = new Fixture(); 
    fixture.Inject(()=>MockFactory().Object) 
    var sut = fixture.CreateAnonymous<SystemUnderTest>(); 
    Assert.That(_returnedStubs.Count,Is.Equal(1)); 
    _returnedStubs[0].Verify(m=>m.Send(),Times.Exactly(1)); 
    _returnedStubs[0].Verify(m=>m.Name = "n1"); 
} 

但感覺有點玄乎/醜陋的我。我很確定測試類中的實例變量是危險的東西

+0

雖然我意識到這可能是一個嘗試將問題減少到可管理的大小,但我不知道你在做什麼? MyClass是一個具體的類嗎?爲什麼它創造了多少次? – 2012-03-23 13:14:18

+0

這很有趣,因爲'IMyClass'是一個收集數據的類,它在Web服務上發送它。但是,如果IMyClass的第一個實例的數據收集引發異常,則使用特定消息創建另一個實例,並且我想驗證:「instance1引發異常時:1)創建instance2,並且2)instance2獲取第一個異常的消息,以及3)不應該創建更多的實例「 – svrist 2012-03-23 13:31:43

+3

我明白了。但是,IMyClass API中存在暫時耦合,這使測試更加困難。如果你改變API來啓用諸如'c1.Send(「n1」);'......這樣的反饋會更容易,這個反饋與AutoFixture真的沒有任何關係,但它也會讓你更容易。 – 2012-03-23 13:48:13

回答

7

由於AutoFixture是able to create anonymous delegates,當被問及創造SystemUnderTest匿名情況下,它也將自動提供一個匿名Func<IMyClass>代表,這反過來又被調用時返回的IMyClass匿名實例。

這意味着,給定這樣的場景:

public class SystemUnderTest 
{ 
    public SystemUnderTest(Func<IMyClass> c) 
    { 
     try 
     { 
      var c1 = c(); 
      // ... 
     } 
     catch (Exception) 
     { 
      var c2 = c(); 
      // ... 
     } 
    } 
} 

下面的代碼:

var fixture = new Fixture(); 
var sut = fixture.CreateAnonymous<SystemUnderTest>(); 

將分配c1c2變量與IMyClass匿名實例。此外,如果您配置AutoFixture到work as an auto-mocking container,例如使用AutoMoqCustomizationIMyClass那些匿名的情況下,也恰巧是起訂量代理人:

var fixture = new Fixture(); 
fixture.Customize(new AutoMoqCustomization()); 
var sut = fixture.CreateAnonymous<SystemUnderTest>(); 

此信息。但是,雖然有用,但並不能真正幫助你在你的特定情況下,因爲你需要在測試中獲得Func<IMyClass>工廠方法返回的模擬對象,以便配置它們的行爲並對它們之間的交互方式做出一些斷言。

最好的解決辦法,在我看來,是變化工廠方法Func<IMyClass>落實到接口。這樣,你可以創建一個假廠當Create方法is invoked multiple times in a sequence返回不同嘲笑IMyClass接口

因此,考慮這個例子:

public interface IFactory<T> 
{ 
    T Create(); 
} 

public class SystemUnderTest 
{ 
    public SystemUnderTest(IFactory<IMyClass> factory) 
    { 
     try 
     { 
      var c1 = factory.Create(); 
      // ... 
     } 
     catch (Exception) 
     { 
      var c2 = factory.Create(); 
      // ... 
     } 
    } 
} 

你可以設置你的測試方案如下:

// Given 
    var c1 = new Mock<IMyClass>(); 
    var c2 = new Mock<IMyClass>(); 
    // TODO: setup the behavior of the mock objects 
    var factory = new Mock<IFactory<IMyClass>>(); 
    factory.Setup(s => s.Create()).ReturnsInOrder(c1.Object, c2.Object); 

    // When 
    var fixture = new Fixture(); 
    fixture.Inject(() => factory.Object) 
    var sut = fixture.CreateAnonymous<SystemUnderTest>(); 

    // Then 
    // TODO: verify the expectations on the mock objects 

注意,ReturnsInOrder是使用Callback方法在沫一個custom extension method q在連續多次調用時從stubbed方法返回不同的值。

1

最好的方法之一是創建自己的函數並將其傳遞,然後設置期望值或狀態。

int numberOfTimesUsed = 0; 
myObject.Func = (x) => 
{ 
    numberOfTimesUsed++; 
    Assert.IsNotNull(x); // checks if x passed was not null 
}; 

... 

Assert.AreEqual(2, numberOfTimesUsed); // checks if called twice