Skip to content
Permalink
Browse files

Merge pull request #28 from rlove/DataDrivenTestsFoundations

Implemented the foundations to provide custom data driven tests.
  • Loading branch information...
vincentparrett committed Dec 28, 2013
2 parents d28922e + fba3dd6 commit eb41c8bcd0e71dec23c166158d36c3ec8bf2005d
Showing with 207 additions and 52 deletions.
  1. +78 −34 DUnitX.TestFixture.pas
  2. +87 −18 DUnitX.TestFramework.pas
  3. +42 −0 Tests/DUnitX.Tests.TestFixture.pas
@@ -66,6 +66,14 @@ TDUnitXTestFixture = class(TWeakReferencedObject, ITestFixture, ITestFixtureIn
protected
//uses RTTI to buid the fixture & tests
procedure GenerateFixtureFromClass;
//used by GenerateFixtureFromClass to be tests from TestCaseInfo
function CreateTestFromTestCase(ACaseInfo : TestCaseInfo; AMethod : TRttiMethod; ATestEnabled : Boolean) : ITest;
//used by GenerateFixtureFromClass to be tests from a method with out test cases
function CreateTestFromMethod(AMethod : TRttiMethod; ATestEnabled : Boolean;AIgnored : Boolean;AIgnoredReason: String) : ITest;
// Check overriding attribute, other wise uses returns fixture value
function GetIgnoreMemoryLeaksForMethod(AMethod : TRttiMethod) : Boolean;


function GetEnabled: Boolean;
procedure SetEnabled(const value: Boolean);

@@ -169,11 +177,15 @@ procedure TDUnitXTestFixture.GenerateFixtureFromClass;
ignoreFixtureSetup : boolean;

fixtureAttrib : TestFixtureAttribute;
testCases : TArray<TestCaseAttribute>;
testCaseAttrib : TestCaseAttribute;
testCases : TArray<CustomTestCaseAttribute>;
testCaseAttrib : CustomTestCaseAttribute;
testCaseSources : TArray<CustomTestCaseSourceAttribute>;
testCaseSourceAttrb : CustomTestCaseSourceAttribute;
testCaseData : TestCaseInfo;
testEnabled : boolean;
ignoredAttrib : IgnoreAttribute;
IgnoreMemoryLeak: IgnoreMemoryLeaks;
ignoredTest : boolean;
ignoredReason : string;
begin
rType := FRttiContext.GetType(FTestClass);
System.Assert(rType <> nil);
@@ -258,63 +270,67 @@ procedure TDUnitXTestFixture.GenerateFixtureFromClass;
((attribute.ClassType <> TestAttribute) and (method.Visibility = TMemberVisibility.mvPublished) and (not method.HasAttributeOfType<TestAttribute>)) then
begin
ignoredAttrib := method.GetAttributeOfType<IgnoreAttribute>;
// Determine if test should be ignored and why
if (ignoredAttrib <> nil) then
begin
ignoredTest := true;
ignoredReason := ignoredAttrib.Reason;
end
else
begin
ignoredTest := false;
ignoredReason := '';
end;

if attribute.ClassType = TestAttribute then
begin
testEnabled := TestAttribute(attribute).Enabled;

if testEnabled and (ignoredAttrib = nil) then
begin
//find out if the test fixture has test cases.
testCases := method.GetAttributesOfType<TestCaseAttribute>;
testCases := method.GetAttributesOfType<CustomTestCaseAttribute>;
//find out if the test has test sources
testCaseSources := method.GetAttributesOfType<CustomTestCaseSourceAttribute>;
end;

if Length(testCases) > 0 then
if (Length(testCases) > 0) or (Length(testCaseSources) > 0) then
begin
// Add individual test cases first
for testCaseAttrib in testCases do
begin
newTest := TDUnitXTestCase.Create(FFixtureInstance, Self, testCaseAttrib.Name, method.Name, method, testEnabled, testCaseAttrib.Values);
newTest.Enabled := testEnabled;

if method.TryGetAttributeOfType<IgnoreMemoryLeaks>(IgnoreMemoryLeak) then
newTest.IgnoreMemoryLeaks := IgnoreMemoryLeak.IgnoreMemoryLeaks
else
newTest.IgnoreMemoryLeaks := FIgnoreMemoryLeaks;

newTest := CreateTestFromTestCase(testCaseAttrib.CaseInfo, method, testEnabled );
FTests.Add(newTest);
end;
// Add test case from test case sources
for testCaseSourceAttrb in testCaseSources do
begin
for testCaseData in testCaseSourceAttrb.CaseInfoArray do
begin
newTest := CreateTestFromTestCase(TestCaseData, method, testEnabled );
FTests.Add(newTest);
end;
end;
end
else
begin
if ignoredAttrib <> nil then
newTest := TDUnitXTest.Create(Self, method.Name, TTestMethod(meth),testEnabled,true,ignoredAttrib.Reason)
else
newTest := TDUnitXTest.Create(Self, method.Name, TTestMethod(meth),testEnabled);

if method.TryGetAttributeOfType<IgnoreMemoryLeaks>(IgnoreMemoryLeak) then
newTest.IgnoreMemoryLeaks := IgnoreMemoryLeak.IgnoreMemoryLeaks
else
newTest.IgnoreMemoryLeaks := FIgnoreMemoryLeaks;

// Add Test annotated with TestAttribute and no test cases or test case sources;
newTest := CreateTestFromMethod(method,testEnabled,ignoredTest,ignoredReason);
FTests.Add(newTest);
end;
end
else
begin
if ignoredAttrib <> nil then
newTest := TDUnitXTest.Create(Self, method.Name, TTestMethod(meth),true,true,ignoredAttrib.Reason)
else
newTest := TDUnitXTest.Create(Self, method.Name, TTestMethod(meth),true);

if method.TryGetAttributeOfType<IgnoreMemoryLeaks>(IgnoreMemoryLeak) then
newTest.IgnoreMemoryLeaks := IgnoreMemoryLeak.IgnoreMemoryLeaks
else
newTest.IgnoreMemoryLeaks := FIgnoreMemoryLeaks;

// Add Published Method that has no attributes
newTest := CreateTestFromMethod(method,true,ignoredTest,ignoredReason);
FTests.Add(newTest);
end;
end;
end;
end
else if method.Visibility = TMemberVisibility.mvPublished then
begin
// Add Published Method that has no Attributes
newTest := TDUnitXTest.Create(Self,method.Name,TTestMethod(meth),true);
FTests.Add(newTest);
end;
@@ -415,6 +431,17 @@ function TDUnitXTestFixture.GetHasTests: boolean;
result := (FTests <> nil) and (FTests.Count > 0);
end;

function TDUnitXTestFixture.GetIgnoreMemoryLeaksForMethod(
AMethod: TRttiMethod): Boolean;
var
IgnoreMemoryLeak: IgnoreMemoryLeaks;
begin
if AMethod.TryGetAttributeOfType<IgnoreMemoryLeaks>(IgnoreMemoryLeak) then
result := IgnoreMemoryLeak.IgnoreMemoryLeaks
else
result := FIgnoreMemoryLeaks;
end;

function TDUnitXTestFixture.GetName: string;
begin
result := FName;
@@ -507,7 +534,7 @@ procedure TDUnitXTestFixture.OnMethodExecuted(const AMethod: TTestMethod);
if TMethod(AMethod).Code = TMethod(FTearDownFixtureMethod).Code then
FFixtureInstance := nil;
end;

end;

procedure TDUnitXTestFixture.SetEnabled(const value: Boolean);
@@ -520,5 +547,22 @@ procedure TDUnitXTestFixture.SetEnabled(const value: Boolean);
FRttiContext := TRttiContext.Create;
end;

function TDUnitXTestFixture.CreateTestFromMethod(AMethod: TRttiMethod;
ATestEnabled, AIgnored: Boolean; AIgnoredReason: String): ITest;
var
Meth : TMethod;
begin
meth.Code := AMethod.CodeAddress;
meth.Data := FFixtureInstance;
result := TDUnitXTest.Create(Self, AMethod.Name, TTestMethod(meth),ATestEnabled,AIgnored,AIgnoredReason);
result.IgnoreMemoryLeaks := GetIgnoreMemoryLeaksForMethod(AMethod);
end;

function TDUnitXTestFixture.CreateTestFromTestCase(ACaseInfo : TestCaseInfo; AMethod : TRttiMethod; ATestEnabled : Boolean) : ITest;
begin
result := TDUnitXTestCase.Create(FFixtureInstance, Self, ACaseInfo.Name, AMethod.Name, AMethod, ATestEnabled, ACaseInfo.Values);
result.IgnoreMemoryLeaks := getIgnoreMemoryLeaksForMethod(AMethod);
end;

end.

@@ -170,23 +170,77 @@ RepeatAttribute = class(TCustomAttribute)


/// <summary>
/// The TestCaseAttribute allows you to specify the name of a function that
/// returns a TValueArray which will be passed into a function that takes
/// parameters. This is really only needed to work around the problens with
/// the TestCaseAttribute. 
/// Internal Structure used for those implementing CustomTestCase or
/// CustomTestCaseSource descendants.
/// </summary>
TestCaseInfo = record

/// <summary>
/// Name of the Test Case
/// </summary>
Name : string;

/// <summary>
/// Values that will be passed to the method being tested.
/// </summary>
Values : TValueArray;
end;

TestCaseInfoArray = array of TestCaseInfo;


/// <summary>
/// Base class for all Test Case Attributes.   
/// </summary>
/// <remarks>
/// Note that the types in the TConstArray should match the parameters of
/// the method we are testing.
/// Class is abstract and should never be, used to annotate a class as a
/// attribute.   Instead use a descendant, that implements the GetCaseInfo
/// method.
/// </remarks>
TestCaseAttribute = class(TCustomAttribute)
private
FCaseName : string;
FValues : TValueArray;
CustomTestCaseAttribute = class abstract(TCustomAttribute)
protected
function GetCaseInfo : TestCaseInfo; virtual; abstract;
public
constructor Create(const ACaseName : string; const AValues : string);overload;
property Name : string read FCaseName;
property Values : TValueArray read FValues;
property CaseInfo : TestCaseInfo read GetCaseInfo;
end;

/// <summary>
/// Base class for all Test Case Source Attributes.   
/// </summary>
/// <remarks>
/// <para>
/// Class is abstract and should never be, used to annotate a class as a
/// attribute.   Instead use a descendant, that implements the
/// GetCaseInfoArray method.    
/// </para>
/// <para>
/// Note: If a method is annotated with a decendant of
/// TestCaseSourceAttribute and returns an empty TestCaseInfoArray, then
/// no test will be shown for the method.
/// </para>
/// </remarks>
CustomTestCaseSourceAttribute = class abstract(TCustomAttribute)
protected
function GetCaseInfoArray : TestCaseInfoArray; virtual; abstract;
public
property CaseInfoArray : TestCaseInfoArray read GetCaseInfoArray;
end;


/// <summary>
/// The TestCaseAttribute allows you to pass values to a test function.
/// Each value is delimited in the string, by default the delimiter is ','
/// </summary>
TestCaseAttribute = class(CustomTestCaseAttribute)
protected
FCaseInfo : TestCaseInfo;
function GetCaseInfo : TestCaseInfo; Override;
function GetName: String;
function GetValues: TValueArray;
public
constructor Create(const ACaseName : string; const AValues : string;const ASeperator : string = ',');overload;
property Name : String read GetName;
property Values : TValueArray read GetValues;
end;

TTestMethod = procedure of object;
@@ -1452,18 +1506,18 @@ class procedure TDUnitX.RegisterTestFixture(const AClass: TClass; const AName :
{ TestCaseAttribute }


constructor TestCaseAttribute.Create(const ACaseName: string; const AValues: string);
constructor TestCaseAttribute.Create(const ACaseName: string; const AValues: string;const ASeperator : string);
var
i: Integer;
l : integer;
lValues : TStringDynArray;
begin
FCaseName := ACaseName;
lValues := SplitString(AValues,',');
FCaseInfo.Name := ACaseName;
lValues := SplitString(AValues,ASeperator);
l := Length(lValues);
SetLength(FValues,l);
SetLength(FCaseInfo.Values,l);
for i := 0 to l -1 do
FValues[i] := TValue.From<string>(lValues[i]);
FCaseInfo.Values[i] := TValue.From<string>(lValues[i]);
end;


@@ -1502,6 +1556,21 @@ procedure TTestFixtureHelper.WriteLn(const msg: string);
end;
{$ENDIF}

function TestCaseAttribute.GetCaseInfo: TestCaseInfo;
begin
result := FCaseInfo;
end;

function TestCaseAttribute.GetName: String;
begin
result := FCaseInfo.Name;
end;

function TestCaseAttribute.GetValues: TValueArray;
begin
result := FCaseInfo.Values;
end;

{ RepeatAttribute }

constructor RepeatAttribute.Create(const ACount: Cardinal);
@@ -47,10 +47,28 @@ TTestClassWithNonPublicSetup = class
end;
{$M-}

MockTestSourceAttribute = class(CustomTestCaseSourceAttribute)
protected
function GetCaseInfoArray : TestCaseInfoArray; override;
end;

{$M+}
[TestFixture]
TTestClassWithTestSource = class
public
[Test]
[MockTestSource]
procedure DataTest(Value : Integer);
end;


{$M-}


implementation

uses
Math,
SysUtils;
{ TDUnitXTestFixtureTests }

@@ -68,6 +86,30 @@ procedure TTestClassWithNonPublicSetup.Setup;
FSetupRun := True;
end;

{ TTestSourceAttribute }

function MockTestSourceAttribute.GetCaseInfoArray: TestCaseInfoArray;
var
I : Integer;
begin
SetLength(result,3);
for I := 0 to 2 do
begin
result[I].Name := 'DataTest' + IntToStr(I);
SetLength(result[I].Values,1);
result[I].Values[0] := I;
end;
end;

{ TTestClassWithTestSource }

procedure TTestClassWithTestSource.DataTest(Value: Integer);
begin
TDUnitX.CurrentRunner.Status(Format('DataTest(%d) Called',[Value]));
Assert.IsTrue(InRange(Value,0,2));
end;

initialization
TDUnitX.RegisterTestFixture(TTestClassWithNonPublicSetup);
TDUnitX.RegisterTestFixture(TTestClassWithTestSource);
end.

0 comments on commit eb41c8b

Please sign in to comment.
You can’t perform that action at this time.