2012-01-25 24 views
20

我正在使用DUnit測試Delphi庫。我有時遇到了一些情況,我寫了幾個非常類似的測試來檢查函數的多個輸入。我可以在DUnit中寫入「參數化」測試

有沒有辦法在DUnit中寫入(類似於)參數化測試?例如,將輸入和期望輸出指定爲合適的測試程序,然後運行測試套件並獲取有關測試的多次運行失敗的反饋?

(編輯:爲例)

例如,假設我有兩個測試是這樣的:

procedure TestMyCode_WithInput2_Returns4(); 
var 
    Sut: TMyClass; 
    Result: Integer; 
begin 
    // Arrange: 
    Sut := TMyClass.Create; 

    // Act: 
    Result := sut.DoStuff(2); 

    // Assert 
    CheckEquals(4, Result); 
end; 

procedure TestMyCode_WithInput3_Returns9(); 
var 
    Sut: TMyClass; 
    Result: Integer; 
begin 
    // Arrange: 
    Sut := TMyClass.Create; 

    // Act: 
    Result := sut.DoStuff(3); 

    // Assert 
    CheckEquals(9, Result); 
end; 

我可能會更加這些測試,做同樣的事情,但隨着不同的投入和期望。我不想將它們合併爲一個測試,因爲我希望它們能夠獨立通過或失敗。

+1

你的意思是動態創建列表中所有輸入值的測試用例嗎?我的(小的)[OpenCTF](http://sourceforge.net/projects/openctf/)測試框架包含動態創建測試用例的代碼。它基於DUnit。 – mjn

+0

您可以隨時在測試類中編寫一個通用參數化方法,並從一個或多個特定(已發佈)的測試方法中調用該方法。 TestCase的Check(Not)Equals方法也可以幫助在這裏幫助保持代碼簡潔,並且仍然爲每個測試提供特定的失敗消息。 –

+0

@Marjan只要第一個Check(Not)Equals失敗,測試方法就會停止執行 - 動態創建測試用例解決了這個問題,所有其他值仍將被測試 – mjn

回答

3

如果DUnit允許編寫這樣的代碼,那麼AddTestForDoStuff的每次調用都會創建一個類似於您示例中的測試用例的測試用例嗎?

Suite.AddTestForDoStuff.With(2).Expect(4); 
Suite.AddTestForDoStuff.With(3).Expect(9); 

我會嘗試發佈一個例子,如何這可以在今天晚些時候進行...


對於.NET已經有類似的東西:流利斷言

http://www.codeproject.com/Articles/784791/Introduction-to-Unit-Testing-with-MS-tests-NUnit-a

+0

沿着這些線會很好。但它可能需要以具體測試作爲參數,對吧?如'Suite.AddTest('DoStuff')。WithArgument(2).Expects(4)' –

+0

@MathiasFalkenberg:這或至少是添加消息的可能性。 –

+0

這個答案很酷。這是我第一次看到因爲一廂情願而獲得的最高票數。是的,如果你能做到這一點,這不會太好!無論如何,如果您能夠生成實際遵循的代碼,將獲得+1首付。 –

0

下面是一個使用TTestCase後代實際(已發佈)測試方法調用的通用參數化測試方法的示例(:

procedure TTester.CreatedWithoutDisplayFactorAndDisplayString; 
begin 
    MySource := TMyClass.Create(cfSum); 

    SendAndReceive; 
    CheckDestinationAgainstSource; 
end; 

procedure TTester.CreatedWithDisplayFactorWithoutDisplayString; 
begin 
    MySource := TMyClass.Create(cfSubtract, 10); 

    SendAndReceive; 
    CheckDestinationAgainstSource; 
end; 

是的,有一些重複,但代碼的主要重複取出這些方法進入SendAndReceive和CheckDestinationAgainstSource方法的祖先類:

procedure TCustomTester.SendAndReceive; 
begin 
    MySourceBroker.CalculationObject := MySource; 
    MySourceBroker.SendToProtocol(MyProtocol); 
    Check(MyStream.Size > 0, 'Stream does not contain xml data'); 
    MyStream.Position := 0; 
    MyDestinationBroker.CalculationObject := MyDestination; 
    MyDestinationBroker.ReceiveFromProtocol(MyProtocol); 
end; 

procedure TCustomTester.CheckDestinationAgainstSource(const aCodedFunction: string = ''); 
var 
    ok: Boolean; 
    msg: string; 
begin 
    if aCodedFunction = '' then 
    msg := 'Calculation does not match: ' 
    else 
    msg := 'Calculation does not match. Testing CodedFunction ' + aCodedFunction + ': '; 

    ok := MyDestination.IsEqual(MySource, MyErrors); 
    Check(Ok, msg + MyErrors.Text); 
end; 

在CheckDestinationAgainstSource參數還允許對於這種類型的用途:

procedure TAllTester.AllFunctions; 
var 
    CF: TCodedFunction; 
begin 
    for CF := Low(TCodedFunction) to High(TCodedFunction) do 
    begin 
    TearDown; 
    SetUp; 
    MySource := TMyClass.Create(CF); 
    SendAndReceive; 
    CheckDestinationAgainstSource(ConfiguredFunctionToString(CF)); 
    end; 
end; 

這最後的測試也使用TRepeatedTest類進行編碼,但我發現類而直觀的使用。上述代碼使我在編碼檢查和生成可理解的故障消息方面具有更大的靈活性。然而它的缺點是在第一次失敗時停止測試。

11

我認爲你正在尋找的東西是這樣的:

unit TestCases; 

interface 

uses 
    SysUtils, TestFramework, TestExtensions; 

implementation 

type 
    TArithmeticTest = class(TTestCase) 
    private 
    FOp1, FOp2, FSum: Integer; 
    constructor Create(const MethodName: string; Op1, Op2, Sum: Integer); 
    public 
    class function CreateTest(Op1, Op2, Sum: Integer): ITestSuite; 
    published 
    procedure TestAddition; 
    procedure TestSubtraction; 
    end; 

{ TArithmeticTest } 

class function TArithmeticTest.CreateTest(Op1, Op2, Sum: Integer): ITestSuite; 
var 
    i: Integer; 
    Test: TArithmeticTest; 
    MethodEnumerator: TMethodEnumerator; 
    MethodName: string; 
begin 
    Result := TTestSuite.Create(Format('%d + %d = %d', [Op1, Op2, Sum])); 
    MethodEnumerator := TMethodEnumerator.Create(Self); 
    Try 
    for i := 0 to MethodEnumerator.MethodCount-1 do begin 
     MethodName := MethodEnumerator.NameOfMethod[i]; 
     Test := TArithmeticTest.Create(MethodName, Op1, Op2, Sum); 
     Result.addTest(Test as ITest); 
    end; 
    Finally 
    MethodEnumerator.Free; 
    End; 
end; 

constructor TArithmeticTest.Create(const MethodName: string; Op1, Op2, Sum: Integer); 
begin 
    inherited Create(MethodName); 
    FOp1 := Op1; 
    FOp2 := Op2; 
    FSum := Sum; 
end; 

procedure TArithmeticTest.TestAddition; 
begin 
    CheckEquals(FOp1+FOp2, FSum); 
    CheckEquals(FOp2+FOp1, FSum); 
end; 

procedure TArithmeticTest.TestSubtraction; 
begin 
    CheckEquals(FSum-FOp1, FOp2); 
    CheckEquals(FSum-FOp2, FOp1); 
end; 

function UnitTests: ITestSuite; 
begin 
    Result := TTestSuite.Create('Addition/subtraction tests'); 
    Result.AddTest(TArithmeticTest.CreateTest(1, 2, 3)); 
    Result.AddTest(TArithmeticTest.CreateTest(6, 9, 15)); 
    Result.AddTest(TArithmeticTest.CreateTest(-3, 12, 9)); 
    Result.AddTest(TArithmeticTest.CreateTest(4, -9, -5)); 
end; 

initialization 
    RegisterTest('My Test cases', UnitTests); 

end. 

看起來像這樣在GUI測試運行:

enter image description here

我會很有興趣知道,如果我已經以不理想的方式解決了這個問題。 DUnit非常全面和靈活,每當我使用它時,我總會感到我錯過了一個更好,更簡單的方法來解決問題。

+2

我的感受是一樣的......這就是我發佈這個問題的原因。雖然你的代碼肯定會產生所需的輸出,但我希望我的測試更具可讀性。 'CreateTest'方法給測試代碼帶來了一層複雜性,我真的寧願避免...... –

19

您可以使用DSharp來改進您的DUnit測試。特別是新單位DSharp.Testing.DUnit.pas(在Delphi 2010及更高版本中)。

只需在TestFramework中將其添加到您的使用中,您就可以將屬性添加到您的測試用例。然後,它可能看起來像這樣:

unit MyClassTests; 

interface 

uses 
    MyClass, 
    TestFramework, 
    DSharp.Testing.DUnit; 

type 
    TMyClassTest = class(TTestCase) 
    private 
    FSut: TMyClass; 
    protected 
    procedure SetUp; override; 
    procedure TearDown; override; 
    published 
    [TestCase('2;4')] 
    [TestCase('3;9')] 
    procedure TestDoStuff(Input, Output: Integer); 
    end; 

implementation 

procedure TMyClassTest.SetUp; 
begin 
    inherited; 
    FSut := TMyClass.Create; 
end; 

procedure TMyClassTest.TearDown; 
begin 
    inherited; 
    FSut.Free; 
end; 

procedure TMyClassTest.TestDoStuff(Input, Output: Integer); 
begin 
    CheckEquals(Output, FSut.DoStuff(Input)); 
end; 

initialization 
    RegisterTest(TMyClassTest.Suite); 

end. 

當你運行它的測試看起來像這樣:

enter image description here

由於德爾福的屬性只接受常量的屬性只取參數作爲一個字符串,其中值由分號分隔。但是沒有任何東西阻止您創建自己的屬性類,這些屬性類會使用正確類型的多個參數來防止「魔術」字符串。無論如何,你僅限於可以是const的類型。

您也可以在方法的每個參數上指定Values屬性,並使用任何可能的組合調用它(如在NUnit中)。

參考其他答案個人,我想編寫單元測試時寫儘可能少的代碼。另外我想知道當我看到接口部分時沒有通過實現部分挖掘測試做什麼(我不打算說:「讓我們做BDD」)。這就是爲什麼我更喜歡這種陳述式的方式。

+0

+1這看起來確實非常有用和有趣。感謝您將它引入我們的關注。 –

+0

+1確實!我一定會考慮一下。我認爲這是迄今爲止建議的最簡單的方法! –

相關問題