Skip to content

Commit

Permalink
Implemented the foundations to provide custom data driven tests.
Browse files Browse the repository at this point in the history
Created new record structure TestCaseInfo that contains, the name and values used for a given test case.
Created two new abstract classes, fully  XML documented in the code.
CustomTestCaseAttribute -  Implementor must return a TestCaseInfo
CustomTestCaseSourceAttribute - Implementor must return an Array of TestCaseInfo

Changed TestCaseAttribute to descend from the CustomTestAttribute.

TDUnitXTestFixture.GenerateFixtureFromClass
 - Was Refactored to remove duplicated code.
 - Implmented checking for CustomTestCaseAttribute, and CustomTestCaseSourceAttribute and created correct tests.
 - Added a few comments so I could keep track of why all the different "if" blocks are needed.

Create a MockDescendant of CustomTestCaseSourceAttribute, and associated tests to insure new functionality works.
  • Loading branch information
rlove committed Dec 20, 2013
1 parent d9645ef commit fba3dd6
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 52 deletions.
112 changes: 78 additions & 34 deletions DUnitX.TestFixture.pas
Expand Up @@ -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);

Expand Down Expand Up @@ -167,11 +175,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);
Expand Down Expand Up @@ -254,63 +266,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;
Expand Down Expand Up @@ -359,6 +375,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;
Expand Down Expand Up @@ -451,7 +478,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);
Expand All @@ -464,5 +491,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.

105 changes: 87 additions & 18 deletions DUnitX.TestFramework.pas
Expand Up @@ -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;
Expand Down Expand Up @@ -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;


Expand Down Expand Up @@ -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);
Expand Down
42 changes: 42 additions & 0 deletions Tests/DUnitX.Tests.TestFixture.pas
Expand Up @@ -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 }

Expand All @@ -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 fba3dd6

Please sign in to comment.