Specflow step definition inheritance

2020-07-26 02:36发布

问题:

I have ExcelStepDefinition class in which are my steps for excel testing. I have WordStepDefinition class too. Since I have a great number of steps that are same for both classes I made some StepDefinition class which will be base class for those two classes.

In this base class I need to have some args in constructor which depends on what class is instanced (excel or word). I have made all of this, but when I start tests in Nunit it fails with following stack trace:

System.IndexOutOfRangeException : Index was outside the bounds of the array.
TearDown : System.IndexOutOfRangeException : Index was outside the bounds of the array.
at TechTalk.SpecFlow.ScenarioContext.GetBindingInstance(Type bindingType)
at TechTalk.SpecFlow.ScenarioContext.GetBindingInstance(Type bindingType)
at lambda_method(ExecutionScope )
at TechTalk.SpecFlow.Bindings.MethodBinding.InvokeAction(Object[] arguments, ITestTracer testTracer, TimeSpan& duration)
at TechTalk.SpecFlow.TestRunner.FireEvents(BindingEvent bindingEvent, IEnumerable`1 tags)
at TechTalk.SpecFlow.TestRunner.FireScenarioEvents(BindingEvent bindingEvent)
at TechTalk.SpecFlow.TestRunner.OnScenarioStart(ScenarioInfo scenarioInfo)
at ABZ.ExcelTest.DisplayValueOfLinkedItemUsingFormattingRulesDefinedForAGivenLanguageFeature.ScenarioSetup(ScenarioInfo scenarioInfo) in D:\Projects\VS2008\ABZ\ABZ Report Office\ABZ.ExcelTest\ExcelSwitchLanguage.feature.cs:line 0
at ABZ.ExcelTest.DisplayValueOfLinkedItemUsingFormattingRulesDefinedForAGivenLanguageFeature.DisplayFactValueWithFormattingDefinedInSelectedLanguage(String cell, String column, String label, String lang, String cellValue) in d:\Projects\VS2008\ABZ\ABZ Report Office\ABZ.ExcelTest\ExcelSwitchLanguage.feature:line 23
--TearDown
at TechTalk.SpecFlow.ScenarioContext.GetBindingInstance(Type bindingType)
at TechTalk.SpecFlow.ScenarioContext.GetBindingInstance(Type bindingType)
at lambda_method(ExecutionScope )
at TechTalk.SpecFlow.Bindings.MethodBinding.InvokeAction(Object[] arguments, ITestTracer testTracer, TimeSpan& duration)
at TechTalk.SpecFlow.TestRunner.FireEvents(BindingEvent bindingEvent, IEnumerable`1 tags)
at TechTalk.SpecFlow.TestRunner.FireScenarioEvents(BindingEvent bindingEvent)
at TechTalk.SpecFlow.TestRunner.OnScenarioEnd()
at ABZ.ExcelTest.DisplayValueOfLinkedItemUsingFormattingRulesDefinedForAGivenLanguageFeature.ScenarioTearDown() in D:\Projects\VS2008\ABZ\ABZ Report Office\ABZ.ExcelTest\ExcelSwitchLanguage.feature.cs:line 0

Here are following base and derived class(just definition and constructors):

// base class
[Binding]
    public class StepDefinition : Steps
    {
        IOfficeAppDriver officeAppDriver ;
        public StepDefinition(IReportFactoryAddInGuiElements repo, string application)
        {
            officeAppDriver = new OfficeAppDriver(new ReportFactoryOfficeAddInDriver(repo), application);
        }

// derivded one
[Binding]   
    public class ExcelStepDefinition : StepDefinition
    {          
        IExcelDriver excelDriver;
        public ExcelStepDefinition() : base(new Excel2007Repository(), "excel")
        {
            excelDriver = new ExcelDriver(officeAppDriver.ReportFactoryOfficeAddInDriver, factReader);          
        }

Maybe isn't possible to have args in this constructor, i tried without them and it passed.

Do you know how to solve this?

回答1:

-- This is a copy from my answer on the SpecFlow Googe Group --

I think there is a misunderstanding here.

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/ScopedStep/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/cucumber/cucumber/wiki/Feature-Coupled-Steps-(Antipattern)
Step organisation:
https://github.com/cucumber/cucumber/wiki/Step-Organisation



回答2:

I found that this is bug. I contributed patch for this bug to specflow, and it will be in next release. From now inheritance with abstract classes will be possible.



回答3:

I think you are right - in a way.

SpecFlow supports something called Context injection which means that you can inject context into a Binding-class (see this https://github.com/techtalk/SpecFlow/blob/master/Tests/FeatureTests/ContextInjection/FeatureWithADependentContextSteps.cs). You can even have multiple context arguments (https://github.com/techtalk/SpecFlow/blob/master/Tests/FeatureTests/ContextInjection/FeatureWithMultipleContextsSteps.cs).

I think this can have to do with your problem. The thing is that all the contextes that are injected need to be parameterless, i.e. instantiated with a simple new, like this:

var obj = new MyType();

As I understand your class StepDefintion also contains steps? When these are called SpecFlow will try to resolve the injected dependencies to the constructor and fail, if on nothing else on the string which SpecFlow cannot know what to set to.

Maybe you could break out your Office-repository stuff to a separate class that the StepDefinition-classes could take as an argument.

Or you could go with an base-class that doesn't have steps (and hence no [Binding]-attribute).

I haven't checked this to much but this is what I think happens - love to hear you thoughts on this.



标签: specflow