2013-03-23 2 views
9

Я начинаю использовать фреймворк Delphi-Mocks, и у меня возникают проблемы с издевательством над классом с параметрами в конструкторе. Функция класса «Создать» для TMock не позволяет параметры. Если вы попытаетесь создать макетный экземпляр TFoo.Create (Bar: someType); Я получаю несоответствие счетчика параметров », когда TObjectProxy.Create; пытаюсь вызвать метод Т.Delphi-Mocks: Издевается над классом с параметрами в конструкторе

«Создать»

Очевидно, что это происходит потому, что следующий код не проходит никаких параметров для метода «Invoke»:

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

Я создал перегруженную функцию класса, Проходят параметры:

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

и работает с ограниченным тестированием, которое я сделал.

Мой вопрос:

Является ли это ошибка или я просто делаю неправильно?

Благодаря

PS: Я знаю, что Delphi-Mocks является интерфейсом-ориентированным, но это делает классы поддержки и базовый код, я работаю на 99% Классов.

+1

Вот что я не понимаю. Если вы пытаетесь издеваться над классом, зачем вам нужен экземпляр класса, который вы издеваетесь над созданием. Наверняка весь смысл насмешек заключается в том, что вы, ну, издеваетесь над классом. –

+1

Когда вы делаете 'TMock . Создайте фреймворк Mocks, создав экземпляр' TFoo'. Возможно, я не понимаю насмешек, но я думал, что все дело в том, что вы создали нечто, что не было 'TFoo'. Я имею в виду, если все, что вам нужно сделать, это создать 'TFoo', тогда просто сделайте это. Если вы хотите издеваться над этим, найдите инфраструктуру, которая создаст макет 'TFoo', а не экземпляр' TFoo'. –

+0

@ Давид. Мне жаль, что мой вопрос не подходит к моей проблеме без каких-либо предпосылок; Ты прав. Я хочу высмеять класс, у конструктора которого есть параметр (ы). Так как образец, представленный в проекте Delphi-Mocks, показывает [образец TesTObjectMock] (https://github.com/VSoftTechnologies/Delphi-Mocks/blob/master/Sample1Main.pas) проверяемый класс (TFoo) передается как общий параметр, как в mock: = TMock .create. Проблема заключается в функции класса «Создать», и она вызывает «Invoke». – TDF

ответ

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 во время выполнения.

Как я понимаю код, экземпляры класса создаются исключительно с целью перехвата виртуальных методов. Мы не хотим ничего делать с классом, так как это скорее победит цель издевательства над ним. Все, что вам действительно нужно, это экземпляр объекта с подходящей виртуальной таблицей, которую можно манипулировать TVirtualMethodInterceptor. Вам не нужно или хотите, чтобы ваш конструктор запускался. Вы просто хотите быть в состоянии высмеять класс, который имеет конструктор с параметрами.

Так вместо того, чтобы этот код вызова конструктора Я предлагаю вам изменить его, чтобы сделать его называют NewInstance. Это минимальный минимум, который вам нужно сделать, чтобы иметь возможность управлять виртуальной таблицей. И вам также потребуется изменить код, чтобы он не пытался уничтожить экземпляр mock и вместо этого набрал 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

Прежде всего ... WOW, просто WOW! Я очень ценю работу, которую вы вложили в это. Я постоянно удивляюсь сообществу SO. Спасибо. Итак, ссылаясь на исходный вопрос, это скорее всего ошибка? Если это так, я бы хотел представить ваше решение для проекта (после тщательного тестирования). С тобой все в порядке? – TDF

+0

@TDF Ну, я не знаю достаточно о дизайне Delphi mocks, чтобы узнать, является ли это ошибкой. Я бы, конечно, не хотел это предлагать. Я предлагаю вам связаться с автором. Автор знает дизайн лучше всего. Это очень интересный вопрос и тема.Кстати, у вас достаточно репутации, чтобы иметь возможность голосовать. Вы можете голосовать и принимать. Я, конечно, думаю, что у вас есть ответы здесь, которые заслуживают голоса. –

+0

@DavideHeffernan Я принял ваш ответ и проголосовал за него. Вы предлагаете, чтобы в качестве вопроса этикета я голосовал за других участников? Они, безусловно, помогли с этим вопросом, я просто не знаю этого обычая. Спасибо – TDF

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 для вызова конструктора. Предполагается, что он называется 'Create'. Если код, который ему нужен, мог бы легко передавать параметры при вызове 'Invoke' на экземпляре' TRttiMethod'. –

+0

@DavidHeffernan Aha. Но даже если это не сводится к дженерикам, как структура будет знать, какие параметры и какие значения должны пройти? Rtti может определять количество и типы параметров, но структура все равно не будет иметь понятия о значении их. Если вы хотите, чтобы среда создавала классы, вам нужны «общие» конструкторы: без параметров и/или один, который получает массив TValue (или Variant); или у вас должен быть общий метод в макетной структуре, чтобы предоставить ему массив TValue для передачи в порядке спецификации в конкретные параметры конструктора. –

+0

Вы просто передадите параметры в 'TMock .Create'. Открытый массив 'TValue'. Но для меня я не могу понять, почему вы хотите использовать mocks для создания экземпляра реального класса. Для меня это не имеет смысла. –

3

Я не уверен, правильно ли я получил ваши потребности, но, возможно, этот хакерский подход может помочь. Если у вас есть класс, который требует параметр в конструкторе

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 заканчивается тем, что создается. Наверняка именно это мы и стараемся избегать. Конечно, то, что вы предлагаете, будет аккуратно разрешать компиляцию и запуск кода в рамках этой структуры. –

Смежные вопросы