2010-02-11 101 views
8

如果我有一個在特定條件下調用自己的方法,是否可以編寫一個測試來驗證行爲?我很想看到一個例子,我不關心模擬框架或語言。我在C#中使用RhinoMocks,所以我很好奇它是否是框架的一個缺失功能​​,或者如果我誤解了一些基本的東西,或者它是不可能的。如何編寫一個遞歸方法的Mockist測試

+0

這我不清楚。什麼是試圖測試?該方法自稱爲「在特定條件下」(即「調用堆棧」將在某些條件下遵循某一特定路徑)或其他內容? – Ando 2010-03-02 08:23:10

回答

3

假設你想要做的事就像從一個完整的路徑獲得的文件名,例如:

c:/windows/awesome/lol.cs -> lol.cs 
c:/windows/awesome/yeah/lol.cs -> lol.cs 
lol.cs -> lol.cs 

,你必須:

public getFilename(String original) { 
    var stripped = original; 
    while(hasSlashes(stripped)) { 
    stripped = stripped.substringAfterFirstSlash(); 
    } 
    return stripped; 
} 

,你想寫:

public getFilename(String original) { 
    if(hasSlashes(original)) { 
    return getFilename(original.substringAfterFirstSlash()); 
    } 
    return original; 
} 

這裏的遞歸是一個實現細節,不應該進行測試。您確實希望能夠在兩個實現之間切換,並驗證它們是否產生相同的結果:兩者都爲上述三個示例生成lol.cs。這就是說,因爲你是通過名字遞歸而不是說thisMethod.again()等,所以在Ruby中你可以將原始方法別名爲新名稱,用舊名稱重新定義方法,調用新的名稱並檢查您是否以新定義的方法結束。

def blah 
    puts "in blah" 
    blah 
end 

alias blah2 blah 

def blah 
    puts "new blah" 
end 

blah2 
+0

你是說在這種情況下,驗證方法狀態的單元測試是否足夠好? – JeremyWeir 2010-03-01 17:45:16

+0

對不起,我不完全瞭解你的問題。 在我的文件路徑示例中,驗證方法輸出的單元測試就足夠了,甚至比驗證遞歸的單元測試更好。 但是,我不知道你的具體情況,所以可能會有所不同。 – miaubiz 2010-03-02 09:14:52

+0

@jayrdub - 在一般情況下,狀態驗證正是您希望單元測試所要做的。檢查方法的返回值和/或被測對象的公共屬性。其他一切都是實現細節,並且在重構期間可能會更改。 – TrueWill 2010-03-04 15:07:57

1

在我意識到的任何模擬框架中,沒有任何東西需要監視堆棧深度/(遞歸)函數調用次數。但是,適當的模擬前提條件提供正確輸出的單元測試應該與嘲諷非遞歸函數相同。

導致堆棧溢出的無限遞歸需要單獨調試,但單元測試和嘲笑從來沒有擺脫過這種需求。

6

一個自稱在一定條件下,是有可能寫一個測試,以驗證該行爲的方法?

是的。但是,如果您需要測試遞歸,則最好將入口點分爲遞歸和遞歸步驟以進行測試。

無論如何,這裏是如何測試它的例子,如果你不能這樣做。你真的不需要任何嘲弄:

// Class under test 
public class Factorial 
{ 
    public virtual int Calculate(int number) 
    { 
     if (number < 2) 
      return 1 
     return Calculate(number-1) * number; 
    } 
} 

// The helper class to test the recursion 
public class FactorialTester : Factorial 
{ 
    public int NumberOfCalls { get; set; } 

    public override int Calculate(int number) 
    { 
     NumberOfCalls++; 
     return base.Calculate(number) 
    } 
}  

// Testing 
[Test] 
public void IsCalledAtLeastOnce() 
{ 
    var tester = new FactorialTester(); 
    tester.Calculate(1); 
    Assert.GreaterOrEqual(1, tester.NumberOfCalls ); 
} 
[Test] 
public void IsCalled3TimesForNumber3() 
{ 
    var tester = new FactorialTester(); 
    tester.Calculate(3); 
    Assert.AreEqual(3, tester.NumberOfCalls ); 
} 
4

你誤解了模擬對象的目的。 Mock(以Mockist的意義)用於測試與被測系統相關性的行爲交互。

所以,舉例來說,你可能有這樣的事情:

interface IMailOrder 
{ 
    void OrderExplosives(); 
} 

class Coyote 
{ 
    public Coyote(IMailOrder mailOrder) {} 

    public void CatchDinner() {} 
} 

狼取決於IMailOrder。在生產代碼中,Coyote的實例將傳遞一個實現IMailOrder的Acme實例。 (這可以通過手動依賴注入或通過DI框架完成。)

想要測試方法CatchDinner並驗證它是否調用OrderExplosives。要做到這一點,您可以:

  1. 創建實現IMailOrder模擬對象,並通過將模擬對象給它的構造創建狼實例(被測系統)。 (Arrange)
  2. 致電CatchDinner。 (Act)
  3. 請求模擬對象驗證是否滿足給定的期望值(OrderExplosives called)。 (Assert)

當您設置對模擬對象的期望可能取決於您的模擬(隔離)框架。

如果您正在測試的類或方法沒有外部依賴關係,則不需要(或不想)爲該組測試使用模擬對象。這個方法是遞歸的還是沒有關係都沒關係。

您一般都想測試邊界條件,因此您可能會測試不應遞歸的調用,具有單個遞歸調用的調用以及深度遞歸調用。 (miaubiz有大約遞歸是一個實現細節,雖然好點。)

編輯:通過在最後一段我的意思是帶參數的調用或對象會觸發一個給定的遞歸深度狀態「呼」。我也推薦閱讀The Art of Unit Testing

編輯2:例使用Moq測試代碼:

var mockMailOrder = new Mock<IMailOrder>(); 
var wily = new Coyote(mockMailOrder.Object); 

wily.CatchDinner(); 

mockMailOrder.Verify(x => x.OrderExplosives()); 
+0

「如果您正在測試的類或方法沒有外部依賴關係,那麼您不需要(或不想)爲該組測試使用模擬對象,這種方法是否遞歸併不重要。」 這是我需要提醒的部分,謝謝。我最喜歡你的回答,但是在我能夠之前自動選擇。 – JeremyWeir 2010-03-04 17:20:59

+0

@jayrdub - 謝謝! :) – TrueWill 2010-03-04 18:59:15

0

這裏是我的 '農民' 的方法(在Python,經測試,請參閱基本原理的評論)是

注實現詳細的「暴露」在這裏不成問題,因爲你正在測試的是底層架構,它恰好被「頂級」代碼所利用。所以,測試它是合法的,行爲良好(我也希望,這是你的想法)。

的代碼(其主要思想是從一個單一的,而是「不可測」遞歸函數去的等效對遞歸依賴性的(並且因此可測試)功能):

def factorial(n): 
    """Everyone knows this functions contract:) 
    Internally designed to use 'factorial_impl' (hence recursion).""" 
    return factorial_impl(n, factorial_impl) 

def factorial_impl(n, fct=factorial): 
    """This function's contract is 
    to return 'n*fct(n-1)' for n > 1, or '1' otherwise. 

    'fct' must be a function both taking and returning 'int'""" 
    return n*fct(n - 1) if n > 1 else 1 

測試:

import unittest 

class TestFactorial(unittest.TestCase): 

    def test_impl(self): 
     """Test the 'factorial_impl' function, 
     'wiring' it to a specially constructed 'fct'""" 

     def fct(n): 
      """To be 'injected' 
      as a 'factorial_impl''s 'fct' parameter""" 
      # Use a simple number, which will 'show' itself 
      # in the 'factorial_impl' return value. 
      return 100 

     # Here we must get '1'. 
     self.assertEqual(factorial_impl(1, fct), 1) 
     # Here we must get 'n*100', note the ease of testing:) 
     self.assertEqual(factorial_impl(2, fct), 2*100) 
     self.assertEqual(factorial_impl(3, fct), 3*100) 

    def test(self): 
     """Test the 'factorial' function""" 
     self.assertEqual(factorial(1), 1) 
     self.assertEqual(factorial(2), 2) 
     self.assertEqual(factorial(3), 6) 

輸出:

Finding files... 
['...py'] ... done 
Importing test modules ... done. 

Test the 'factorial' function ... ok 
Test the 'factorial_impl' function, ... ok 

---------------------------------------------------------------------- 
Ran 2 tests in 0.000s 

OK 
相關問題