More consistent handling of optional arguments in test methods (#1281)#2104
More consistent handling of optional arguments in test methods (#1281)#2104Stan-RED wants to merge 7 commits intonunit:masterfrom Stan-RED:fixes/issue-1281
Conversation
|
This approach takes control away from the attribute, and gives it back to internal nunit processing. What's mirror does it in a way that affects any method called by nunit through Reflect, whether it's a test method or not. It also implies that all new data attributes, including those written by users, will have the same logic applied and breaks Reflect in the sense that it no longer uses exactly the arguments given to it. As discussed, we need a utility class that transforms arguments in various ways. However, the policy decisions about which transforms to use has to rest with the attribute itself. It should be possible to write a data attribute that doesn't allow optional arts, for example. FYI this is similar to the NUnit V2 approach we have moved away from. |
|
I should add.. thanks for taking this on. I think you have the right idea but need to massage it a bit to be consistent with our design goals. Separation of policy (deciding what to so) from implementation is part of that. |
|
I'm not sure what exact problem you’re focusing on so have decomposed into separate concerns. 1. InvokeMethod should be more flexible and have a setting to configure if alignment should be applied or not?I don’t see a big reason why InvokeMethod should work differently compared to how methods are invoked in real word (you won't receive TargetParameterCountException when calling method with optional argument missing). But I think that such additional flexibility may be useful and have updated the idea in code (separate alignment method, used by default in invocation and easy way to change the default behavior). 2. Reflect class is not a good place for this and attributes should be responsible for this?Obviously that this is a reflection specific details, because in natural way we don't call someMethod(1, Type.Missing) we do it as someMethod(1). And only because method is executed in denatured way (through reflection) the call had to be adjusted with Type.Missing values instead of non-existing args. |
|
Let's back up and take a broader view of how test case arguments come to be and how they are used. Data attributes provide arguments in an instance derived from the The two attributes are used as follows:
Putting the transformation of arguments in There may be some changes made to the arguments that don't need to be reflected in the XML or through TestContext. Such changes could be placed directly in the Your original idea was to put this code in |
|
Thank you for the detailed explanation. I think that the only slight disagreement is here:
Maybe I'm wrong, but right now feel a bit more arguments that this is an internal specifics of the Reflection's invocation process. Just imagine such virtual conversation between user and NUnit: Me: Call the TestMethod with two arguments (1,2). Note the big difference between this situation and arguments conversion. After conversion you receive the proper list of arguments that meets the method signature. E.g. for [TestCase(1, 2)]
public void TestMethod(byte a, byte b) => Assert.That(a + b, Is.EqualTo(3));You get the list of real arguments with matching types used to call this method. But for this method: [TestCase(1)]
public void TestMethod(byte a, byte b = 2) => Assert.That(a + b, Is.EqualTo(3));You'll get arguments (1, Type.Missing). And, of course, the call TestMethod(1, Type.Missing) wouldn't be even compiled. In case of "object" type there will be even worse behavior, because it overwrites the default value. So there is a good question, what arguments should be in XML output and TestContext info:
Probably it becomes a little better if replace Type.Missing with default value of missing argument. At least, from the point of reporting. |
|
Looking at how the TestCaseAttribute implements optional parameters, I see your point clearly. The use of @ChrisMaddock Is there a particular reason you did it that way rather using Maybe I'm asking for too much, but what I would really like to see in @Yarmonov 's example is (1, 2) for both cases!!! I"m embarrassed to say that I thought we already did that, which has probably made this discussion a little less coherent than it should have been! |
Not off the top of my head, no! 😄 I've never considered 2.2 - and 2.3 didn't exist at the time I wrote it. 2.3 alone probably means that the attribute should be responsible for filling in ParameterInfo.DefaultValue - so |
|
So, from all this, I guess my conclusion is
As further steps, in subsequent issues we could
Does this approach seem sensible to both of you? |
|
I'm not sure where you meant by I'd also like to see 'further steps' include all similar attributes such as TestFixtureAttribute/TestFixtureSourceAttribute, which also have similar code. Maybe that was implied! |
|
@ChrisMaddock I meant
@Yarmonov are you moving ahead with this based on the recommended changes? |
|
@CharliePoole not yet, have a short vacation right now. But if you're not in hurry I'll try to deal with it during the next week. |
|
@Yarmonov I don't think there's a hurry on this one... just wanted to be sure you were still on it after our various comments. 😄 |
|
@CharliePoole Yes, I hate unfinished things 😄 |
|
Hey @ChrisMaddock, I've commented it in #2268. |
|
Things are not 100% completed yet. Right now working on TestFixture/TestFixtureSource and also going to improve tests. Will try to continue one the week after the next one. |
|
Great, thanks for the update, @StanEgo. When this is ready for review, will you let us know, and also write a little overview of the structure your going for? There's a lot of change here, and I think it would help review if we had your idea of what you're working towards, rather than working it out from the changes! I'm hopeful this change will make this bit of the code much more maintainable. 😄 |
| /// <param name="source"> | ||
| /// Extended instance of <see cref="IAlignArguments"/>. | ||
| /// </param> | ||
| /// <param name="method"> |
There was a problem hiding this comment.
Sorry to pick up on the little things - would you be able to format these <param> tags all on one line however, so they're consistent with the rest of the codebase? Thanks!
Will review this properly once everything's in place - but this one jumped out at me. 🙂
There was a problem hiding this comment.
Params consistency applied))
|
Also, could you give a rough outline of any known changes to functionality? From a quick skim, this is going to introduce some new parameter-parsing options to some attributes (which is good, in that it will standardise them) - just to clarify, there should be no breaking changes from this? |
Sure. I intentionally split all changes into specific commits, with some portion of updated focused on some area.
Feel free to pick up such things. I like the consistency of code style and have read the developer's FAQ. Just have many projects on the table with different conventions, so sometimes mix them 😃
I tried to avoid breaking changes so almost all existing tests stay unchanged. There is some room for discussion, improvements and unlocking some new benefits, but right now for compatibility purposes I even left some things that I'm not completely agreed with 😄 |
|
Sounds great, thanks. 😄 Yes - in general NUnit is pretty serious about backwards-compatibility. Ideally, this change would have no breaking changes - we'll see what it looks like! |
jnm2
left a comment
There was a problem hiding this comment.
Very nice work! I'm really happy you picked this up!
|
|
||
| //Special handling for optional parameters | ||
| if (parms.Arguments.Length < argsNeeded) | ||
| if (!Explicit) |
There was a problem hiding this comment.
Can you help me understand why we only do this for TestCaseAttribute instances without Explicit = true? What about TestCaseAttribute instances with Explicit = false but ExplicitAttribute on the same method?
|
|
||
| /// <summary> | ||
| /// Construct a parameter set with a list of arguments | ||
| /// Construct a parameter set with a list of <paramref name="args">arguments</paramref>. |
There was a problem hiding this comment.
Keep in mind that VS intellisense won't show the contents of <paramref> and <see> elements. Personally I wouldn't bother and would make it self-closing, but if you do include contents make sure that it makes sense when read either way like you did here.
| /// <param name="args"></param> | ||
| public TestParameters(object[] args) | ||
| protected TestParameters(object[] args) | ||
| : this() |
There was a problem hiding this comment.
Any particular reason you added the : this() line?
| #endregion | ||
|
|
||
| #region IAlignArguments Members | ||
| /// <inheritdoc /> |
There was a problem hiding this comment.
I'm pretty sure we don't do XML doc post-processing, so <inheritdoc /> will do nothing in this codebase. .NET has no special understanding of this tag. @nunit/framework-team please correct me if I'm wrong.
| public void TestMethodWithWrongArgumentTypesProvidedGivesError() | ||
| { | ||
| TestAssert.IsRunnable(fixtureType, "TestMethodWithWrongArgumentTypesProvided", ResultState.Error); | ||
| // TODO: Should conversion failure somehow differs from the other alignment errors? |
There was a problem hiding this comment.
Should conversion failure somehow differ from the other alignment errors?
I lean towards yes if it doesn't overcomplicate things. @nunit/framework-team what do you think about this?
There was a problem hiding this comment.
Wouldn't this just be covered by what's written in the failure message? Not too sure I understand what's being suggested here?
|
@jnm2, thank you. Unfortunately I'm having business trip right now and strongly overloaded. Will be back in the early beginning of September and will definitely answer all your questions. And hopefully will finish the last tricky thing with TestFixture migration to complete this PR. |
|
[edited to fix mention] |
|
Sorry, it's slightly delayed again, but just for the purpose of another PR #2439 😃 |
…rolled back. Tests will be later reused.
Arguments to parameters aligning routine for TestCase/TestCaseSource/etc is heavily using type conversion to pass actual argument value into parameter with type that may differ. So the first step is to absorb this functionality that is spread over different places (TypeHelper.ConvertArgumentList, TestCaseAttribute.PerformSpecialConversions, ValuesAttribute.GetData) into single responsible class. Also such routines were almost not covered by tests that is improved as well. See #2104 See #2268
|
@StanEgo - @jnm2 raised the point that this PR is getting quite large - from our point of view, this can make things difficult to review to ensure no unexpected changes slip through. Once you're happy with where you've got to, do you think there's any way you might be able to chunk the work here up into multiple PRs, to review as logical changsets? e.g. standardise one attribute at a time or something? That could be really helpful for us |
|
@StanEgo - @jnm2 raised the point over in #2638 that this PR is getting pretty large. From our point of view, this can make things very difficult to review, and ensure no unintended changes are slipping through. Once you're happy with where you're at on this one, do you think you might be able to chunk the work up here into multiple PRs, each changing one logical area? I'm not sure what will work best - but perhaps attribute by attribute, so you could explain the changes required in each case? |
|
@ChrisMaddock this is a good idea. I've intentionally did this PR as a group of relatively independent commits, so it will be easy to implement sub-PRs. Especially it will be good because the next commits I'm preparing right now, related to TestFixture/TestFixtureSource, are really huge due to design issues in reflection wrappers. So I also afraid of how you'll review them. May be use #2268 as a kind of roadmap to discuss all PR's involved? |
|
Closing because nobody has touched this PR in so long. |
An idea addressing #1281