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.
Kommentare
Kommentar veröffentlichen