2013-03-23 74 views
9

我開始使用Delphi-Mocks框架,並且在構造函數中模仿具有參數的類時遇到了問題。 TMock的類函數「創建」不允許參數。如果嘗試創建TFoo.Create的模擬實例(Bar:someType); TObjectProxy.Create時,我得到一個參數計數不匹配';試圖呼叫T.Delphi-Mocks:使用構造函數中的參數嘲笑一個類

的「創建」方法

顯然,這是因爲下面的代碼不會傳遞任何參數的「調用」方法:

instance := ctor.Invoke(rType.AsInstance.MetaclassType, []); 

我創建了一個重載類功能是否通過參數:

class function Create(Args: array of TValue): TMock<T>; overload;static; 

並正在進行有限的測試我已經完成。

我的問題是:

這是一個bug或我只是做錯了?

感謝

PS:我知道,德爾福嘲笑爲界面爲中心,但它不支持類和代碼庫我工作的是99層%的類。

+1

這是我不明白。如果你想模擬一個類,爲什麼你想要創建一個你正在嘲笑的類的實例。當然,嘲笑的全部意義在於,你嘲笑課堂。 – 2013-03-23 14:18:46

+1

當您執行'TMock 。創建'Mocks框架將創建'TFoo'的實例。也許我不明白嘲笑,但我認爲整個觀點是你創造了一些不是'TFoo'的東西。我的意思是,如果你需要做的就是創建'TFoo',那麼就做吧。如果你想嘲笑它,然後找到一個框架,將創建一個'TFoo'的模擬,而不是'TFoo'的一個實例。 – 2013-03-23 14:47:09

+0

@David。我很抱歉,我的問題跳到我的問題沒有任何背景;你是對的。我想模擬一個構造函數有一個參數的類。正如在Delphi-Mocks項目展示[TesTObjectMock示例](https://github.com/VSoftTechnologies/Delphi-Mocks/blob/master/Sample1Main)上提供的示例一樣。pas)被測試的類(TFoo)作爲通用參數傳遞,如同mock:= TMock .create。問題在於類函數「Create」,它調用「Invoke」。 – TDF 2013-03-23 14:53:52

回答

6

正如我所看到的,根本問題是TMock<T>.Create導致被測試類(CUT)被實例化。我懷疑這個框架的設計是基於假設你會嘲笑一個抽象基類。在這種情況下,實例化它將是良性的。我懷疑你正在處理遺留的代碼,它沒有CUT的方便的抽象基類。但在你的情況下,實例化CUT的唯一方法就是將參數傳遞給構造函數,從而打敗了嘲笑的全部目的。而且我想象一下,重新設計遺留代碼庫將需要很多工作,直到您爲需要模擬的所有類抽象基類爲止。

您正在撰寫TMock<TFoo>.Create其中TFoo是一類。這會導致創建代理對象。這發生在TObjectProxy<T>.Create。其代碼如下所示:

constructor TObjectProxy<T>.Create; 
var 
    ctx : TRttiContext; 
    rType : TRttiType; 
    ctor : TRttiMethod; 
    instance : TValue; 
begin 
    inherited; 
    ctx := TRttiContext.Create; 
    rType := ctx.GetType(TypeInfo(T)); 
    if rType = nil then 
    raise EMockNoRTTIException.Create('No TypeInfo found for T'); 

    ctor := rType.GetMethod('Create'); 
    if ctor = nil then 
    raise EMockException.Create('Could not find constructor Create on type ' + rType.Name); 
    instance := ctor.Invoke(rType.AsInstance.MetaclassType, []); 
    FInstance := instance.AsType<T>(); 
    FVMInterceptor := TVirtualMethodInterceptor.Create(rType.AsInstance.MetaclassType); 
    FVMInterceptor.Proxify(instance.AsObject); 
    FVMInterceptor.OnBefore := DoBefore; 
end; 

正如你所看到的代碼假設你的類有一個沒有參數的構造函數。當你在你的類上調用它時,它的構造函數確實有參數,這會導致運行時RTTI異常。

據我瞭解的代碼,類實例化僅用於截取其虛擬方法的目的。我們不想在課堂上做任何其他事情,因爲那樣會打敗嘲笑它的目的。您真正需要的是具有合適的vtable的對象的實例,可由TVirtualMethodInterceptor操縱。你不需要或者不想讓你的構造函數運行。你只是希望能夠模擬恰好具有參數的構造函數的類。

因此,而不是調用構造函數的代碼,我建議您修改它以使其調用NewInstance。這是爲了擁有一個可以被操縱的vtable所需要做的最低限度的事情。而且您還需要修改代碼,以便它不會試圖銷燬模擬實例,而是調用FreeInstance。只要你所做的就是在模擬上調用虛擬方法,所有這些都可以正常工作。

的修改是這樣的:

constructor TObjectProxy<T>.Create; 
var 
    ctx : TRttiContext; 
    rType : TRttiType; 
    NewInstance : TRttiMethod; 
    instance : TValue; 
begin 
    inherited; 
    ctx := TRttiContext.Create; 
    rType := ctx.GetType(TypeInfo(T)); 
    if rType = nil then 
    raise EMockNoRTTIException.Create('No TypeInfo found for T'); 

    NewInstance := rType.GetMethod('NewInstance'); 
    if NewInstance = nil then 
    raise EMockException.Create('Could not find NewInstance method on type ' + rType.Name); 
    instance := NewInstance.Invoke(rType.AsInstance.MetaclassType, []); 
    FInstance := instance.AsType<T>(); 
    FVMInterceptor := TVirtualMethodInterceptor.Create(rType.AsInstance.MetaclassType); 
    FVMInterceptor.Proxify(instance.AsObject); 
    FVMInterceptor.OnBefore := DoBefore; 
end; 

destructor TObjectProxy<T>.Destroy; 
begin 
    TObject(Pointer(@FInstance)^).FreeInstance;//always dispose of the instance before the interceptor. 
    FVMInterceptor.Free; 
    inherited; 
end; 

坦率地說,這看起來更明智一點給我。調用構造函數和析構函數肯定沒有意義。

請讓我知道,如果我在這裏廣泛的標誌,並錯過了這一點。這完全有可能!

+0

首先...哇,只是哇!我非常感謝你爲此付出的努力。我一直對SO社區感到驚歎。謝謝。所以指的是原來的問題,這很可能是一個錯誤?如果是這樣,我想提交你的解決方案的項目(經過廣泛的測試課程)。你還好嗎? – TDF 2013-03-23 17:22:23

+0

@TDF那麼,我不太瞭解Delphi mock的設計,知道它是否是一個bug。我當然不想提出這樣的建議。我建議你聯繫作者。作者最瞭解設計。這是一個非常有趣的問題和話題。順便說一句,你有足夠的聲望能夠投票。您可以投票以及接受。我當然認爲你在這裏得到應得票數的答案。 – 2013-03-23 17:25:21

+0

@DavideHeffernan我確實接受了你的回答並投票給它。你是否認爲,作爲一種禮節,我會投其他貢獻者?他們肯定幫助解決了這個問題,我只是對這種習俗一無所知。謝謝 – TDF 2013-03-23 17:32:15

0

聲明:我不知道Delphi-Mocks。

我想這是設計。從你的示例代碼看起來像Delphi-Mocks正在使用泛型。如果你想實例化一個泛型參數的實例,如:

function TSomeClass<T>.CreateType: T; 
begin 
    Result := T.Create; 
end; 

,那麼你需要在通用類的構造函數約束:

TSomeClass<T: class, constructor> = class 

有一個構造函數約束意味着類型傳遞必須有無參數的構造函數。

你也許可以做類似

TSomeClass<T: TSomeBaseMockableClass, constructor> = class 

,並給TSomeBaseMockableClass特定的構造以及可能再被使用,

需要你的框架的所有用戶獲得他們的所有類從一個特定的基類只是...呃...過於嚴格(輕描淡寫),特別是考慮到Delphi的單一繼承。

+0

這不是泛型。該代碼使用RTTI調用構造函數。它假定被命名爲「創建」。如果代碼想要在調用'TRttiMethod'實例的'Invoke'時很容易傳遞參數。 – 2013-03-23 14:17:30

+0

@DavidHeffernan Aha。但即使不是泛型,框架如何知道參數和通過什麼值? Rtti可以確定參數的數量和類型,但框架仍然不知道它們的含義。如果你希望框架實例化類,你需要「泛型」構造函數:無參數的構造函數和/或接收TValue(或變體)數組的函數;或者你必須在模擬框架上有一個通用的方法來爲其提供一個TValue數組,以便按照規範的順序將其傳遞給構造函數的特定參數。 – 2013-03-23 15:00:01

+0

您只需將參數傳遞給'TMock .Create'。一個開放的'TValue'數組。但對我來說,我不明白你爲什麼要用模擬來創建真實課堂的一個實例。這對我來說毫無意義。 – 2013-03-23 15:01:25

3

我不知道我是否正確地獲得了您的需求,但也許這種hacky方法可能有所幫助。假設你有一個需要在其構造函數的參數類

type 
    TMyClass = class 
    public 
    constructor Create(AValue: Integer); 
    end; 

你可以繼承這個類有一個參數的構造函數和類屬性保存參數

type 
    TMyClassMockable = class(TMyClass) 
    private 
    class var 
    FACreateParam: Integer; 
    public 
    constructor Create; 
    class property ACreateParam: Integer read FACreateParam write FACreateParam; 
    end; 

constructor TMyClassMockable.Create; 
begin 
    inherited Create(ACreateParam); 
end; 

現在你可以使用類屬性將參數傳遞給構造函數。當然,你必須把繼承的類給模擬框架,但是沒有其他改變,派生類也應該這樣做。

如果您確切知道何時實例化該類,那麼您可以爲該類屬性提供正確的參數,這也將起作用。

不用說,這種方法不是線程安全的。

+0

我可以看到的基本問題是CUT最終被實例化。當然,這正是我們首先想要避免的。當然,你提出的建議將在這個框架的範圍內完整地允許代碼編譯和運行。 – 2013-03-23 15:59:58

相關問題