A full demonstration of StoryQ

The code for this lives in the project "StoryQ.Demo":

Story Code:

using System;
using System.Reflection;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using StoryQ;
using StoryQ.Formatting.Parameters;

namespace StoryQ.Demo
{
    [TestClass]
    public class DemoTest
    {

        [TestMethod]
        public void PassingExample()
        {
            new Story("Data Safety")
              .InOrderTo("Keep my data safe")
              .AsA("User")
              .IWant("All credit card numbers to be encrypted")
                  .WithScenario("submitting shopping cart")
                    .Given(IHaveTypedMyCreditCardNumberIntoTheCheckoutPage)
                    .When(IClickThe_Button, "Buy")
                      .And(TheBrowserPostsMyCreditCardNumberOverTheInternet)
                    .Then(TheForm_BePostedOverHttps, true)
                    .ExecuteWithSimpleReport(MethodBase.GetCurrentMethod());

        }

        private void TheForm_BePostedOverHttps([BooleanParameterFormat("should", "should not")]bool isHttps)
        {
        }


        private void TheBrowserPostsMyCreditCardNumberOverTheInternet()
        {
        }

        private void IHaveTypedMyCreditCardNumberIntoTheCheckoutPage()
        {
        }

        [TestMethod]
        public void PendingExample()
        {
            new Story("Data Safety")
                .InOrderTo("Keep my data safe")
                .AsA("User")
                .IWant("All credit card numbers to be encrypted")

                .WithScenario("submitting shopping cart")
                    .Given("I have typed my credit card number into the checkout page")
                    .When(IClickThe_Button, "Buy")
                        .And("the browser posts my credit card number over the internet")
                    .Then("the form should be posted over https")
                .ExecuteWithSimpleReport(MethodBase.GetCurrentMethod());

        }

        [TestMethod]
        public void FailingExample()
        {
            new Story("Data Safety")
                .InOrderTo("Keep my data safe")
                .AsA("User")
                .IWant("All credit card numbers to be encrypted")

                .WithScenario("submitting shopping cart")
                    .Given("I have typed my credit card number into the checkout page")
                    .When(IClickThe_Button, "non existent")
                        .And("the browser posts my credit card number over the internet")
                    .Then("the form should be posted over https", () => { throw new Exception("Oh no again!"); })
            .ExecuteWithSimpleReport(MethodBase.GetCurrentMethod());
        }

        private void IClickThe_Button(string buttonName)
        {
            if (buttonName != "Buy")
            {
                throw new Exception("No button with that name found!");
            }
        }
    }
}

Test runner output:

DemoTest.PassingExample : PassedStory is Data Safety
  In order to Keep my data safe
  As a User
  I want All credit card numbers to be encrypted

      With scenario submitting shopping cart
        Given I have typed my credit card number into the checkout page => Passed
        When I click the Buy button                                     => Passed
          And the browser posts my credit card number over the internet => Passed
        Then the form should be posted over https                       => Passed

DemoTest.PendingExample : IgnoredPending
Story is Data Safety
  In order to Keep my data safe
  As a User
  I want All credit card numbers to be encrypted

      With scenario submitting shopping cart
        Given I have typed my credit card number into the checkout page => Pending !!
        When I click the Buy button                                     => Passed
          And the browser posts my credit card number over the internet => Pending !!
        Then the form should be posted over https                       => Pending !!

DemoTest.FailingExample : FailedStory is Data Safety
  In order to Keep my data safe
  As a User
  I want All credit card numbers to be encrypted

      With scenario submitting shopping cart
        Given I have typed my credit card number into the checkout page => Pending !!
        When I click the non existent button                            => Failed  -> No button with that name found! [1]
          And the browser posts my credit card number over the internet => Pending !!
        Then the form should be posted over https                       => Failed  -> Oh no again! [2]

_______________________
Full exception details:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
[1]: System.Exception: No button with that name found!
   at StoryQ.Demo.DemoTest.IClickThe_Button(String buttonName) in C:\Rob\FlitBasedStoryQ\src\StoryQ.Demo\DemoTest.cs:line 90
   at StoryQ.Condition.<>c__DisplayClass17`1.<When>b__16() in C:\Rob\FlitBasedStoryQ\src\StoryQ\Flit.g.cs:line 517
   at StoryQ.Narrative.Execute() in C:\Rob\FlitBasedStoryQ\src\StoryQ\Narrative.cs:line 68

[2]: System.Exception: Oh no again!
   at StoryQ.Demo.DemoTest.<FailingExample>b__0() in C:\Rob\FlitBasedStoryQ\src\StoryQ.Demo\DemoTest.cs:line 82
   at StoryQ.Narrative.Execute() in C:\Rob\FlitBasedStoryQ\src\StoryQ\Narrative.cs:line 68


System.Exception: No button with that name found!
at StoryQ.Demo.DemoTest.IClickThe_Button(String buttonName) in DemoTest.cs: line 90
at StoryQ.Condition.<>c__DisplayClass17`1.<When>b__16() in Flit.g.cs: line 517
at StoryQ.Narrative.Execute() in Narrative.cs: line 68

-- End of original stack trace, test framework stack trace follows: --

at StoryQ.FragmentBase.Execute(IRenderer[] renderers) in FragmentBase.cs: line 91
at StoryQ.FragmentBase.ExecuteWithSimpleReport(MethodBase currentMethod) in FragmentBase.cs: line 71
at StoryQ.Demo.DemoTest.FailingExample() in DemoTest.cs: line 73 


Report output

You can skip the stage that generates the html report by calling Execute instead of ExecuteWithSimpleReport(MethodBase.GetCurrentMethod())
StoryQFullReport.png

Method-per-scenario

StoryQ offers quite a bit of flexibility in the way it lets you structure your test classes. Some people like to have each scenario within its own test method. The result of each step in the StoryQ interface can be reused, so all you need to do is story the results of your "InOrderTo/AsA/IWant" in a field:

        Feature story = new Story("demonstrating splitting scenarios across methods")
            .InOrderTo("see more granularity in my test runner")
            .AsA("developer")
            .IWant("to share the same story, but have different scenarios in each test method");

        [Test]
        public void PassingExample()
        {
            //these steps all take strings because they are NEVER for execution
            story
                .WithScenario("Passing shopping cart example")
                //these steps all take Methods because they are meant to be exectuable. Steps that don't throw exceptions will pass
                .Given(IHaveTypedMyCreditCardNumberIntoTheCheckoutPage)
                .When(IClickThe_Button, "Buy")
                .And(TheBrowserPostsMyCreditCardNumberOverTheInternet)
                .Then(TheForm_BePostedOverHttps, true).Tag("sprint 1")
                .ExecuteWithReport(MethodBase.GetCurrentMethod());
        }

        [Test]
        public void PendingExample()
        {
            story
                .WithScenario("Pending shopping cart example")
                .Given(IHaveTypedMyCreditCardNumberIntoTheCheckoutPage)
                .When(IClickThe_Button, "Buy")
                .And(TheBrowserPostsMyCreditCardNumberOverTheInternet)

                // because the following method throws NotImplementedException, this step counts as pending:
                .Then(TheForm_BePostedOverHttpsPending, true).Tag("this one ought to pend")
                .ExecuteWithReport(MethodBase.GetCurrentMethod());

        }

Here is a base class you can use for your test methods if you'd like to take things a step further, and have StoryQ figure out your story and scenario names from your class and method names:

    public abstract class StoryTestBase
    {
        private Feature feature;

        protected StoryTestBase()
        {
            Story s = new Story(Uncamel(GetType().Name));
            feature = DescribeStory(s);
        }

        protected abstract Feature DescribeStory(Story story);

        protected Scenario Scenario
        {
            [MethodImpl(MethodImplOptions.NoInlining)]
            get
            {
                return feature.WithScenario(Uncamel(new StackFrame(1).GetMethod().Name));
            }
        }

        private string Uncamel(string methodName)
        {
            return Regex.Replace(methodName, "[A-Z_]", x => " " + x.Value.ToLowerInvariant()).Trim();
        }
    }

Just implement the DescribeFeature() method, and then add test methods. These test methods will just need to hang their "Given/When/Then" off the existing Scenario property

Last edited Jul 2, 2010 at 7:51 AM by robfe, version 5

Comments

robfe Aug 5, 2010 at 11:23 PM 
thank you nuhusky!

nuhusky Aug 5, 2010 at 7:58 PM 
Pending example is outdated. In order for a feature to be marked as "Pending", create a method that will throw a new NotImplementedException.