From 09dcd3a3b066f1a6e8f91bafd666b72ab3e7c8a7 Mon Sep 17 00:00:00 2001 From: Manfred Brands Date: Fri, 12 Jul 2024 17:00:04 +0800 Subject: [PATCH] Generic test case attribute (#4755) * Add support for generic TestCaseAttribute * Add support for generic TestCaseData --- .../framework/Attributes/TestCaseAttribute.cs | 87 +++++++++++++++ src/NUnitFramework/framework/TestCaseData.cs | 78 ++++++++++++++ .../testdata/TestCaseAttributeFixture.cs | 11 +- .../Attributes/TestCaseAttributeTests.cs | 82 +++++++++++++- .../tests/Attributes/TestCaseSourceTests.cs | 102 ++++++++++++++++++ 5 files changed, 358 insertions(+), 2 deletions(-) diff --git a/src/NUnitFramework/framework/Attributes/TestCaseAttribute.cs b/src/NUnitFramework/framework/Attributes/TestCaseAttribute.cs index 9fa2041997..114c80b251 100644 --- a/src/NUnitFramework/framework/Attributes/TestCaseAttribute.cs +++ b/src/NUnitFramework/framework/Attributes/TestCaseAttribute.cs @@ -476,4 +476,91 @@ public IEnumerable BuildFrom(IMethodInfo method, Test? suite) #endregion } + +#if NET6_0_OR_GREATER // Although this compiles for .NET Framework, it fails at runtime with a NotSupportedException : Generic types are not valid. + +#pragma warning disable CS3015 // Type has no accessible constructors which use only CLS-compliant types + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public class TestCaseAttribute : TestCaseAttribute + { + /// + /// Construct a TestCaseAttribute with a list of arguments. + /// + public TestCaseAttribute(T argument) + : base(new object?[] { argument }) + { + TypeArgs = new[] { typeof(T) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public class TestCaseAttribute : TestCaseAttribute + { + /// + /// Construct a TestCaseAttribute with a list of arguments. + /// + public TestCaseAttribute(T1 argument1, T2 argument2) + : base(new object?[] { argument1, argument2 }) + { + TypeArgs = new[] { typeof(T1), typeof(T2) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public class TestCaseAttribute : TestCaseAttribute + { + /// + /// Construct a TestCaseAttribute with a list of arguments. + /// + public TestCaseAttribute(T1 argument1, T2 argument2, T3 argument3) + : base(new object?[] { argument1, argument2, argument3 }) + { + TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public class TestCaseAttribute : TestCaseAttribute + { + /// + /// Construct a TestCaseAttribute with a list of arguments. + /// + public TestCaseAttribute(T1 argument1, T2 argument2, T3 argument3, T4 argument4) + : base(new object?[] { argument1, argument2, argument3, argument4 }) + { + TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)] + public class TestCaseAttribute : TestCaseAttribute + { + /// + /// Construct a TestCaseAttribute with a list of arguments. + /// + public TestCaseAttribute(T1 argument1, T2 argument2, T3 argument3, T4 argument4, T5 argument5) + : base(new object?[] { argument1, argument2, argument3, argument4, argument5 }) + { + TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }; + } + } + +#pragma warning restore CS3015 // Type has no accessible constructors which use only CLS-compliant types +#endif } diff --git a/src/NUnitFramework/framework/TestCaseData.cs b/src/NUnitFramework/framework/TestCaseData.cs index 8f634e228b..5d9511398f 100644 --- a/src/NUnitFramework/framework/TestCaseData.cs +++ b/src/NUnitFramework/framework/TestCaseData.cs @@ -194,4 +194,82 @@ public IgnoredTestCaseData Ignore(string reason) #endregion } + +#if NET6_0_OR_GREATER // Although this compiles for .NET Framework, it fails at runtime with a NotSupportedException : Generic types are not valid. + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + public class TestCaseData : TestCaseData + { + /// + /// Construct a TestCaseData with a list of arguments. + /// + public TestCaseData(T argument) + : base(new object?[] { argument }) + { + TypeArgs = new[] { typeof(T) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + public class TestCaseData : TestCaseData + { + /// + /// Construct a TestCaseData with a list of arguments. + /// + public TestCaseData(T1 argument1, T2 argument2) + : base(new object?[] { argument1, argument2 }) + { + TypeArgs = new[] { typeof(T1), typeof(T2) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + public class TestCaseData : TestCaseData + { + /// + /// Construct a TestCaseData with a list of arguments. + /// + public TestCaseData(T1 argument1, T2 argument2, T3 argument3) + : base(new object?[] { argument1, argument2, argument3 }) + { + TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + public class TestCaseData : TestCaseData + { + /// + /// Construct a TestCaseData with a list of arguments. + /// + public TestCaseData(T1 argument1, T2 argument2, T3 argument3, T4 argument4) + : base(new object?[] { argument1, argument2, argument3, argument4 }) + { + TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }; + } + } + + /// + /// Marks a method as a parameterized test suite and provides arguments for each test case. + /// + public class TestCaseData : TestCaseData + { + /// + /// Construct a TestCaseData with a list of arguments. + /// + public TestCaseData(T1 argument1, T2 argument2, T3 argument3, T4 argument4, T5 argument5) + : base(new object?[] { argument1, argument2, argument3, argument4, argument5 }) + { + TypeArgs = new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4), typeof(T5) }; + } + } +#endif } diff --git a/src/NUnitFramework/testdata/TestCaseAttributeFixture.cs b/src/NUnitFramework/testdata/TestCaseAttributeFixture.cs index fefc008c83..24c6a4fbff 100644 --- a/src/NUnitFramework/testdata/TestCaseAttributeFixture.cs +++ b/src/NUnitFramework/testdata/TestCaseAttributeFixture.cs @@ -116,7 +116,16 @@ public void MethodWithArrayArguments(object o) } [TestCase("doesn't work", TypeArgs = new[] { typeof(int) })] - public static void MethodWithIncompatibleTypeArgs(T input) + public static void MethodWithIncompatibleTypeArgs(T _) + { + } + + [TestCase(2.0)] +#if NET6_0_OR_GREATER + [TestCase(2)] + [TestCase(2.0)] +#endif + public static void MethodWithoutTypeArgsWithIncompatibleParameters(string _) { } } diff --git a/src/NUnitFramework/tests/Attributes/TestCaseAttributeTests.cs b/src/NUnitFramework/tests/Attributes/TestCaseAttributeTests.cs index 1d86d6acee..ea92ac27f6 100644 --- a/src/NUnitFramework/tests/Attributes/TestCaseAttributeTests.cs +++ b/src/NUnitFramework/tests/Attributes/TestCaseAttributeTests.cs @@ -766,9 +766,66 @@ public void ExplicitTypeArgsWithUnrelatedParameters(string input) [TestCase(2L, TypeArgs = new[] { typeof(long) }, ExpectedResult = typeof(long))] [TestCase(2, ExpectedResult = typeof(int))] [TestCase(2L, ExpectedResult = typeof(long))] - public Type GenericMethodAndParameterWithExplicitOrImplicitTyping(T input) + [TestCase(2, TypeArgs = new[] { typeof(double) }, ExpectedResult = typeof(double))] + public Type GenericMethodAndParameterWithExplicitOrImplicitTyping(T _) => typeof(T); +#if NET6_0_OR_GREATER + [TestCase(2)] + [TestCase(2.0)] + public void ExplicitGenericTypeArgsWithCompatibleParameters(T input) + { + Assert.That(input, Is.InstanceOf()); + } + + [TestCase(2, 2.0)] + public void ExplicitGenericTypeArgsWithCompatibleParameters(T1 input1, T2 input2) + { + Assert.Multiple(() => + { + Assert.That(input1, Is.InstanceOf()); + Assert.That(input2, Is.InstanceOf()); + }); + } + + [TestCase("2", 2, 2.0)] + public void ExplicitGenericTypeArgsWithCompatibleParameters(T1 input1, T2 input2, T3 input3) + { + Assert.Multiple(() => + { + Assert.That(input1, Is.InstanceOf()); + Assert.That(input2, Is.InstanceOf()); + Assert.That(input3, Is.InstanceOf()); + }); + } + + [TestCase(true, "2", 2, 2.0)] + public void ExplicitGenericTypeArgsWithCompatibleParameters(T1 input1, T2 input2, T3 input3, T4 input4) + { + Assert.Multiple(() => + { + Assert.That(input1, Is.InstanceOf()); + Assert.That(input2, Is.InstanceOf()); + Assert.That(input3, Is.InstanceOf()); + Assert.That(input4, Is.InstanceOf()); + }); + } + + [TestCase(true, 'N', "2", 2, 2.0)] + public void ExplicitGenericTypeArgsWithCompatibleParameters(T1 input1, T2 input2, T3 input3, T4 input4, T5 input5) + { + Assert.Multiple(() => + { + Assert.That(input1, Is.InstanceOf()); + Assert.That(input2, Is.InstanceOf()); + Assert.That(input3, Is.InstanceOf()); + Assert.That(input4, Is.InstanceOf()); + Assert.That(input5, Is.InstanceOf()); + }); + } + +#endif + [Test] public void ExplicitTypeArgsWithUnassignableParametersFailsAtRuntime() { @@ -786,6 +843,29 @@ public void ExplicitTypeArgsWithUnassignableParametersFailsAtRuntime() Assert.That(result.Message, Does.Contain("Object of type 'System.String' cannot be converted to type 'System.Int32'.")); } + [Test] + public void MethodWithoutTypeArgsWithIncompatibleParametersFailsAtRuntime() + { + var suite = TestBuilder.MakeParameterizedMethodSuite( + typeof(TestCaseAttributeFixture), + nameof(TestCaseAttributeFixture.MethodWithoutTypeArgsWithIncompatibleParameters)); + + Assert.Multiple(() => + { + for (int i = 0; i < suite.TestCaseCount; i++) + { + var test = (Test)suite.Tests[i]; + + Assert.That(test.RunState, Is.EqualTo(RunState.Runnable)); + + var result = TestBuilder.RunTest(test); + + Assert.That(result.FailCount, Is.EqualTo(1)); + Assert.That(result.Message, Does.Contain("Object of type 'System.Double' cannot be converted to type 'System.String'.")); + } + }); + } + [TestCase(2, TypeArgs = new[] { typeof(long) })] public void ExplicitTypeArgsWithGenericConstraintSatisfied(int input) where T : IConvertible diff --git a/src/NUnitFramework/tests/Attributes/TestCaseSourceTests.cs b/src/NUnitFramework/tests/Attributes/TestCaseSourceTests.cs index f1b64e7c7f..fd19d01289 100644 --- a/src/NUnitFramework/tests/Attributes/TestCaseSourceTests.cs +++ b/src/NUnitFramework/tests/Attributes/TestCaseSourceTests.cs @@ -514,6 +514,20 @@ private static IEnumerable GenericMethodAndParameterWithExplicitOr { ExpectedResult = typeof(long) }; +#if NET6_0_OR_GREATER + yield return new TestCaseData(2) + { + ExpectedResult = typeof(long) + }; + yield return new TestCaseData(2L) + { + ExpectedResult = typeof(long) + }; + yield return new TestCaseData(2) + { + ExpectedResult = typeof(int) + }; +#endif } [Test] @@ -556,8 +570,96 @@ private static IEnumerable ExplicitTypeArgsWithGenericConstraintSa { TypeArgs = new[] { typeof(IntConverter), typeof(int) } }; +#if NET6_0_OR_GREATER + yield return new TestCaseData(new DerivedIntConverter(), 2); +#endif + } + +#if NET6_0_OR_GREATER + [TestCaseSource(nameof(GenericDataWithGenericConstraint1))] + public void ExplicitGenericDataWithCompatibleParameters(T input) + { + Assert.That(input, Is.InstanceOf()); + } + + private static IEnumerable GenericDataWithGenericConstraint1() + { + yield return new TestCaseData(2); + yield return new TestCaseData(2); + } + + [TestCaseSource(nameof(GenericDataWithGenericConstraint2))] + public void ExplicitGenericDataWithCompatibleParameters(T1 input1, T2 input2) + { + Assert.Multiple(() => + { + Assert.That(input1, Is.InstanceOf()); + Assert.That(input2, Is.InstanceOf()); + }); + } + + private static IEnumerable GenericDataWithGenericConstraint2() + { + yield return new TestCaseData(2, 2.0); + yield return new TestCaseData(2.0, 2); + } + + [TestCaseSource(nameof(GenericDataWithGenericConstraint3))] + public void ExplicitGenericDataWithCompatibleParameters(T1 input1, T2 input2, T3 input3) + { + Assert.Multiple(() => + { + Assert.That(input1, Is.InstanceOf()); + Assert.That(input2, Is.InstanceOf()); + Assert.That(input3, Is.InstanceOf()); + }); + } + + private static IEnumerable GenericDataWithGenericConstraint3() + { + yield return new TestCaseData("2", 2, 2.0); + yield return new TestCaseData(2.0, 2, "2"); + } + + [TestCaseSource(nameof(GenericDataWithGenericConstraint4))] + public void ExplicitGenericDataWithCompatibleParameters(T1 input1, T2 input2, T3 input3, T4 input4) + { + Assert.Multiple(() => + { + Assert.That(input1, Is.InstanceOf()); + Assert.That(input2, Is.InstanceOf()); + Assert.That(input3, Is.InstanceOf()); + Assert.That(input4, Is.InstanceOf()); + }); } + private static IEnumerable GenericDataWithGenericConstraint4() + { + yield return new TestCaseData(true, "2", 2, 2.0); + yield return new TestCaseData(2.0, 2, "2", true); + } + + [TestCaseSource(nameof(GenericDataWithGenericConstraint5))] + public void ExplicitGenericDataWithCompatibleParameters(T1 input1, T2 input2, T3 input3, T4 input4, T5 input5) + { + Assert.Multiple(() => + { + Assert.That(input1, Is.InstanceOf()); + Assert.That(input2, Is.InstanceOf()); + Assert.That(input3, Is.InstanceOf()); + Assert.That(input4, Is.InstanceOf()); + Assert.That(input5, Is.InstanceOf()); + }); + } + + private static IEnumerable GenericDataWithGenericConstraint5() + { + yield return new TestCaseData(true, 'N', "2", 2, 2.0); + yield return new TestCaseData(2.0, 2, "2", 'N', true); + } + +#endif + #region Sources used by the tests private static readonly object[] MyData = new object[] {