diff --git a/DUnitX.TestFixture.pas b/DUnitX.TestFixture.pas index bc9d6eb3..97e660b0 100644 --- a/DUnitX.TestFixture.pas +++ b/DUnitX.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); @@ -167,11 +175,15 @@ procedure TDUnitXTestFixture.GenerateFixtureFromClass; ignoreFixtureSetup : boolean; fixtureAttrib : TestFixtureAttribute; - testCases : TArray; - testCaseAttrib : TestCaseAttribute; + testCases : TArray; + testCaseAttrib : CustomTestCaseAttribute; + testCaseSources : TArray; + testCaseSourceAttrb : CustomTestCaseSourceAttribute; + testCaseData : TestCaseInfo; testEnabled : boolean; ignoredAttrib : IgnoreAttribute; - IgnoreMemoryLeak: IgnoreMemoryLeaks; + ignoredTest : boolean; + ignoredReason : string; begin rType := FRttiContext.GetType(FTestClass); System.Assert(rType <> nil); @@ -254,56 +266,59 @@ procedure TDUnitXTestFixture.GenerateFixtureFromClass; ((attribute.ClassType <> TestAttribute) and (method.Visibility = TMemberVisibility.mvPublished) and (not method.HasAttributeOfType)) then begin ignoredAttrib := method.GetAttributeOfType; + // 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; + testCases := method.GetAttributesOfType; + //find out if the test has test sources + testCaseSources := method.GetAttributesOfType; + 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(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(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(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; @@ -311,6 +326,7 @@ procedure TDUnitXTestFixture.GenerateFixtureFromClass; 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; @@ -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(IgnoreMemoryLeak) then + result := IgnoreMemoryLeak.IgnoreMemoryLeaks + else + result := FIgnoreMemoryLeaks; +end; + function TDUnitXTestFixture.GetName: string; begin result := FName; @@ -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); @@ -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. diff --git a/DUnitX.TestFramework.pas b/DUnitX.TestFramework.pas index dd4e2277..2d2fd46d 100644 --- a/DUnitX.TestFramework.pas +++ b/DUnitX.TestFramework.pas @@ -170,23 +170,77 @@ RepeatAttribute = class(TCustomAttribute) /// - /// 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. + /// + TestCaseInfo = record + + /// + /// Name of the Test Case + /// + Name : string; + + /// + /// Values that will be passed to the method being tested. + /// + Values : TValueArray; + end; + + TestCaseInfoArray = array of TestCaseInfo; + + + /// + /// Base class for all Test Case Attributes.    /// /// - /// 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. /// - 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; + + /// + /// Base class for all Test Case Source Attributes.    + /// + /// + /// + /// Class is abstract and should never be, used to annotate a class as a + /// attribute.   Instead use a descendant, that implements the + /// GetCaseInfoArray method.     + /// + /// + /// 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. + /// + /// + CustomTestCaseSourceAttribute = class abstract(TCustomAttribute) + protected + function GetCaseInfoArray : TestCaseInfoArray; virtual; abstract; + public + property CaseInfoArray : TestCaseInfoArray read GetCaseInfoArray; + end; + + + /// + /// The TestCaseAttribute allows you to pass values to a test function. + /// Each value is delimited in the string, by default the delimiter is ',' + /// + 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(lValues[i]); + FCaseInfo.Values[i] := TValue.From(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); diff --git a/Tests/DUnitX.Tests.TestFixture.pas b/Tests/DUnitX.Tests.TestFixture.pas index 8853d2a6..b8632109 100644 --- a/Tests/DUnitX.Tests.TestFixture.pas +++ b/Tests/DUnitX.Tests.TestFixture.pas @@ -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.