A Behaviour Driven Testing Framework


Keywords
bdd, given, tdd, test, testing, then, when, test-automation, tests
License
Apache-2.0
Install
Install-Package BDTest -Version 1.0.0

Documentation

BDTest

A testing and reporting framework for .NET

nuget Codacy Badge CodeFactor Nuget

Support

If you like using BDTest, consider buying me a coffee.

Buy Me A Coffee

Wiki

Please view the wiki to see how to use BDTest and how to get setup: https://github.com/thomhurst/BDTest/wiki

What is it?

BDTest is a testing and reporting framework focusing on 'Behaviour Driven Testing' ideologies. It's a way to structure and run your tests so that:

  • They are clear, concise and easy to understand
  • They are easy to run and debug
  • They don't share state. They are independent, side-effect free and able to run in parallel, meaning faster and more stable tests!
  • They translate into business criteria and focus on how the system under test would be used by an end user and their behaviours
  • You have an easy way to see test results and share them with your team or business

BDTest can be used standalone, or alongside an existing test running framework. For best results, I recommend NUnit, and installing the BDTest.NUnit package alongside the core BDTest package.

Why?

I was originally inspired to build BDTest due to SpecFlow. My team at work used SpecFlow (as does much of the .NET based industry) for their tests when I joined, and upon using it, realised I really didn't enjoy using it and found it to be difficult to work with, finnicky and long-winded. It turned out, my team also really disliked it, so I thought I'd change things.

Think of BDTest as a pure code-based alternative to SpecFlow.

What's the benefit over SpecFlow?

  • Passing in more complicated arguments to tests. Enums, models, etc.
  • You get the benefit of separating steps into Given, When, Then. However you can use and re-use existing steps without redefining or attributing them as Given step, or a When step, etc.
  • No auto-generated code. All code is your code! So you shouldn't see any weirdness. This also means no extra weird compilation steps and bringing in specific test compilers/runners.
  • Easier to debug. Test has failed and thrown an exception? The stacktrace should take you straight to where it happened. No being taken to strange auto-generated classes that you don't care about!
  • Step into all your code. I used Rider as my IDE, which didn't support SpecFlow, so I couldn't step into my test methods. I had to do a painful Find All! Now as it's all code, I can step straight into my test methods.
  • No learning and maintaining separate Gherkin files!

But non-technical team members can write tests using SpecFlow

  • Does that EVER actually happen though?
  • They can only use the high level methods. Any new methods, a developer will still need to code the steps for them. If they can only use the high level methods, make the method names human readable, and they can still use them!

I need reports that translates to business acceptance criteria

  • BDTest does that!
  • BDTest produces you a clear, easy to understand report, showing what failed and what passed, with output and exceptions from tests automatically captured
  • Mark your classes and methods with [StoryText], [ScenarioText], and [StepText] attributes, providing the output you want, and your reports will produce clean text output
  • If you don't mark your methods with attributes, BDTest will automatically convert the method name to text (separating words by capitalisation or underscores)
  • If you use the report server, you can just send the relevant party a URL directly to your test report!

Where can I use it?

BDTest is written in .NET Standard, so you can use it in either .NET Framework or .NET Core

How do I get started?

View the wiki to see how to use BDTest and how to get setup: https://github.com/thomhurst/BDTest/wiki

Example Test

namespace BDTest.Example
{
    [Story(AsA = "BDTest author",
        IWant = "to show an example of how to use the framework",
        SoThat = "people can get set up easier")]
    public class ExampleTest : NUnitBDTestContext<MyTestContext>
    {
        // Context property is magically available, created by the `NUnitBDTestContext` base class. It's unique for each NUnit test!
        private AccountSteps AccountSteps => new AccountSteps(Context);
        private HttpAssertionsSteps HttpAssertions => new HttpAssertionsSteps(Context);
        
        [Test]
        [ScenarioText("Searching for a non-existing account number returns Not Found")]
        public async Task FindNonExistingAccountReturnsNotFound()
        {
            await When(() => AccountSteps.FindAccount(12345))
                .Then(() => HttpAssertions.TheHttpStatusCodeIs(HttpStatusCode.NotFound))
                .BDTestAsync();
        }
        
        [Test]
        [BugInformation("123456")]
        [ScenarioText("Can successfully create a new account")]
        public async Task CreateNewAccountSuccessfully()
        {
            await When(() => AccountSteps.CreateAnAccount())
                .Then(() => HttpAssertions.TheHttpStatusCodeIs(HttpStatusCode.OK))
                .BDTestAsync();
        }
    }
    
    public class AccountSteps
    {
        private MyTestContext _context;
        
        public class AccountSteps(MyTestContext context)
        {
            _context = context;
        }
        
        [StepText("I create an account")
        public async Task CreateAnAccount()
        {
            // ... Some code to create an account!
            // You can use all the information stored in your test context object that was passed into the constructor!
            // And store stuff for later use - Such as assertions!
            // e.g. 
            // var request = new HttpRequestMessage { Method = POST, Body = SomeBody, Uri = SomeUri };
            // _context.HttpResponse = await HttpHelper.SendAsync(request);
        }
        
        [StepText("I search for the account with the customer ID '{0}'")
        public async Task FindAccount(int customerId)
        {
            // ... Some code to find an account!
            // e.g. 
            // var request = new HttpRequestMessage { Method = GET, Uri = $"{SomeUri}/{customerId}" };
            // _context.HttpResponse = await HttpHelper.SendAsync(request);
        }
    }
    
    public class HttpAssertionsSteps
    {
        private MyTestContext _context;
        
        public class HttpAssertionsSteps(MyTestContext context)
        {
            _context = context;
        }
        
        [StepText("the HTTP status code is {0}")
        public async Task TheHttpStatusCodeIs(HttpStatusCode code)
        {
            // ... Some code to assert - Read data previously stored in your context object!
            // e.g.
            // Assert.That(_context.HttpResponse.StatusCode, Is.EqualTo(code));
        }
    }
}

Report Server Example Image

Report Server Example Image