Using discriminated unions to fix Liskov substitution principle violation

As an example of LSP violation, I will consider e-commerce payment system design. One needs to develop generic mechanism for multiple payment service providers (PSP) that are using on checkout step.

From the requirements there are different kinds of PSP in terms of integration. There are PSP that require customer to be redirected to PSP portal page, where customer needs to enter payment information, e.g. credit card. On the other hand, there are PSP that provide possibility to integrate through the API where the response is synchronous, e.g. direct payments.

Knowing that, we could start to design simple interface with two methods for different type of payments, and trying to provide several implementations.


    public interface IPaymentProvider
    {
        PaymentFormDetails GetPaymentFormDetails(Order order);
        PaymentResponse MakeDirectPayment(Order order);
    }

    public class PayPalDirectPaymentProvider : IPaymentProvider
    {
        public PaymentFormDetails GetPaymentFormDetails(Order order)
        {
            throw new NotSupportedException("Not supported for Direct payments. Used only for 3D secure payments.");
        }

        public PaymentResponse MakeDirectPayment(Order order)
        {
           //do some real job here, e.g. call real services etc
            return new PaymentResponse()
            {
                TransactionId = Guid.NewGuid().ToString()
            };
        }
    }

    public class PayPalStandartPaymentProvider : IPaymentProvider
    {
        public PaymentFormDetails GetPaymentFormDetails(Order order)
        {
            return new PaymentFormDetails();
        }

        public PaymentResponse MakeDirectPayment(Order order)
        {
            throw new NotSupportedException("Direct payments are not supported by PayPal direct.");
        }
    }

    public class PaymentResponse
    {
        public string TransactionId { get; set; }
    }

    public class Order
    {
        public decimal Amount { get; set; } 
        public string OrderNumber { get; set; }
    }

    public class PaymentFormDetails
    {
        public string PaymentGatewayUrl { get; set; }
        public string OrderNumber { get; set; }
        public decimal Amount { get; set; }
    }

Unfortunately there is no way for specific payment provider to implement both of methods, so we have to throw NotSupportedException. That is clearly indicates LSP violation.

As we try to adhere to SOLID principles, let’s try to fix the violation.

LSP violation fix#1 – NotSupportedException

First attempt could be to introduce marker interface, it could greatly simplified interface and implementations themselves,
But now calling side should perform downcasting from interface to its implementation in order to correctly interpret the result.

 public interface IPaymentProvider
    {
        IPaymentRequestProcessingResult ProcessPaymentRequest(Order order);
    }

    public interface IPaymentRequestProcessingResult
    {
    }

    public class PaymentResponse : IPaymentRequestProcessingResult
    {
        public string TransactionId { get; set; }
    }

    public class PaymentFormDetails : IPaymentRequestProcessingResult
    {
        public string PaymentGatewayUrl { get; set; }
        public string OrderNumber { get; set; }
        public decimal Amount { get; set; }
    }

    public class PayPalDirectPaymentProvider : IPaymentProvider
    {
        public IPaymentRequestProcessingResult ProcessPaymentRequest(Order order)
        {
            //do some real job here, e.g. call real services etc
            return new PaymentResponse()
            {
                TransactionId = Guid.NewGuid().ToString()
            };
        }
    }

    public class PayPalStandartPaymentProvider : IPaymentProvider
    {
        public IPaymentRequestProcessingResult ProcessPaymentRequest(Order order)
        {
            return new PaymentFormDetails()
            {
                Amount = order.Amount,
                OrderNumber = order.OrderNumber,
                PaymentGatewayUrl = "https://www.paypal.com"
            };
        }
    }

Remove LSP violation#2 – Downcasting and type checking. Introduce discriminated union

Discriminated union is terms came from functional langauges, particularly from F#. Base on Microsoft documentation

Discriminated unions provide support for values that can be one of a number of named cases, possibly each with different values and types. Discriminated unions are useful for heterogeneous data;

https://docs.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/discriminated-unions

Discriminated unions and not supported out of the box in C#, but there are several ready to use implementations over the Internet. I used implementation from here http://pastebin.com/EEdvVh2R

public sealed class Union2<A, B>
    {
        readonly A Item1;
        readonly B Item2;
        int tag;

        public Union2(A item) { Item1 = item; tag = 0; }
        public Union2(B item) { Item2 = item; tag = 1; }

        public T Match<T>(Func<A, T> f, Func<B, T> g)
        {
            switch (tag)
            {
                case 0: return f(Item1);
                case 1: return g(Item2);
                default: throw new Exception("Unrecognized tag value: " + tag);
            }
        }
    }

Than we will remove from

  public interface IPaymentProvider
    {
        PaymentRequestProcessingResult ProcessPaymentRequest(Order order);
    }

    public class PaymentRequestProcessingResult: Union2<PaymentResponse, PaymentFormDetails>
    {
        public PaymentRequestProcessingResult(PaymentResponse item) : base(item)
        {
        }

        public PaymentRequestProcessingResult(PaymentFormDetails item) : base(item)
        {
        }
    }

And implementations now:

 public class PayPalDirectPaymentProvider : IPaymentProvider
    {
        public PaymentRequestProcessingResult ProcessPaymentRequest(Order order)
        {
            //do some real job here, e.g. call real services etc
            return new PaymentRequestProcessingResult(
                new PaymentResponse()
                {
                    TransactionId = Guid.NewGuid().ToString()
                });
        }
    }

    public class PayPalStandartPaymentProvider : IPaymentProvider
    {
        public PaymentRequestProcessingResult ProcessPaymentRequest(Order order)
        {
            return new PaymentRequestProcessingResult(new PaymentFormDetails()
            {
                Amount = order.Amount,
                OrderNumber = order.OrderNumber,
                PaymentGatewayUrl = "https://www.paypal.com"
            });
        }
    }

And finally usage,

public static void Main()
        {
            var order = new Order
            {
                Amount = 20m,
                OrderNumber = "00001"
            };

            Console.WriteLine("Select payment provider (1 - PayPal Direct, 2 - PayPal standart):");
            var pl = int.Parse(Console.ReadLine());
            var paymentProvider = GetPaymentProvider(pl);

            var res = paymentProvider.ProcessPaymentRequest(order);

            Console.WriteLine(res.Match(
                r => "Direct payment succeed. Transaction ID:" + r.TransactionId,
                fd => "Standart payment providers. Redirecting to PSP. Paymew gateway url:" + fd.PaymentGatewayUrl));

            Console.Read();
        }

ProcessPaymentRequest method results are clearly defined and allow to use them in type-safe manner.

Review

Let’s review what we achieved so far:

  • Removed throwing NotSupportedException
  • Eliminate downcasting to specific class types
  • Enable type-safe way of working with possible payment processing results
Posted in object-oriented-principles, software-patterns | Leave a comment

FAKE.IIS Add application to existing IIS site

Recently I had a task to change IIS application path in existing IIS site.
That was repetitive operation so I decided to write script for it.

I recently began to study F# and function programming so decided to try FAKE.
There is also cool explanation of FAKE here: http://blog.tamizhvendan.in/blog/2015/01/06/step-4-build-automation-using-fake/.

I have easily find IIS() function, that allows to get all the things ready at once: setup site/application pool/applications.
IIS() works in way that it will not create new site or application pool, if the same already exists.
But it will always add new version of site bindings even if the same is already exists. It makes IIS site stopped and there extra binding are to be taken care manually.

That said, I created interactive script, *.fsx file.
It is “hardcoded” and not error safe, but contains some parts, that I spent time struggling with, like figuring out where to take instance of ServerManager to create application etc.


#r @"packages/FAKE/tools/Fake.IIS.dll"
#r @"packages/FAKE/tools/FakeLib.dll"
#r @"packages/FAKE/tools/Microsoft.Web.Administration.dll"

open System
open System.Security.Principal
open Microsoft.Web
open Microsoft.Web.Administration
open Fake
open Fake.IISHelper

let siteName = "site"
let vdir = "/dir"
let path = @"C:\IIS\site\dir"

let addNewApp vdir path = 
    use mgr = new ServerManager()
    let site = mgr.Sites.[siteName]
    let app = Application vdir path site mgr
    commit mgr
Posted in F# | Tagged , | Leave a comment

Tools and libraries to consider when writing autotests

Below is a summary of tools/libraries I have used to build large maintainable suite of auto tests on large web project.

Test Framework

XUnit

xUnit.net is a developer testing framework, built to support Test Driven Development, with a design goal of extreme simplicity and alignment with framework features.

Test setup

AutoFixture

AutoFixture makes it easier for developers to do Test-Driven Development by automating non-relevant Test Fixture Setup, allowing the Test Developer to focus on the essentials of each test case. (copied from nuget package description)

I could not stress enough how useful the library is. It allows to greatly decrease the lines of code for your test suits and make tests more readable.

AutoFixture.XUnit2

By leveraging the data theory feature of xUnit.net, this extension turns AutoFixture into a declarative framework for writing unit tests. In many ways it becomes a unit testing DSL (Domain Specific Language).

Test assert

FluentAssertions

A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit test. (copied from nuget package description)

It providesa a lot more possibilities to assert the test outcome than standard test frameworks do.

ApprovalTests

A picture’s worth a 1000 tests. Unit testing asserts can be difficult to use. Approval tests simplify this by taking a snapshot of the results, and confirming that they have not changed.

Posted in Unit test | Tagged | Leave a comment

How to identify process ID in PerfMon

Last time I need to troubleshoot memory leak in IIS process hosted on productions server I decided to start with PerfMon and got stuck on Add Counters screen, where I saw seventeen w3wp (IIS process name) processes up to w3wp#17.

Here for the demo I used two instances of the same console application. It is enough to get the idea.

Add Counters

It occurs that there are some advice how to identify PID of the process, most of them about changing registry settings, and it  does not always help.

Others suggest to use third-party tools to match PerfMon process identification with real PID.

I used another solution. Add another performance counter, like on the screen below.

Add Counters 2016-03-22 22.59.16

Now I have possibility to identify that process I was interested in was #1.

Performance Monitor 2016-03-22 23.03.39

Happy using PerfMon. It is amazing!

Posted in SysInternals | Tagged | Leave a comment

Software developer productivity

From the starting of my software developer career I wanted to be as productive as possible. And the worst feeling I could have, was when I  was working on a problem that I have already solved before, and need to refresh my memory to solve it again with applying efforts again.

Most of us (so did I) are quite lazy in applying such patterns. When the problem first occurs we usually do not want to apply patterns that would save us time in future. Until it really hurts 🙂 Then we really start working on solution.

The list of things I was not doing from the start and am sorry about that :

Using browser bookmarks

It really helps to keep already found answers close to you. So you are not obliged to search again for information that already was found. Especially when there is browser bookmark is synchronised within multiple client devices (like in Chrome). When there will be more than *some figure*, you could reorganise your collection of bookmarks into folders etc.

Keep your source code on github

Previously I kept my source code, that I often use in Dropbox. But using github makes it much easier. Things I put on github are for instance: project skeleton of unit tests project with all its packages; or skeleton of reactjs project with all required dependencies. So I do not waste time setup it all over again. I download it from well-known location and start doing valuable things.

Don’t mess around your folder structure

Formerly any file I downloaded I stored in “Downloads” folder. It is obvious that since some time I barely could found any stuff there. That said I want to say that folders structure is important and keeping information in its place is important too.

When I think about getting things fast, I always remember the comparison of searching in array of unordered items and search in search binary tree. Search in sorted binary tree is much faster. The same principle applies to many aspects of the life, including the information organisation.

Posted in Uncategorized | Leave a comment

Better handling ‘NoSuchElementException’ using simple remote web driver wrapper

While implementing test scenarios, I often write some element selectors wrong and finally get ‘NoSuchElementException’ when run the test. Unfortunately it does not specify, which selector caused the problem. Its message only says the following:

OpenQA.Selenium.NoSuchElementException: no such element
(Session info: chrome=45.0.2454.99)
(Driver info: chromedriver=2.10.267521,platform=Windows NT 6.3 x86_64)

So I need to waste time to locate exact failed selector using debugging…
Wouldn’t be nicer to have not found selector specified right in exception message? So it would be possible to troubleshoot the problems easily.
Ok, let’s  add it!

First prepare the environment. I use Visual studio 2013, update 5.
1. Create test project. Ensure that you have NUnit runner extension installed in VS.
2. Install following packages:

<packages>
  <package id="CuttingEdge.Conditions" version="1.2.0.0" targetFramework="net45" />
  <package id="NUnit" version="2.6.4" targetFramework="net45" />
  <package id="Selenium.WebDriver" version="2.47.0" targetFramework="net45" />
  <package id="WebDriverChromeDriver" version="2.10" targetFramework="net45" />
</packages>

3. Modify UnitTest1 class to use NUnit.

 [TestFixture]
    public class UnitTest1
    {
        [Test]
        public void TestMethod1()
        {
            using (var myDriver = new ExceptioHandleDriver(new ChromeDriver(new ChromeOptions())))
            {
               var element = myDriver
                    .GoTo("https://wordpress.com/wp-login.php")
                    .FindElement(By.Id("unique-id"));

                Assert.IsTrue(element != null);
            }
        }
    }

4. Add driver implementation. Driver is wrapper around RemoteWebDriver and implements only few functionality, just to fit the demonstration purpose:

 public class ExceptioHandleDriver : IDisposable
    {
        private readonly RemoteWebDriver _innerDriver;

        public ExceptioHandleDriver(RemoteWebDriver innerDriver)
        {
            Condition.Requires(innerDriver).IsNotNull();
            _innerDriver = innerDriver;
        }

        public ExceptioHandleDriver GoTo(string url)
        {
            _innerDriver.Navigate().GoToUrl(url);
            return this;
        }

        public IWebElement FindElement(By by)
        {
            try
            {
                return _innerDriver.FindElement(@by);
            }
            catch (NoSuchElementException e)
            {
                throw new NoSuchElementException(string.Format("Element '{0}' was found.", @by), e);
            }
        }

        #region Dispose pattern implementation

        ~ExceptioHandleDriver()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_innerDriver != null)
                {
                    _innerDriver.Dispose();
                }
            }
        }

        #endregion
    }

Now when we run the test (it assumes to be failed, do not worry), we could happily see better error representation:

Result Message:
OpenQA.Selenium.NoSuchElementException : Element ‘By.Id: unique-id’ was found.
—-> OpenQA.Selenium.NoSuchElementException : no such element
(Session info: chrome=45.0.2454.99)
(Driver info: chromedriver=2.10.267521,platform=Windows NT 6.3 x86_64)
Result StackTrace:
….

Posted in Selenium | Tagged | Leave a comment

MVC configuration – ‘path’ attribute inside ‘location’ section

This is post about specifying path attribute of location section in Web.config file. If you are not familiar with this section, go here

Examples in above article concern about ASP.NET where specifying path like ‘Page1.aspx’ is enough.
When I started mapping this knowledge to ASP.NET MVC, first time I failed.
As a test environment I used default ASP.NET MVC project (not empty) to have ready to work profject structure. What I wanted is to override key “test1” for my Index view. Index view was rendered through the Index action of Home controller. I started with following:

  <location allowOverride="false" path="Index">
    <appSettings>
      <add key="test1" value="test4" />
    </appSettings>
  </location>

Unfortunately it did not work.

I found out that in MVCC, where routing module does all work, we need to specify full path to the action, not just the ‘action’ name. Like following (working code):

  <location allowOverride="false" path="Home/Index">

Note here: specifying the path started from ~/ (root in MVC) is not allowed here and will not be resolved to the proper path!

Posted in ASP.NET | Leave a comment

Add Visual Studio intellisense support for custom configuration section

On my main project we use heavily the awesome log4net framework. It could be quite easily configured but for some advanced configuration I need to constantly check the documentation. I personally prefer to have all required knowledge on how write configuration in Visual Studio.
Fortunately VS allows to add intellisense support for custom configuration section (like log4net or whatever you want).

As configuration file is still XML, what you need is to provide the XSD schema to the editor. And you got intellisense do hard work for you. See how:

  1. Find configuration file.
  2. Open file for edit. Its properties window should be seen in the bottom right corner of the VS editor (default settings). 
  3. Click on ‘Schemas’ row:
    Edit XSD schemas for XML
  4. You could see tons of already included XSD schemas that makes editing configuration file so playful! Click on “Add” to choose schema. I downloaded log4net XSD here beforehand.
    Manage XML Schemas
    Then you will see new schema now included into the configuration file schemas.
    Log4net XSD successfully added
  5. The last this is to test it. Start typing app.
    Intellisense power

Looks like it work now. Great!

So this was a tip on how to include custom configuration section intellisense support.

Image | Posted on by | 2 Comments

Reading large XML files. Performance comparison of different methods

For really large XML documents larger than 100 MB the idea to load entire document in memory could be not the best choice. Now let’s consider the other ways for reading those large files.

For our test lab we will create console project. Now let’s add simple domain class:

public class CustomDiscount
    {
        public string Name { get; set; }
        public int Type { get; set; }
        public decimal Offer { get; set; }
    }

In order to get test results let’s first create target Xml file and fill it with some discounts using following method:

private static void FillLargeXml(string filePath, int elementsCount)
{
    var random = new Random();
    var discounts = new CustomDiscount[elementsCount];
    for (int i = 0; i &lt; discounts.Length; i++)
    {
        discounts[i] = new CustomDiscount()
            {
                Offer = (decimal) (random.NextDouble()*200),
                Type = random.Next(1, 2),
                Name = Guid.NewGuid().ToString(&quot;N&quot;)
            };
    }

    var root = new XElement(&quot;root&quot;);
    Array.ForEach(discounts, d =&gt; root.Add(new XElement(&quot;CustomDiscount&quot;,
        new XElement(&quot;Name&quot;, d.Name),
        new XElement(&quot;Type&quot;, d.Type),
        new XElement(&quot;Offer&quot;, d.Offer))));
    root.Save(filePath);
}

Parsing large xml using XmlSerialiser:

private static CustomDiscount[] DeserialiseLargeXml(string filePath)
{
    var xs = new XmlSerializer(typeof(CustomDiscountRoot));
    var res = (CustomDiscountRoot)xs.Deserialize(XmlReader.Create(filePath));
    return res.Discounts;
}

CustomDiscountRoot class:

[XmlRoot(&quot;root&quot;)]
public class CustomDiscountRoot
{
    [XmlArrayItem(&quot;CustomDiscount&quot;)]
    public CustomDiscount[] Discounts { get; set; }
}

Parsing with LinqToXml

private static CustomDiscount[] ParseLargeXmlWitmXLinq(string filePath)
{
    var xElements = XElement.Load(filePath).Elements(&quot;CustomDiscount&quot;).ToArray();
    return xElements.Select(ConvertFrom).ToArray();
}

private static CustomDiscount ConvertFrom(XElement e)
{
    var value = e.Element(&quot;Name&quot;).Value;
    var s = e.Element(&quot;Type&quot;).Value;
    var value1 = e.Element(&quot;Offer&quot;).Value;

    return new CustomDiscount()
    {
        Name = value,
        Type = int.Parse(s),
        Offer = decimal.Parse(value1, CultureInfo.InvariantCulture)
    };
}

Parse using XmlReader:

private static CustomDiscount[] ParseLargeXmlWithXmlReader(string filePath)
{
    var discounts = new List&lt;CustomDiscount&gt;();
    using (var reader = XmlReader.Create(filePath))
    {
        while (reader.ReadToFollowing(&quot;CustomDiscount&quot;))
        {
            reader.ReadToFollowing(&quot;Name&quot;);
            string name = reader.ReadElementContentAsString();

            reader.ReadToFollowing(&quot;Type&quot;);
            int type = reader.ReadElementContentAsInt();

            reader.ReadToFollowing(&quot;Offer&quot;);
            decimal offer = reader.ReadElementContentAsDecimal();
            discounts.Add(new CustomDiscount
                {
                    Name = name,
                    Type = type,
                    Offer = offer
                });
        }
    }
    return discounts.ToArray();
}

Now let’s see the performance time (in milliseconds) comparison for different approaches of dealing with large XML docs.

Objects in XML 100.000 200.000 400.000 800.000 1.600.000
XmlReader 738 809 2294 3677 8379
LinqToXml 1092 1800 2581 5245 14888
XmlSerialiser 574 432 733 2962 3605

*Time counters were specified in milliseconds
*Each computation was repeated for 5 times
*.NET framework v4.0

It is easy to see that the fastest approach to parse large XML documents is using XmlSerialiser class which is also the most elegant and concise. It uses instance of XmlReader as input parameter so does not load entire document into memory.
The second for performance is XmlReader. Theoretically it should have the same performance as XmlSerialiser.Deserialise() method with more control over the process of reading the document. ( I really like its syntax now, it is much cleaner and handy in comparison for instance with OpenXmlReader that is used for reading Excel documents without its loading in memory)
The “looser” is using LinqToXml. It loads the entire xml into memory so that it document is really huge that OutOfMemory exception is possible there. But on not really large amount of data (something below 1.000.000 entire objects packed inside xml) this is neglectible.

Summary

For extremely large documents use either XmlSerialiser.Deserialise() method if you need the entire objects from xml, or use XmlReader if you need only part of the data.  It will prevent you from OutOfMemory exception and performance bottlenecks.

Posted in XML | Tagged | Leave a comment

Handling test data clean up in SpecFlow

Overview

This post describes how to handle scenario level clean-up in a predictable way.

Note: This blog post was originally written for SpecFlow 1.9 version.
In SpecFlow 2+ version you should use scenario scoped bindings  with  [BeforeScenario] execution order can be now controlled with an Order parameter.

Writing advanced UI tests with SpecFlow and Selenium often requires some test data to be created. During the tests execution this test data is changed. One of requirements for a good test is repeatedness. Test have to be executed multiple  times and should complete with the same results.

So we need some sort of clean-up procedures for our tests in order to satisfy above requirement. Let’s consider what SpecFlow could offer us in this case.

Hooks

Hook is the statement of code that is executed after or before particular event. Here are the most useful from my mind:

  1. [AfterScenario(“MyTag”)] – code is executed after all the scenarios marked with @MyTag.
  2. [BeforeScenario(“MyTag”)] – code is executed before all the scenarios marked with @MyTag

You could find more info about SpecFlow hooks on the project wiki here.

Let’s consider very simple scenario, where we need some sort of clean-up procedures:

Scenario: Price is updated
Given: Price for product foo is 10$
When: Price is changed to 15$
Then: Product foo has price 15$ on the site

What we could expect when test is finished? That the price for product ‘foo’ is 15$.
But what happened when we try to repeat this test? Sure it will fail as now price is 15$. What we need is to organise cleanup action after test execution, so user will be bound to the ABC again.

How could we achieve such behaviour? Well using the hooks in such way.

[AfterScenario]
public void AfterScenario()
{
    var title = ScenarioContext.Current.ScenarioInfo.Title;
    if (title == "Price is updated")
    {
        //do some actions to restore previous state
    }
    if (title == "SomeOtherTest")
    {
        //do some clean-up for that
    }
}

Usually logic inside if statement might be quite complex, as it requires some action to do and reuse the our Page Objects. So AfterScenario method become bloated and hard to read.

Let’s replace above statement with 2 separate methods and mark with our custom attribute:

[ScenarioCleanup("Price is updated")]
public void ResetPrice()
{
    //do some actions to reset the price
}

[ScenarioCleanup("SomeOtherTest")]
public void CleanupAfterOtherTest()
{
    //do some other actions
}

Code for newly created attribute;

public class ScenarioCleanup : Attribute
{
    public string ScenarioTitle { get; set; }

    public ScenarioCleanup(string scenarioTitle)
    {
        ScenarioTitle = scenarioTitle;
    }
}

And finally the code where all parts are gathered;

[Binding]
public class TestBehaviour
{
    public void BeforeTestRun()
    {
        _teardownActions = BuildDictionary(this.GetType());
    }

    [AfterScenario()]
    public void AfterScenario()
    {
        _commonTestBehaviour.ResolveScenarioCleanup(ScenarioContext.Current.ScenarioInfo.Title, this);
        //some other code, like disposing selenium driver or so
    }

    [ScenarioCleanup("User changes company")]
    public void ResetBindedUser()
    {
        //do some actions to rebound user to the initial company
    }

    public void ResolveScenarioCleanup(string title, object instance)
    {
        MethodInfo cleanupMethod;
        if (_teardownActions.TryGetValue(title, out cleanupMethod))
        {
            cleanupMethod.Invoke(instance, null);
        }
    }

    private Dictionary<string, MethodInfo> _teardownActions;

    private Dictionary<string, MethodInfo> BuildDictionary(Type type)
    {
        var withAttrs = type.GetMethods()
            .Where(m => m.GetCustomAttributes(typeof(ScenarioCleanup), false).Length > 0)
            .ToDictionary(key => ((ScenarioCleanup[])key.GetCustomAttributes(typeof(ScenarioCleanup), false))[0].ScenarioTitle, val => val);
        return withAttrs;
    }
}

In short:

  1. Before TestRun via reflection all the cleanup methods init.
  2. When scenario is executed, AfterScenario() method runs
  3. In AfterScenario method, corresponding clean-up method is resolved and executed
Posted in Selenium, SpecFlow | Tagged , , | Leave a comment