-
Notifications
You must be signed in to change notification settings - Fork 723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add explicit generic type specification to TestCase #4620
Conversation
global.json
Outdated
@@ -2,6 +2,6 @@ | |||
"sdk": { | |||
"version": "8.0.100", | |||
"allowPrerelease": false, | |||
"rollForward": "disable" | |||
"rollForward": "latestFeature" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can set "rollForward": "latestPatch"
to allow 8.0.1xx.
Now it also allows 8.0.2xx and MS is has in the past broken things when adding new "features". (6.0.300)
But I haven't found anything breaking with 8.0.200
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I hadn't realized there was a precedent there.
39e9e95
to
93355d8
Compare
@@ -247,6 +247,11 @@ public bool Explicit | |||
/// </summary> | |||
public string? ExcludePlatform { get; set; } | |||
|
|||
/// <summary> | |||
/// Comma-delimited list of platforms to run the test for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to self: Fix this copy/paste error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, copy it from TestFixtureAttribute's TypeArgs property instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comments requesting a practical example where current code without TypeArgs won't work, but it does now.
@@ -247,6 +247,11 @@ public bool Explicit | |||
/// </summary> | |||
public string? ExcludePlatform { get; set; } | |||
|
|||
/// <summary> | |||
/// Comma-delimited list of platforms to run the test for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, copy it from TestFixtureAttribute's TypeArgs property instead.
@@ -114,5 +114,10 @@ public void MethodWithExcludeRuntime(int num) | |||
public void MethodWithArrayArguments(object o) | |||
{ | |||
} | |||
|
|||
[TestCase("doesn't work", TypeArgs = new[] { typeof(int) })] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel an Analyzer rule request coming up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, analyzers are a great validator for what would otherwise be runtime checks. What do you think about also having a generic attribute to extend this on modern (NET8+) runtimes after we get the framework targeting that TFM?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to wait for .NET8? It is a C#11 feature and it seems to work in .NET6.0 but fails in .NET Framework. with a runtime error: Generic types are not valid
/// <summary>
/// Marks a method as a parameterized test suite and provides arguments for each test case.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
public class TestCaseAttribute<T> : TestCaseAttribute
{
/// <summary>
/// Construct a TestCaseAttribute with a list of arguments.
/// </summary>
public TestCaseAttribute(T argument)
: base(new object?[] { argument })
{
TypeArgs = new[] { typeof(T) };
}
}
And equivalent version with 2, 3, 4, etc. parameters.
With a usage :
[TestCase(2, TypeArgs = new[] { typeof(double) }, ExpectedResult = typeof(double))]
[TestCase<double>(2, ExpectedResult = typeof(double))]
public Type GenericMethodAndParameterWithExplicitOrImplicitTyping<T>(T input)
=> typeof(T);
Assert.That(typeof(T), Is.EqualTo(typeof(int))); | ||
} | ||
|
||
[TestCase(2, TypeArgs = new[] { typeof(int) })] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't this already without having to add TypeArgs
as the TryGetTypeArguments
determines the types from the actual parameters?
Maybe add a [TestCase(2)]
as well to proof it still works.
Thanks for syncing your changes here @OsirisTerje @manfred-brands as I've been revisiting the test scenarios, I've realized it's likely very minimal extra work to make this work for TestCaseSource - something Charlie had also originally advocated for in the issue. |
/// If not set explicitly, the generic types will be inferred | ||
/// based on the test case parameters. | ||
/// </summary> | ||
public Type[]? TypeArgs { get; set; } = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was surprised at just how little was required to make it work for TestCaseSource after TestCase was done. If I had made this public originally instead of private then it would've "just worked"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of the tests are wrong and asserting failures that are the result of that instead of what really should be happening.
var exception = Assert.Throws<NUnitException>(() => test.Method!.Invoke(test.Parent, test.Arguments)); | ||
|
||
Assert.That(exception.InnerException, Has.Message.EqualTo("Object does not match target type.")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. The test now fails because test.Parent
is of type ParameterizedMethodSuite
and not TestCaseSourceAttributeFixture
The error talks about target
type which is the this
argument, not about parameters.
You have to call TestBuilder.RunTest
and check the results which shows that the test fails.
However, in case of mismatch of type arguments, the test is marked as non-runnable as you don't have to run it at al.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, maybe there's been a misunderstanding on my end. I like your suggestion to use RunTest()
as it avoids the need to explicitly check exception status. I'll push some changes shortly.
However, in case of mismatch of type arguments, the test is marked as non-runnable as you don't have to run it at al.
I may still not be understanding this suggestion. Are you saying that mismatched values and generic types should fail earlier than test execution time? If so, I think I'm having a hard time reconciling that with "let the (dotnet) runtime sort it out" when TypeArgs
are explicitly passed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you saying that mismatched values and generic types should fail earlier than test execution time
Yes. If NUnit cannot determine the TypeArgs. See NUnitTestCaseBuilder. If NUnit cannot determine the TypeArguments to use, the test is not runnable.
"let the (dotnet) runtime sort it out" when TypeArgs are explicitly passed.
If TypeArgs
is passed, the above TryGetTypeArguments
is not called and NUnit first try to do some conversion in TypeHelper.ConvertArgumentList
but after that passes it on to the runtime. The conversions done by NUnit are the ones normally done by the compiler when passing an int
to a double
parameter.
…ine if it fails to run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes and tests look fine now.
I'll leave it up to you if you want to add the Generic TestCaseAttribute
s
Thanks @manfred-brands ! I'm going to merge this now for release -related timing (FYI @OsirisTerje ) and will create those follow up issues later today |
Fixes #1215
Adds support for explicitly specifying a generic type for a test case method. I've chosen to keep it simple and let the runtime itself ensure that cases like this work or not:
I'd like to hear thoughts on if we wanted to allow the framework to eagerly detect and mark these as
NonRunnable
. My own suggestion is to propose we defer it to another PR