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

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...

Delphi mit Facebook per OAuth2 verbinden

Als ich mich das erste mal mit OAuth2 zur Autorisierung beschäftigte, begann ich mit der RESTDemo.exe von Delphi zu experimentieren. Dies hat aber nicht so einfach funktioniert wie ich dachte. Also beschreibe ich hier mal mein Vorgehen wie ich mich mit Facebook Autorisieren kann. Die Vorarbeit liegt nicht unbedingt auf Seite Delphi, sondern genauso auf der Seite Facebook. Registrierung Facebook Als erstes muss man seine APP auf  https://developers.facebook.com/apps  registrieren. Als nächstes, geht man auf Einstellungen - Allgemeines und holt sich die "App-ID", in Delphi "Client-ID" und "Client-Secret", bzw. "App-Geheimcode". Beides ist später für die Url nötig. Wichtig in dem Zusammenhang, als "App Domain" "localhost" einzutragen. Um den Status der App auf Live zu stellen ist von Seite Facebook ein Links zur Datenschutzrichtlinie erforderlich. Hatte hierfür die Url meines Arbeitgebers eingetragen. Um die App Live zu s...

Wer ist Nathan Thurnreiter

Das bin ich Nathan Thurnreiter. Ein mit Leib und Seele begeisterter Delphi Programmierer. Viele Entwickler von heute kennen sich sehr gut aus wie man schnell was erstellt und ändert. Aber können Prinzipien wie SOLID oder LOD einfach nicht richtig anwenden, bzw. haben es nicht verinnerlicht. Dies bewegte mich endlich, selbst einen Blog zu eröffnen und den mal in deutsch. Nun aber mal ein bisschen zu mir. Bin über Umwege zur Informatik gestossen. Hatte zuerst Automobilmechaniker gelernt und dann Jahre beim Studium von Maschienenbau/Fahrzeugtechnik und technischer Physik verbracht, biss ich dann zum Programmieren kam. Dies begann mit Pascal bzw. Delphi und ich bin fast ausschliesslich dabei geblieben. Natürlich habe ich wie jeder Programmierer mal über den Tellerrand geschaut. Aber bis auf ein paar nervige Kleinigkeiten ist Delphi für mich die Sprache schlecht hin. Momentan leite ich ein kleines Entwickler Team, welches mit C# und Delphi ein Warenwirtschaftssystem erstellt. Die nächste...