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
WIP: Additional Test Events for OneTimeSetUp / OneTimeTearDown #4652
base: master
Are you sure you want to change the base?
Changes from all commits
ada9b1e
c76d7d2
545c404
f65c77a
a58ca39
152f85a
a448bb2
f6b920a
ab1245d
dcabbe0
9826753
af52a6f
3b760be
0907d5f
120d308
e34b629
f1ca0bb
8cbd06f
4c4fba4
da87d51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# Additional Test Events for OneTimeSetUp and OneTimeTearDown | ||
|
||
## Problem Statement | ||
|
||
For our higher-level System Tests, we need a mechanism that notified us during the execution of a test run when certain parts of the test suite are executed. Regarding the begin and the end of a test case, NUnit already already provides corresponding test events. So we can implement a custom TestEventListener and attach some handlers to it that react when the corresponding events occure. | ||
|
||
However, we need a similar mechanism also for OneTimeSetUp and OneTimeTearDown. | ||
|
||
## Solution Statement | ||
|
||
In this branch we tried to implement a solution by adding additional test events for | ||
* Start of OneTimeSetUp | ||
* End of OneTimeSetUp | ||
* Start of OneTimeTearDown | ||
* End of OneTimeTearDown | ||
|
||
We would appreciate your opinion if the provided solution is in principle a good idea or if we should tackle the problem in a completely different way. | ||
|
||
### Description of the Implementation | ||
|
||
#### Extending the ITestListener | ||
|
||
First I extended `ITestListener` by the corresponding APIs, | ||
e.g. `void OneTimeSetUpStarted(ITest test);` | ||
> I am fully aware that the parameter type ITest is not suitable here, because it only contains information about the surrounding test fixture. However I took it as a starting point in order to simulate how event parameters will be passed in the `TestProgressReporter`. | ||
|
||
Possible drawback: | ||
Every class implementing `ITestListener` has to be adapted. These are in this case here: | ||
* The EventQueue | ||
* The QueuingEventListener | ||
* The always empty (dummy) TestListener.cs | ||
* The TestProgressReporter, that creates the xml formated events | ||
* The TeamCityEventListener (seems specific, I left the implementation empty) | ||
* The TextRunner for NUnitLite (left implementation empty for now) | ||
* All test classes that implement `ITestListener` | ||
|
||
#### Finding the Right Place to Raise the Events | ||
|
||
I ended up raising the events in the `OneTimeSetUpCommand` and `OneTimeTearDownCommand`, however I experimented with different locations: | ||
|
||
* OneTimeSetUpCommand.cs | ||
Felt most fitting because the event can be raised as part of the `BeforeTest` / `AfterTest` action, thats why I ended up imlementing the solution here. However I noticed that the OneTimeTearDown event in this case is raised too often in the special case that a fixture has an additional `OneTimeSetUp` in a base class. See my additions in the `TestAssemblyRunnerTests` in order to see what I mean. | ||
* SetUpTearDownItem.cs | ||
Benefit from raising the event here would be that we are most close to the execution of the actual method. | ||
Drawback: At this level we cannot distinguish anymore between `SetUp` and `OneTimeSetUp` etc... | ||
Please see also my comments in this class. | ||
* CompositeWorkItem.cs | ||
This was my first attempt, just because the method name ("MakeOneTimeSetUpCommand") indicated it. However, also here I received additional unexpected events and the methods for OneTimeSetUp and OneTimeTearDown looked a bit asymetric, so I didnt follow up the implementation at that place. | ||
I also stumbled over a code block that looked a little suspicious and could be responsible for the additional unexpected event. Please see also my comments in this class. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt | ||
|
||
namespace NUnit.Framework.Interfaces | ||
{ | ||
/// <summary> | ||
/// The ITestListenerExt interface is used internally to receive | ||
/// notifications of significant events while a test is being | ||
/// run. The events are propagated to clients by means of an | ||
/// AsyncCallback. NUnit extensions may also monitor these events. | ||
/// | ||
/// It is an extension to <see cref="ITestListener"/> that | ||
/// covers events for <see cref="OneTimeSetUpAttribute"/> | ||
/// and <see cref="OneTimeTearDownAttribute"/> methods. | ||
/// </summary> | ||
public interface ITestListenerExt | ||
{ | ||
/// <summary> | ||
/// Called when a OneTimeSetUp has just started. | ||
/// </summary> | ||
/// <param name="test">The test to which this OneTimeSetUp belongs.</param> | ||
void OneTimeSetUpStarted(ITest test); | ||
|
||
/// <summary> | ||
/// Called when a OneTimeSetUp has just finished. | ||
/// </summary> | ||
/// <param name="test">The test to which this OneTimeSetUp belongs.</param> | ||
void OneTimeSetUpFinished(ITest test); | ||
|
||
/// <summary> | ||
/// Called when a OneTimeTearDown has just started. | ||
/// </summary> | ||
/// <param name="test">The test to which this OneTimeTearDown belongs.</param> | ||
void OneTimeTearDownStarted(ITest test); | ||
|
||
/// <summary> | ||
/// Called when a OneTimeTearDown has just finished. | ||
/// </summary> | ||
/// <param name="test">The test to which this OneTimeTearDown belongs.</param> | ||
void OneTimeTearDownFinished(ITest test); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,7 +50,29 @@ public void RunSetUp(TestExecutionContext context) | |
_setUpWasRun = true; | ||
|
||
foreach (IMethodInfo setUpMethod in _setUpMethods) | ||
RunSetUpOrTearDownMethod(context, setUpMethod); | ||
{ | ||
RaiseOneTimeSetUpStarted(context); | ||
try | ||
{ | ||
RunSetUpOrTearDownMethod(context, setUpMethod); | ||
} | ||
finally | ||
{ | ||
RaiseOneTimeSetUpFinished(context); | ||
} | ||
} | ||
} | ||
|
||
private void RaiseOneTimeSetUpStarted(TestExecutionContext context) | ||
{ | ||
if (context.CurrentTest is not null && context.CurrentTest.IsSuite) | ||
context.ListenerExt?.OneTimeSetUpStarted(context.CurrentTest); | ||
} | ||
|
||
private void RaiseOneTimeSetUpFinished(TestExecutionContext context) | ||
{ | ||
if (context.CurrentTest is not null && context.CurrentTest.IsSuite) | ||
context.ListenerExt?.OneTimeSetUpFinished(context.CurrentTest); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -72,7 +94,17 @@ public void RunTearDown(TestExecutionContext context) | |
// run the teardowns in reverse order to provide consistency. | ||
var index = _tearDownMethods.Count; | ||
while (--index >= 0) | ||
RunSetUpOrTearDownMethod(context, _tearDownMethods[index]); | ||
{ | ||
RaiseOneTimeTearDownStarted(context); | ||
try | ||
{ | ||
RunSetUpOrTearDownMethod(context, _tearDownMethods[index]); | ||
} | ||
finally | ||
{ | ||
RaiseOneTimeTearDownFinished(context); | ||
} | ||
} | ||
|
||
// If there are new assertion results here, they are warnings issued | ||
// in teardown. Redo test completion so they are listed properly. | ||
|
@@ -86,6 +118,18 @@ public void RunTearDown(TestExecutionContext context) | |
} | ||
} | ||
|
||
private void RaiseOneTimeTearDownStarted(TestExecutionContext context) | ||
{ | ||
if (context.CurrentTest is not null && context.CurrentTest.IsSuite) | ||
context.ListenerExt?.OneTimeTearDownStarted(context.CurrentTest); | ||
} | ||
|
||
private void RaiseOneTimeTearDownFinished(TestExecutionContext context) | ||
{ | ||
if (context.CurrentTest is not null && context.CurrentTest.IsSuite) | ||
context.ListenerExt?.OneTimeTearDownFinished(context.CurrentTest); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you need this if inside here, you know when you call what you have, you can just pass that along, or even better drop this method all together and just call in in-place. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right. Updated in latest commit. |
||
private void RunSetUpOrTearDownMethod(TestExecutionContext context, IMethodInfo method) | ||
{ | ||
Guard.ArgumentNotAsyncVoid(method.MethodInfo, nameof(method)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Copyright (c) Charlie Poole, Rob Prouse and Contributors. MIT License - see LICENSE.txt | ||
|
||
using System; | ||
using NUnit.Framework.Interfaces; | ||
|
||
namespace NUnit.Framework.Internal.Execution | ||
{ | ||
/// <summary> | ||
/// EventPump pulls extended Event instances out of an EventQueue and sends | ||
/// them to a ITestListenerExt. It is used to send these events back to | ||
/// the client without using the CallContext of the test | ||
/// runner thread. | ||
/// </summary> | ||
public sealed class EventPumpExt : EventPump<ExtendedEvent, ITestListenerExt>, IDisposable | ||
{ | ||
/// <summary> | ||
/// Constructor for extended EventPump | ||
/// </summary> | ||
/// <param name="eventListener">The EventListener to receive events</param> | ||
/// <param name="events">The event queue to pull events from</param> | ||
public EventPumpExt(ITestListenerExt eventListener, EventQueue<ExtendedEvent> events) | ||
: base(eventListener, events, "ExtendedEventPump") | ||
{ | ||
} | ||
} | ||
} |
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.
Mixing behaviour. Avoid this.