Review before/after feature behavior #124
Replies: 4 comments 15 replies
-
My goal would be that we remove the dependency of the test runner's (MsTest, xUnit, NUnit) behavior for the feature hooks. This could be achieved in the following way:
My other idea would be to deprecate this feature. The behavior can be simulated with before/after scenario anyway if necessary. What do you think? |
Beta Was this translation helpful? Give feedback.
-
I understand your concern and I am fine finding another name, but "execution context" is a term used for async and it is not exactly the same as ours (although it is similar) so that would be confusing as well. So if you have a good suggestion please share it. I will also think on that. Unfortunately it is also in the API (like
Code based test tools, you can declare and use fields, so there the class-level hooks can be used to initialize/manipulate these. In Gherkin files you cannot declare variables and the steps are not tightened to a particular feature file, so therefore the usage is more limited and the analogy does not work fully.
Unfortunately this is not enough. The example usages of the before/after feature hooks that I have seen (before parallel times) was that they
I'm not saying that these are good usages, but I have seen people using it this way. For these cases it is not enough if the before feature runs some time (but not necessarily directly) before the first scenario in that feature file, but they need to be run directly before the first scenario. If we want to apply a more relaxed specification (with the once before), we should find a good use-case where this is useful for otherwise we just break the current one without adding a benefit. And this is where I usually get stuck. I can't really find a good use-case for before/after feature that makes sense in modern execution environments (parallel, random order).
I also don't have a strong opinion here. But I think tighter integration with the test-framework is what we have now. We rely on the test-framework infra, applied the workarounds for the unmatching behavior or MsTest and need to store the test runner in a static field to ensure that the feature context and the scenario context are linked (they need to run on the same test runner instance). So the question is that if we keep the tighter integration with the test-framework, how could we improve the current situation? Do you have some ideas? |
Beta Was this translation helpful? Give feedback.
-
Yet another try to describe the before/after feature hook problem after some analysis. Let's start with the specification of the before/after feature hooks: The before/after feature hooks are directly before and after a set of scenarios that are placed and only placed in the same feature file. The hooks can use a storage (FeatureContext) that has the lifetime between the pairing before and after feature hook. A consequence of this is, that the hooks of a particular feature might be executed multiple times (and therefore have multiple FeatureContext) if the scenarios of that feature file are not executed in a single block. This specification requires a local sequence (in order to identify the "set of scenarios that are placed and only placed in the same feature file") and that requires the test-thread (worker) concept at the end. The technical implementation of the feature hooks currently: In order to trigger the before/after feature hooks we used the class-level setup/teardown infrastructure of the test execution frameworks, but this works differently for all 3 frameworks (and probably also based on what parallel model you use with them). One of the key challenges here is how we connect the data that the user saved in the before feature hook to the execution of the scenario. So far (at least for xUnit and MsTest) we used static fields to store this data (by saving the TestRunner into a static field - and TestRunner is unique per test-thread (worker)). (The MsTest "ClassInitialize" method has to be static, so there are not much other options we had.) This technical implementation was good as long as the parallelization was per-class at max, because the test executions of the same class could happily work together on the same static TestRunner. This is obviously not true for test-level parallelization. Side note: The PR #119 of @obligaron uses a separate static field for storing the TestRunner for the before/after feature hook, but this is not sufficient, because is might happen that the data you save into the FeatureContext in the before feature hook is not "visible" during the scenario execution. Maybe too early to conclude here, but I can see the following possible options:
I feel that many of you would tend to go for option 2), and I don't have any problem with that in principle. As far as I understood your main arguments are basically making the Reqnroll codebase and the concepts simpler. I can agree to this, but this is really a tough decision. Not from the technical point of view, but from project & product perspective. My fears about this are the following:
If we want to have simplicity, I would rather go for option 3). That is a clearer situation and - as you have mentioned around the resource management - we can provide examples of pooling, etc. where the same behavior can be achieved without feature hooks. The option 1) can be used to have a quick win: it simplifies the code gen, works with scenario-level parallelism, easy and is not a breaking change. But it keeps the complexity and the "cognitive load" of the test-thread (worker) concept. This can also be seen as a "delaying" option as we can later still go for option 2) or 3). |
Beta Was this translation helpful? Give feedback.
-
This issue seems relevant #189 to the discussion. |
Beta Was this translation helpful? Give feedback.
-
It seems that the current before/after feature behavior has some strange aspects, so this discussion can be used to decide on what we should do with this feature.
Here is the current behavior (discussed at #119, but reviewed).
Note: I will refer often to the term test-thread below. As the name is a bit misleading, here is it's definition: Reqnroll somehow assigns the test execution requests to test-threads. These are basically logical flows (not physical threads!) and Reqnroll ensures that the test execution within the same test-thread are not started parallel. (How it ensures that is out of scope here, and it might depend on the test runner framework, but this is given.) Of course in an unlucky situation it can happen that every scenario will be executed in a unique test-thread, so there is no guarantee that is is "optimal", but usually it is.
The
ITestRunner
interface is the representation of the execution methods of a particular test-thread, so we have as manyTestRunner
instances as the number of test-threads.Currently the hooks are triggered by the class-level setup/teardown methods of the test runners (MsTest, xUnit, NUnit). These methods are static (or have static behavior).
For Reqnroll to behave correctly, the before/after feature hook must be run on the same "test-thread" as the related scenario execution and hooks. (Otherwise some stange null ref exceptions come.)
As the test runners might use different tactics to run the class-level setup/teardown methods and the tests, especially in parallel situations, it is difficult to ensure that Reqnroll assigns these executions to the same test-thread. The way is slightly different for each test runner, but mainly it works in a way that during the class-level setup method it saves the
ITestRunner
to a static field and this is reused for the test execution.MsTest has a special handling of the class-level teardown method, because it might be called delayed, so a scenario of the next class might come faster to the test-thread than the teardown of the previous one in some situations. To handle this, there is a special trick implemented in
TestExecutionEngine.OnFeatureStartAsync
: if the previous one was not finished, we finish that first.So altogether the behavior is pretty complex and it depends heavily on the behavior of the test runners (that might even change).
There are some important consequences of the fact that Reqnroll requires the before/after feature hook to be run on the same test-thread as the related scenarios:
Originally, the before/after feature hooks have been designed for caching some automation infrastructure elements that are expensive to initialize and related to a particular feature file (not easy to find a good example though).
With the modern test runners (parallel, randomized order), this benefit is quite limited, because usually the scenarios of the same feature file are anyway not running in a sequence block. But of course this depends on the runner configuration as well.
//CC @Code-Grump @obligaron
Beta Was this translation helpful? Give feedback.
All reactions