I think it does not make sense to use inheritance the way you propose it.
SpecFlow works quite different compared to traditional xUnit Test frameworks.
In SpecFlow step-definitions are global. Step definitions do not have
to reside in a base class to be usable from a subclass. Step
definitions are not comparabe to methods in test fixtures from xUnit
frameworks.
Generally all classes that are decorated with the [Binding] attribute
are scanned by SpecFlow to discover step definitions.
All the step definitions that are found are availabe at runtime when
SpecFlow parses and executes features.
For SpecFlow to find a matching step definition it is not relevant in
which class the step definitiond is definied.
However when SpecFlow has found a matching step definition, it needs
to be able to instantiate the class on which it is defined. Therefore
classes that contain step definitions must not be abstract.
The instance is primarily used to pass state between related step
definitions (however there are other possibilities to pass state).
The same is true for hooks (Before .../ After ...): They are global,
at runtime it does not matter on which class they are defined.
The above is the general concept.
Things get a bit more complicated, when we start considering scoped steps:
Step definitions can be scoped to tags and scenarios, hooks can be
scoped to tags.
Examples:
https://github.com/techtalk/SpecFlow/blob/master/Tests/FeatureTests/ScopedSteps/ScopedStepsBindings.cs
https://github.com/techtalk/SpecFlow-Examples/blob/master/ASP.NET-MVC/BookShop/BookShop.AcceptanceTests.Selenium/Support/SeleniumSupport.cs
Read more here:
http://groups.google.com/group/specflow/browse_frm/thread/080c531cb17c86e0/5350665da2544871?#5350665da2544871
Read more on the Cucumber wiki.
About global steps:
https://github.com/aslakhellesoy/cucumber/wiki/Feature-Coupled-Steps-%28Antipattern%29
Step organisation:
https://github.com/aslakhellesoy/cucumber/wiki/Step-Organisation
--
mail: jonas...@gmail.com
web: www.jonasbandi.net
blog: blog.jonasbandi.net
twitter: twitter.com/jbandi
If SpecFlow wants to execute a step definition at runtime, it needs to
instantiate an object that contains this step definition.
Currently SpecFlow tries to instantiate the class that defines the
step definition.
Currently SpecFlow has no way to find out which subclass it should
instantiate when running a scenario.
There is no relation beween scenarios and step definitions or the
classes that implement step definitions. Step definitions are global
and not feature or scenario coupled.
So as a rule: Step definitions must be declared in classes that will
get instantiated at runtime.
Steps can be shared for both of you plugins without using inheritance.
For instance you can implement three classes:
SharedSteps.cs
ExcelSteps.cs
WordSteps.cs
SpecFlow will discover the steps at runtime. For the Excel scenarios
it will use the ExcelSteps and the SharedSteps for Word scenarios it
will use WordSteps and SharedSteps. But it will have all steps always
available.
If you need the same steps (same regexp binding) but with different
meaning depending on the context you are using them then there are two
different solutions:
- If you have two independent projects for Word and Excel, you could
use three different assemblies that contain the steps. In the
Word-solution you would only reference the assemblies containting the
SharedSteps and the WordSteps.
- You can use scoped steps:
https://github.com/techtalk/SpecFlow/blob/master/Tests/FeatureTests/ScopedSteps/ScopedStepsBindings.cs
As the above example shows you can scope a single step definition or a
whole class containing step definitions to tags, scenarios or
features.
At runtime SpecFlow will then choose the step definition that matches
best to your scenario.
As seen in the example above this is accomplished by using tags
(@excel, @word) on your scenarios.
Then use for example step bindings like this:
[StepScope(Tag = "excel")]
[StepScope(Feature = "Excel plugin feature bla bla")]
[StepScope(Scenario = "Word is automatically completing sentences")]
[StepScope(Tag = "excle", Scenario = "Excel plugin feature bla bla")]
If you need shared functionality, you can place it in a abstract
base-class. But this base-class cannot contain step definition itself.
You can also inject common used instances as context into step classes
with dependency injection:
https://github.com/techtalk/SpecFlow/blob/master/Tests/FeatureTests/ContextInjection/FeatureWithASingleContextSteps.cs
--
mail: jonas...@gmail.com
This is exactly what we discovered as well by looking at the code in
BindingRegistry and MethodBinding classes. However, thanks to the good
code design, this could changed easily: in
BindingRegistry.BuildBindingsFromType(Type type) one can check if the
method declaring type is the same as the type discovered via Binding
attribute, and if not to instantiate the discovered type in
MethodBinding.CreateActionDelegate() instead. What do you think about
this? If you do not see any problem with this, we would be keen to
contribute the patch, if you agree - of course.
> There is no relation beween scenarios and step definitions or the
> classes that implement step definitions. Step definitions are global
> and not feature or scenario coupled.
This is fine and understandable, but if developer introduces some
orthogonal coupling (i.e. steps spread in a base abstract class and
inherited class), SpecFlow could detect such a situation (see above)
and instantiate at the runtime only classes that are decorated with
Binding attribute (in our case - this is only the inherited class).
Otherwise, what is then the purpose of the Binding attribute, if
SpecFlow tries to instantiate classes containing some SpecFlow steps,
but are not decorated with the Binding attribute?
> If you need the same steps (same regexp binding) but with different
> meaning depending on the context you are using them then there are two
> different solutions:
> - If you have two independent projects for Word and Excel, you could
> use three different assemblies that contain the steps. In the
> Word-solution you would only reference the assemblies containting the
> SharedSteps and the WordSteps.
This is the approach we tend to use, but our shared and word/excel
steps need to use the same instance of Word/Excel driver class. Here
is the class hierarchy:
interface IOfficeAppDriver {
}
interface IExcelDriver: IOfficeAppDriver {
}
Now, the shared steps need a reference to an instance of
IOfficeAppDriver, whereas Excel steps need reference to an instance of
IExcelDriver. Furthermore, we have different implementations of
IExcelDriver, depending on the Excel version itself, and we wanted to
inject of create it in the constructor of Excel step class. Hence, we
needed to define shared step class as abstract and to inherit
Excel/Word step class out of it.
Speaking about dependency injection, could you please clarify what is
going on in the following situation:
[Binding]
class StepDefinition1 {
public StepDefinition1(A a) {
}
}
[Binding]
class StepDefinition2 {
public StepDefinition2(A a) {
}
}
Do both step definition classes get a shared instance of type A, or
each of them gets its own instance?
Best,
Predrag
Dependency injection:
This is currently solved with a simple Dictionary<Type, object> that
is instantiated per scenario.
So, yes: In your case both step definition classes get the same
instance injected during the same scenario.
Thanks for your help.
jonas
2011/3/21 Predrag Knežević <ped...@gmail.com>:
--
Did you take this into account? (its not much) ... also if you have
any tips, please update this wiki ...
--
mail: jonas...@gmail.com
On Wed, Mar 23, 2011 at 6:15 PM, Vajda <vlada...@gmail.com> wrote:
> I solved it, I had Wix 3.6 on my machine, when I replaced it with wix
> 3.5 it built like magic. :)
>
> regards,
>
> Vajda
--
mail: jonas...@gmail.com
--
You are right - having several subclases for a baseclass would make
you troubles, because some steps would appear multiple times. However,
this is not the scenario we are looking for: it is enough for us to
have single subclass (extended from a baseclass) defining all needed
steps within a test project.
Cheers,
Predrag
I tried to formulate the feature that would describe the behavior of specflow:
Feature: Using inherited step definitions
In order to use inheritance in my step definition classes
As a programmer that implements step definitions
I want SpecFlow to work with inherited steps in an intuitive way
Scenario: Abstract baseclass1 with binding attribute, no subclass
Then SpecFlow should fail and report an error
Scenario: Abstract baseclass2 with binding attribute, concrete
subclass3 with binding attribute
Then SpecFlow should instantiate subclass3
And call the base step on that instance3
And call the sub step on that instance3
Scenario: Abstract baseclass4 with no binding attribute, concrete
subclass5 with binding attribute
Then SpecFlow should instantiate subclass5
And call the base step on that instance5
And call the sub step on that instance5
Scenario: Concrete baseclass6 with binding attribute, concrete
subclass7 with binding attribute
Then SpecFlow should instantiate subclass7
And call the base step on that instance7
And call the sub step on that instance7
Scenario: Concrete baseclass8 with no binding attribute, concrete
subclass9 with binding attribute
Then SpecFlow should instantiate subclass9
And call the base step on that instance9
And call the sub step on that instance9
Scenario: Concrete baseclass10 with binding attribute, first
subclass11 with binding attribute, second subclass12 of first
subclass11 with binding attribute
Then SpecFlow should instantiate subclass12
And call the base step on that instance12
And call the sub step on that instance12
Scenario: Concrete baseclass13 with binding attribute, first
subclass14 of baseclass13 with binding attribute, second baseclass13
of with binding attribute
Then SpecFlow should fail and report an AmbiguousMatchError
What do you think?
jonas
2011/3/28 Predrag Knežević <ped...@gmail.com>:
--
Yes, I think it is the good idea too. The formulated scenarios make
all sense, but some of them are perhaps harder to support :)
Cheers,
Predrag
In the next release we could then support the above scenarios.
2011/3/29 Predrag Knežević <ped...@gmail.com>:
--