Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   IInvokable nachträglich einem Interface hinzufügen (https://www.delphipraxis.net/197779-iinvokable-nachtraeglich-einem-interface-hinzufuegen.html)

WiPhi 3. Sep 2018 20:30

IInvokable nachträglich einem Interface hinzufügen
 
Hallo liebe Delphianer,

Das Delphi-Mocks braucht ja zwingend Methodeninformationen ($M+) um arbeiten zu können. Nun habe ich ein Problem beim Testen diverser Klassen, welche von einem Interface abhängig sind, welches nicht von IInvokable erbt und auch keine Methodeninformationen hat.

Da das Interface fest in einem Delphi-Package verankert ist, habe ich keine Möglichkeit das noch nachträglich einzubauen. (Bzw. sollte das nur der letzte Ausweg sein. Ich suche eine elegantere Lösung :-D, da ich diese Infos nur für die Tests brauche...)

Nun die große Frage: Bekomme ich diese Informationen irgendwie nachträglich rein?
Das Interface "abzuleiten" und für dieses die Methodenionformationen mittels $M+ hinzu zu compilieren klappt leider nicht.

Ich versuche das ganze zusätzlich noch einmal zu veranschaulichen:

Gegeben ist ein Interface, welches ich nicht ändern kann:
Delphi-Quellcode:
INichtAenderbaresInterface = interface // Hier fehlt das (IInvokable) oder $M+
  procedure MacheEtwas;
  procedure MacheEtwasAnderes;
end;
Und eine Klasse, welches dieses Interface benutzt
Delphi-Quellcode:
TEineKlasse = class
private
  FBenoetigtesInterface: INichtAenderbaresInterface;
public
  procedure IchSollGetestetWerden;
  constructor Create(const BenoetigtesInterface: INichtAenderbaresInterface);
end;

...
TEineKlasse.Create(const BenoetigtesInterface: INichtAenderbaresInterface);
begin
  FBenoetigtesInterface := BenoetigtesInterface;
end;

TEineKlasse.IchSollGetestetWerden;
begin
  // Die Funktion greift direkt auf das Interface zu
  FBenoetigtesInterface.MacheEtwas;
  // ....
  FBenoetigtesInterface.MacheEtwasAnderes;
end;
Diese Klasse funktioniert in der laufenden Applikation, jedoch nicht wenn ich sie teste und das Interface mocke. Beispielcode in DUnitX:
Delphi-Quellcode:
procedure TestFall.Teste;
var
  dummy: TMock<INichtAenderbaresInterface>;
  TesteEineKlasse: TEineKlasse;
begin
  dummy := TMock<INichtAenderbaresInterface>.Create; // <-- Hier knallt es, da keine RTTI Infos
  TesteEineKlasse := TEineKlasse.Create(dummy);
  TesteEineKlasse.IchSollGetestetWerden;
end;
Für Ideen und Ansätze bedanke ich mich schon vorab :)

Stevie 4. Sep 2018 09:54

AW: IInvokable nachträglich einem Interface hinzufügen
 
Nachträglich method info ($M+) hinzufügen geht nicht, bzw es bewirkt nur, dass ab dieser Ebene RTTI für Methoden erzeugt werden.

Wenn du den Code aus dem Package wirklich nicht anfassen kannst, bliebe noch die Möglichkeit, die Interface Deklaration zu kopieren, ihm $M+ zu verpassen und das im Mock zu benutzen und dann beim Übergeben an deine zu testende Klasse zu hardcasten.

WiPhi 4. Sep 2018 15:00

AW: IInvokable nachträglich einem Interface hinzufügen
 
Zitat:

Zitat von Stevie (Beitrag 1412408)
... die Interface Deklaration zu kopieren, ihm $M+ zu verpassen und das im Mock zu benutzen und dann beim Übergeben an deine zu testende Klasse zu hardcasten.

Da spuckt mir leider das TVirtualInterface in die Suppe:
Code:
"Unzureichende RTTI zur Unterstützung dieser Operation verfügbar"
Ich muss das Interface ja dann von dem ursprünglichen "erben" lassen, das ich den Cast machen kann, oder?
Delphi-Quellcode:
{$M+}
ITestNichtAenderbaresInterface = interface(INichtAenderbaresInterface)
  procedure MacheEtwas;
  procedure MacheEtwasAnderes;
end;
{$M-}
Was mich dann zu dieser Lösung bringt:
Delphi-Quellcode:
procedure TestFall.Teste;
var
  dummy: TMock<ITestNichtAenderbaresInterface>;
  TesteEineKlasse: TEineKlasse;
begin
  dummy := TMock<ITestNichtAenderbaresInterface>.Create; // <-- Mag er, kein Problem mehr
  TesteEineKlasse := TEineKlasse.Create(ITestNichtAenderbaresInterface(dummy)); // <-- Hardcast
  TesteEineKlasse.IchSollGetestetWerden; // Aufruf der zu testenden Methode
end;
... ruft auch den Testfall auf
Delphi-Quellcode:
TEineKlasse.IchSollGetestetWerden;
begin
  FBenoetigtesInterface.MacheEtwas;  // <-- Jetzt knallt es hier mit einer EInsufficientRtti
  // ....
  FBenoetigtesInterface.MacheEtwasAnderes;
end;
... Den Zugriff das die Interface-Methode, welche ich ihm bei $M+ gegeben habe, sieht er trotzdem nicht und versucht stattdessen die ursprüngliche aufzurufen. (Interface Methoden zu überschreiben geht ja auch nicht :D)

Ich glaube auch nicht das Du das so meintest, wie das mit der Ableitung gemacht habe. Da hab ich sicher was falsch verstanden. Aber wenn ich das Interface nicht von dem ursprünglichen ableite, gehen auch keine Typecasts mehr. Ich stehe sozusagen etwas auf dem Schlauch. Vielleicht brauche ich auch noch einen Kaffee :roll:.

Ich danke Dir aber schon mal für den Input!

Schokohase 4. Sep 2018 15:36

AW: IInvokable nachträglich einem Interface hinzufügen
 
Nein, so sollst du das machen
Delphi-Quellcode:
unit Unit1;

interface

type
  IFoo = interface
    ['{45486F85-F11C-47B8-A85A-328C8845CA41}']
    procedure Foo( );
  end;

implementation

end.
Delphi-Quellcode:
unit Unit2;

interface

type
{$M+}
  IMirrorFoo = interface
    ['{B5628A7F-7ECF-4123-A079-0193C8894185}']
    procedure Foo( );
  end;
{$M-}

implementation

end.
Delphi-Quellcode:
program MockMock;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Rtti,
  Delphi.Mocks,
  Unit1 in 'Unit1.pas',
  Unit2 in 'Unit2.pas';

procedure Test( );
var
  mock: TMock<IMirrorFoo>;
  mirror: IMirrorFoo;
  org: IFoo;
begin
  mock := TMock<IMirrorFoo>.Create( );
  mock.Setup.WillExecute( 'Foo', function( const args: TArray<TValue>; const ReturnType: TRttiType ): TValue
    begin
      WriteLn( 'Foo called' );
    end );
  mirror := mock;
  org := IFoo( mirror );
  org.Foo();
end;

begin
  try
    Test( );
  except
    on E: Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;
  ReadLn;
end.

Stevie 4. Sep 2018 16:25

AW: IInvokable nachträglich einem Interface hinzufügen
 
Zitat:

Zitat von Schokohase (Beitrag 1412438)
Nein, so sollst du das machen

***schnipp***

Gönnau 8-)

WiPhi 6. Sep 2018 10:28

AW: IInvokable nachträglich einem Interface hinzufügen
 
Zitat:

Zitat von Schokohase (Beitrag 1412438)
Nein, so sollst du das machen
Delphi-Quellcode:
procedure Test( );
var
  mock: TMock<IMirrorFoo>;
  mirror: IMirrorFoo;
  org: IFoo;
begin
  // .....
  mirror := mock;
  org := IFoo( mirror ); // <-- WiPhi: darauf bin ich nicht gekommen, ehrlich gesagt wundert es mich das der Compiler das zulässt
  org.Foo();

Das sieht sehr gut aus! Vielen Dank.

Leider zieht das Problem weitere Kreise. Ich habe ein Interface (in dem Package) gefunden, welches als Rückgabewert einer Funktion ein Interface liefert, welches auch keine RTTI Informationen enthält. Sobald er im Test darauf zugreifen möchte, schmiert er mit einer Zugriffsverletzung ab.

Delphi-Quellcode:
IBar = interface
  procedure KannNichts;
end;

IFoo = interface
  function DoBar: IBar;
end;

{$M+}
IMirrorFoo = interface
  function DoBar: IBar;
end;
{$M-}

// ........

procedure TestKlasse.TesteFoo;
var
  mock: TMock<IMirrorFoo>;
  mirror: IMirrorFoo;
  foo: IFoo;
  bar: IBar;
begin
  // ... Code wie bisher ...
  bar := org.DoBar; // <-- Da kommt nichts mehr sinnvolles, Zugriffsverletztung
end;
Dieses IBar Interface auch nch irgendwie da rein zu mogeln kommt mir fast einer Schande gleich, dennoch die Frage ob es möglich ist.

Aber!
Wahrscheinlich wäre es sinnvoller die betreffenden Interfaces zu wrappen und nur noch über diese Wrapper Interfaces zu arbeiten. Dann müsste ich zwar ziemlich viel doppelt schreiben (Wrapper und Package Interfaces), aber ich könnte das Package evtl. jederzeit ersetzen und wesentlich komfortabler testen.

Wie würdet ihr damit verfahren? (Ein Glück hier gibt's so viele Profis :thumb:)

Stevie 6. Sep 2018 10:39

AW: IInvokable nachträglich einem Interface hinzufügen
 
Bevor wir hier noch mehr rumhacken mal die Frage: über was für ein Package reden wir hier? Drittanbieter ohne Code, wo man nicht mal ebend beherzt ein {$M+} reinschreiben und rekompilieren kann?

Davon abgesehen verwirrt mich der code etwas. Was ist nun
Delphi-Quellcode:
org
? Das gemockte IFoo? Wenn ja, na dann hast du ihm ja wohl im mock Setup gesagt, dass er bei DoBar irgendwas sinnvolles zurück geben soll oder?

Schokohase 6. Sep 2018 11:27

AW: IInvokable nachträglich einem Interface hinzufügen
 
Wo ist das Problem?

Vorgehen nach Schema F plus der Methode etwas Lben in die Mock-Hülle pusten
Delphi-Quellcode:
program MockMock;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Rtti,
  Delphi.Mocks;

type
  IBar = interface
    ['{BFDEEA1F-182C-4009-93DA-78236C497E2E}']
    procedure Bar( );
  end;

  IFoo = interface
    ['{45486F85-F11C-47B8-A85A-328C8845CA41}']
    function Foo( ): IBar;
  end;

type
{$M+}
  IMirrorBar = interface
    ['{AC275EFC-EED0-46A9-9409-8D11B3A86AF9}']
    procedure Bar( );
  end;
{$M-}

{$M+}
  IMirrorFoo = interface
    ['{B5628A7F-7ECF-4123-A079-0193C8894185}']
    function Foo( ): IBar;
  end;
{$M-}


procedure Test( );
var
  foomock: TMock<IMirrorFoo>;
  foomirror: IMirrorFoo;
  foo: IFoo;
  barmock: TMock<IMirrorBar>;
  barmirror: IMirrorBar;
  bar: IBar;
begin
  barmock := TMock<IMirrorBar>.Create( );
  barmock.Setup.WillExecute( 'Bar', function( const args: TArray<TValue>; const ReturnType: TRttiType ): TValue
    begin
      WriteLn( 'Bar called' );
    end );
  barmirror := barmock;
  bar := IBar( barmirror );

  foomock := TMock<IMirrorFoo>.Create( );
  foomock.Setup.WillExecute( 'Foo', function( const args: TArray<TValue>; const ReturnType: TRttiType ): TValue
    begin
      WriteLn( 'Foo called' );

      Result := TValue.From( bar );
    end );
  foomirror := foomock;
  foo := IFoo( foomirror );

  foo.Foo( ).Bar( );
end;

begin
  try
    Test( );
  except
    on E: Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;
  ReadLn;
end.

WiPhi 6. Sep 2018 13:04

AW: IInvokable nachträglich einem Interface hinzufügen
 
Es geht um ein Drittanbieter Package zu welchem ich auch den Quellcode habe.
Das Interface anzupassen und das Package neu zu übersetzen wäre nicht das Problem. Müsste ich nur bei jedem Update des Packages den Quellcode abgleichen (was bei diesem wahrscheinlich relativ einfach wäre, da aller Interfaces zentral liegen).

Mir stellt sich aber dabei die grundsätzliche Frage, wie man mit so etwas am besten umgeht. Ich habe ja auch einige Packages ohne Quellcode, dessen Interfaces ich nutze, auf die ich zugreife und testen möchte.

Zitat:

Zitat von Schokohase (Beitrag 1412571)
Wo ist das Problem?

Vorgehen nach Schema F plus der Methode etwas Lben in die Mock-Hülle pusten

Danke! So etwas habe ich gesucht. Ich bin auf dem Gebiet der Mocks noch neu.

Danke an alle für die hilfreiche Unterstützung!

Schokohase 6. Sep 2018 13:51

AW: IInvokable nachträglich einem Interface hinzufügen
 
Ich denke du machst da generell etwas falsch.

Eigentlich definiert man sich eigene Interfaces und verwendet diese in der Anwendung. Bei der Implementierung dieser Interfaces kann man dann auf diese Drittanbieter zurückgreifen. Dadurch kommen diese aber nur mittelbar mit der Anwendung in Berührung.

Die Unit-Tests erfolgen auf jeden Fall mit den eigenen Interfaces.


Alle Zeitangaben in WEZ +1. Es ist jetzt 23:11 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz