W poprzednich postach, używałem domyślnego runner’a dla danego framework’u. Standardowo SpecFlow może być zintegrowany np. z nUnit lub Mbunit. Jeśli tylko to możliwe, polecam zainstalowanie SpecRun, bo jak zaraz pokażę, jest dużo wygodniejszy w przypadku BDD.
Na początku sprawdźmy, co jest wygenerowane dla poniższego testu:
Feature: NewPost
Jakis opis tutaj...
Scenario: Create a new post
Given I have logged into CMS
When I press the create a post button
Then article should be created.
Code-behind:
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by SpecFlow (http://www.specflow.org/).
// SpecFlow Version:1.9.0.77
// SpecFlow Generator Version:1.9.0.0
// Runtime Version:4.0.30319.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
// ------------------------------------------------------------------------------
#region Designer generated code
#pragma warning disable
namespace ConsoleApplication4
{
using TechTalk.SpecFlow;
[System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[NUnit.Framework.TestFixtureAttribute()]
[NUnit.Framework.DescriptionAttribute("NewPost")]
public partial class NewPostFeature
{
private static TechTalk.SpecFlow.ITestRunner testRunner;
#line 1 "NewPost.feature"
#line hidden
[NUnit.Framework.TestFixtureSetUpAttribute()]
public virtual void FeatureSetup()
{
testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner();
TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "NewPost", " Jakis opis tutaj...", ProgrammingLanguage.CSharp, ((string[])(null)));
testRunner.OnFeatureStart(featureInfo);
}
[NUnit.Framework.TestFixtureTearDownAttribute()]
public virtual void FeatureTearDown()
{
testRunner.OnFeatureEnd();
testRunner = null;
}
[NUnit.Framework.SetUpAttribute()]
public virtual void TestInitialize()
{
}
[NUnit.Framework.TearDownAttribute()]
public virtual void ScenarioTearDown()
{
testRunner.OnScenarioEnd();
}
public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo)
{
testRunner.OnScenarioStart(scenarioInfo);
}
public virtual void ScenarioCleanup()
{
testRunner.CollectScenarioErrors();
}
[NUnit.Framework.TestAttribute()]
[NUnit.Framework.DescriptionAttribute("Create a new post")]
public virtual void CreateANewPost()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Create a new post", ((string[])(null)));
#line 3
this.ScenarioSetup(scenarioInfo);
#line 4
testRunner.Given("I have logged into CMS", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 5
testRunner.When("I press the create a post button", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 6
testRunner.Then("article should be created.", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
this.ScenarioCleanup();
}
}
}
#pragma warning restore
#endregion
Z ciekawszych rzeczy, widzimy wyraźnie jak odpalany jest test:
[NUnit.Framework.TestAttribute()]
[NUnit.Framework.DescriptionAttribute("Create a new post")]
public virtual void CreateANewPost()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Create a new post", ((string[])(null)));
#line 3
this.ScenarioSetup(scenarioInfo);
#line 4
testRunner.Given("I have logged into CMS", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 5
testRunner.When("I press the create a post button", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 6
testRunner.Then("article should be created.", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
this.ScenarioCleanup();
}
}
CreateANewPost to zwykły test jednostkowy. Zaletą jest, że nie musimy nic zmieniać na serwerach CI. We wszelkich raportach jednak, będziemy mieli nazwę CreateANewPost, co nie jest zbyt eleganckie. Na przykład, w Test Explorer mamy:

Instalacja SpecRun odbywa się za pomocą NuGet:

W App.Config zobaczymy, że nUnit został zastąpiony SpecRun:
<specFlow>
<!-- For additional details on SpecFlow configuration options see http://go.specflow.org/doc-config -->
<!-- For additional details on SpecFlow configuration options see http://go.specflow.org/doc-config --><!-- use unit test provider SpecRun+NUnit or SpecRun+MsTest for being able to execute the tests with SpecRun and another provider --><unitTestProvider name="SpecRun" /><plugins>
<add name="SpecRun" />
</plugins>
</specFlow>
CodeBehind również zostanie ponownie wygenerowany:
// ------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by SpecFlow (http://www.specflow.org/).
// SpecFlow Version:1.9.0.77
// SpecFlow Generator Version:1.9.0.0
// Runtime Version:4.0.30319.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
// ------------------------------------------------------------------------------
#region Designer generated code
#pragma warning disable
namespace ConsoleApplication4
{
using TechTalk.SpecFlow;
[System.CodeDom.Compiler.GeneratedCodeAttribute("TechTalk.SpecFlow", "1.9.0.77")]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[TechTalk.SpecRun.FeatureAttribute("NewPost", Description=" Jakis opis tutaj...", SourceFile="NewPost.feature", SourceLine=0)]
public partial class NewPostFeature
{
private static TechTalk.SpecFlow.ITestRunner testRunner;
#line 1 "NewPost.feature"
#line hidden
[TechTalk.SpecRun.FeatureInitialize()]
public virtual void FeatureSetup()
{
testRunner = TechTalk.SpecFlow.TestRunnerManager.GetTestRunner();
TechTalk.SpecFlow.FeatureInfo featureInfo = new TechTalk.SpecFlow.FeatureInfo(new System.Globalization.CultureInfo("en-US"), "NewPost", " Jakis opis tutaj...", ProgrammingLanguage.CSharp, ((string[])(null)));
testRunner.OnFeatureStart(featureInfo);
}
[TechTalk.SpecRun.FeatureCleanup()]
public virtual void FeatureTearDown()
{
testRunner.OnFeatureEnd();
testRunner = null;
}
public virtual void TestInitialize()
{
}
[TechTalk.SpecRun.ScenarioCleanup()]
public virtual void ScenarioTearDown()
{
testRunner.OnScenarioEnd();
}
public virtual void ScenarioSetup(TechTalk.SpecFlow.ScenarioInfo scenarioInfo)
{
testRunner.OnScenarioStart(scenarioInfo);
}
public virtual void ScenarioCleanup()
{
testRunner.CollectScenarioErrors();
}
[TechTalk.SpecRun.ScenarioAttribute("Create a new post", SourceLine=2)]
public virtual void CreateANewPost()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Create a new post", ((string[])(null)));
#line 3
this.ScenarioSetup(scenarioInfo);
#line 4
testRunner.Given("I have logged into CMS", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 5
testRunner.When("I press the create a post button", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 6
testRunner.Then("article should be created.", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
this.ScenarioCleanup();
}
[TechTalk.SpecRun.TestRunCleanup()]
public virtual void TestRunCleanup()
{
TechTalk.SpecFlow.TestRunnerManager.GetTestRunner().OnTestRunEnd();
}
}
}
#pragma warning restore
#endregion
Nadal będziemy mieli metodę o nazwie CreateANewPost, ale atrybuty nUnit zostały zastąpione SpecRun:
[TechTalk.SpecRun.ScenarioAttribute("Create a new post", SourceLine=2)]
public virtual void CreateANewPost()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Create a new post", ((string[])(null)));
#line 3
this.ScenarioSetup(scenarioInfo);
#line 4
testRunner.Given("I have logged into CMS", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 5
testRunner.When("I press the create a post button", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 6
testRunner.Then("article should be created.", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
this.ScenarioCleanup();
}
[TechTalk.SpecRun.TestRunCleanup()]
public virtual void TestRunCleanup()
{
TechTalk.SpecFlow.TestRunnerManager.GetTestRunner().OnTestRunEnd();
}
}
Warto upewnić się, że w opcjach (Tools->Options), został faktycznie ustawiony SpecRun:

W Test Explorer mamy teraz dużo czytelniejszą nazwę testu:

Co więcej, po odpaleniu testów, zostanie również wygenerowany raport:

Klikając na nazwę testu, dostaniemy ładne podsumowanie w formacie GWT+kod:

Przeglądając wygenerowane pliki w Solution Explorer, zobaczymy również runtests.cmd. Oczywiście to skrypt odpalający testy:

Jeśli ktoś posiada kilkadziesiąt testów, napisanych w SpecFlow, warto pomyśleć o SpecRun. Oprócz raportu i przejrzystości, zyskamy również możliwość równoległego wykonywania testów, co w przypadku testów systemowych, UI może mieć znaczenie. Niestety za darmo mamy do dyspozycji wyłącznie evaluation mode. Nie jest on limitowany czasowo, ale testy są specjalnie opóźniane co można zaobserwować w Test Explorer.,