Direkt zum Hauptbereich

Wie setzte ich Mocking Frameworks ein um meine Logik zu Testen?


Allgemein

Bisher hatte ich zum Mocken meist das Delphi-Mocks Framework verwendet, was in den meisten Fällen ausreichte. In dem Post zeige ich, wie man das Mocking von Spring4D verwendet. Der Grund für diesen Post ist, dass es immer noch genügend Programmierer gibt welche noch nie oder viel zu selten Tests schreiben oder einsetzen. Viele reden darüber und erzählen, dass sie "natürlich" Testen. Aber ohne automatische Tests kann man die Qualität sowie einen sauberen Code nicht gewährleisten. TDD (test driven development) hat seine Schattenseiten, aber für jemand der sich nie richtig mit Clean Code, SOLID oder ähnlichem beschäftigt hat, hilft es die Prinzipien einfacher einzuhalten. In jeder Programmiersprache kann ein sauber Code geschrieben werden. Langfristig ist ein schneller, aber unsauberer Code, teurer als einmalig einen sauberen Code zu schreiben. Diese Einsicht ist meiner Erfahrung nach, leider noch nicht bei allen Programmierern angekommen.
Tests dienen dazu, dass mein Code auch später noch das tut was er soll. Aber mit Tests werden Programmierer leichter dazu verleitet, Code in kleine testbare Stücke zu zerlegen und nicht einen riesigen Matsch zu codieren.
Um Programmierer zu einen sauberen Code zu führen, fing ich an alles erst mal als Interface zu definieren. Bedeutet die gesamte "Business" Logik mit Interfaces abzubilden. Es sollte keine Implementation geben. Um nun alles in "Produktiv" Umgebung zu nutzen, sollten die erwarteten Methoden gemockt werden. Wie man dies Anstellt, zeige ich in dem Post.

Dummy.Intf

unit Dummy.Intf;

interface

{$M+}

type
  IMyInterface = interface
    ['{E0021504-654B-4157-A0A2-BE95AEDE86FE}']
    function Additional(A, B: Integer): Integer;
  end;

{$M-}

implementation

end.

Reines Interface ohne jegliche Implementation. Beinhaltet nur die Logik bzw. was die Schnittstelle können muss.

Use.DummyIntf.Demo

unit Use.DummyIntf.Demo;

interface

uses
  Dummy.Intf;

{$M+}

type
  TUseMyInterface = class
  public
    function Execute(Foo: IMyInterface; Value: Integer): Integer;
  end;

{$M-}

implementation

{ **************************************************************************** }

{ TUseMYInterface }

function TUseMyInterface.Execute(Foo: IMyInterface; Value: Integer): Integer;
begin
  Result := Foo.Additional(Value, Value);
end;

end.

Diese Klasse dient nur zur Demonstration wie man das Mocking Objekt einsetzen kann ohne jegliche Implementation. Man kann so, eine Applikation laufen lassen und sehen ob schon alles funktioniert wie gewünscht. Sozusagen ein Prototyp. Die eigentliche Implementation des Interfaces kommt natürlich zu einem späteren Zeitpunkt. Es geht nur darum, den Ablauf eines Moduls oder einer Logik zum laufen zu bringen ohne sich um die Implementation des Interface zu kümmern. Vielleicht stellt man jetzt schon Fehler fest oder es lassen sich Methoden zusammenfassen und oder trennen.

Delphi.Mocks

unit Test.Delphi.Mocks;

interface

{$M+}

uses
  DUnitX.TestFramework;

type
  [TestFixture]
  TUnitTestTObject = class
  published
    [Test]
    procedure Test_DemoDelphiMocks();
  end;

{$M-}

implementation

uses
  System.SysUtils,
  System.Rtti,
  Dummy.Intf,
  Use.DummyIntf.Demo,
  Delphi.Mocks;

{ **************************************************************************** }

{ TUnitTestTObject }

procedure TUnitTestTObject.Test_DemoDelphiMocks;
var
  Actual: Integer;
  Use: TUseMyInterface;

  Mocking: TMock;
begin
  //  Arrange...
  Use := TUseMyInterface.Create;
  try
    Mocking := TMock.Create;
    Mocking.Setup.WillExecute(
      function (const args : TArray; const ReturnType : TRttiType) : TValue
      begin
        args[0] := 1;
        args[1] := 1;
        Result := 2;
      end
      ).When.Additional(1, 1);

    //  Another options...
    //  Mocking.Setup.WillReturn(2).When.Additional(1, 1);
    //  Mocking.Setup.WillReturn(4).When.Additional(It(0).IsAny, It(1).IsAny);

    //  Act...
    Actual := Use.Execute(Mocking.Instance, 1);

    //  Assert...
    Assert.AreEqual(2, Actual);
  finally
    Use.Free;
  end;
end;

initialization
  TDUnitX.RegisterTestFixture(TUnitTestTObject);

end.

Spring.Mocking

unit Test.Spring.Mocking;

interface

{$M+}

uses
  DUnitX.TestFramework;

type
  [TestFixture]
  TUnitTestTObjectSpringMocking = class
  published
    [Test]
    procedure Test_DemoSpringMocks();
  end;

{$M-}

implementation

uses
  System.SysUtils,
  System.IOUtils,
  Spring.Mocking,
  Spring.Mocking.Matching,
  Dummy.Intf,
  Use.DummyIntf.Demo;

procedure TUnitTestTObjectSpringMocking.Test_DemoSpringMocks;
var
  CallerInfo: string;
  Actual: Integer;
  Use: TUseMYInterface;

  ReturnMock: Mock;
begin
  //  Arrange...
  CallerInfo := '';
  Use := TUseMyInterface.Create;
  try
    ReturnMock := Mock.Create;
    ReturnMock.Setup.Executes(
      function(const callInfo: TCallInfo): TValue
      begin
        CallerInfo := 'Has called method name: "'
          + callInfo.Method.Name
          + '" with arguments: '
          + callInfo.Args[0].AsInteger.ToString
          + ' '
          + callInfo.Args[1].AsInteger.ToString;

        Result := 2;
      end).When.Additional(TArg.IsAny, TArg.IsAny);

    //  Another options...
    //  ReturnMock.Setup.Returns(2).When.Additional(1, 1);
    //  ReturnMock.Setup.Returns(2).When.Additional(TArg.IsAny, TArg.IsAny);

    //  Act...
    Actual := Use.Execute(ReturnMock.Instance, 1);

    //  Assert...
    Assert.AreEqual(2, Actual);
    CallerInfo := '* ' + CallerInfo + ' *';
    TFile.AppendAllText('.\Out.txt', CallerInfo);
    Assert.IsTrue(CallerInfo.Length > 0);
  finally
    Use.Free;
  end;
end;

initialization
  TDUnitX.RegisterTestFixture(TUnitTestTObjectSpringMocking);

end.

Mit einem Mocking Framework kann ich Prototypen erstellen, Testen und vorführen ohne schlussendlich irgend eine Implementation im Hintergrund vorrätig zu haben. Stellt sich heraus, dass ich für die Implementation einen anderen Weg einschlagen muss, ändert dies nichts an meinem bisherigen Ablauf. Ich kann hier z.B. die Erstellung durch einen IoC realisieren und bräuchte nur den Type ändern.

Kommentare

Beliebte Posts aus diesem Blog

MVC mit System.Messaging

In der Vergangenheit hatte ich, beim Einsatz vom MVC Pattern immer eine eigene Implementation des Observer Pattern um Nachrichten vom Model ans View zu schicken. Hierbei handelte es sich um ein einfaches Record welches nur Text verschicken konnte. Das View musste deswegen auch immer das Model, bzw. das Interface des Model kennen um Daten für Aktualisierung zu haben. Dies widerspricht meiner Meinung nach, dem Prinzip von Clean Code "Separation of Concerns" und koppelt das View ans Model. Um dem entgegen zu wirken, experimentierte ich mit den Board-Mitteln Delphi, aus dem Namespace System.Messaging . Im View hänge ich mich an den DefaultManager der Klasse TMessageManager . procedure TFormShowMessage.FormCreate(Sender: TObject); begin FSubscriptionId := TMessageManager.DefaultManager.SubscribeToMessage(TMessage , procedure(const Sender: TObject; const AMessage: TMessage) begin Memo1.Lines.Add((AMessage as TMessage ).Value); end); end; Das Model schickt ...

Neuer JSON Serialisierer ab Embarcadero Tokio 10.2

Seit Embarcadero Tokyo 10.2 veröffentlicht hat, steht uns Entwicklern ein neuer sehr gut zu gebrauchender JSON Marshaller zur Verfügung. Leider ist die Dokumentation dürftig. Das schöne ist aber, das Embarcadero sich ziemlich an gängige Verfahren hält. So konnte ich z.B. sehr einfach herausfinden was TJsonCustomCreationConverter<T> macht, nachdem ich C# Beispiele analysierte. Da es leider keine offizielle Dokumentation zum TJsonSerializer gibt, beruhen meine Beispiele auf Versuch und Irrtum. Es gibt 3 Namespaces, welche relevant sind. Zum einen "System.JSON.Serializers", für den eigentlichen Serialisierer und die Klassenattribute, zum anderen "System.JSON.Converters", für eigene Konverter und zuletzt "System.JSON.Types", für z.B. Datumsformate oder Formatierungen. TJsonSerializer Ist die wichtigste Klasse, sie ist implementiert im Namespace System.JSON.Serializers. Diese Klasse ist dafür zuständig, ein Objekt in JSON (Serialisierung) zu formati...