Git Product home page Git Product logo

kekiri's Introduction

Overview

A .NET framework that supports writing low-ceremony BDD tests using Gherkin language.

Kekiri honors the conventions of the Gherkin cucumber language.

Status

Build status

Package Latest Release
Kekiri NuGet version
Kekiri.IoC.Autofac NuGet version
Kekiri.IoC.ServiceProvider NuGet version
Kekiri.Xunit NuGet version
Kekiri.NUnit NuGet version

Setup

Kekiri targets netstandard2.0. To get started, be sure to have the latest dotnet core tools.

Select Test Runner

Xunit (recommended)

PM> Install-Package Kekiri.Xunit

NUnit

PM> Install-Package Kekiri.NUnit

IoC Integration (optional)

Autofac

PM> Install-Package Kekiri.IoC.Autofac

Be sure to call AutofacBootstrapper.Initialize() before your tests run.

IServiceProvider

PM> Install-Package Kekiri.IoC.ServiceProvider

Be sure to call ServiceProviderBootstrapper.Initialize(…) before your tests run.

Why Kekiri

Unlike other BDD frameworks that impose process overhead (management of feature files, custom tooling, etc) Kekiri allows developers to write BDD scenarios just as quickly and easily as they would a "plain old" test.

The resulting scenario fixtures are concise, highly portable, and adhere to Act, Arrange, and Assert.

IoC is also a first-class citizen encouraging testing object interactions in collaboration rather than isolation. More details here.

Example

Implementing a basic calculator.

Start with the test

    class Calculator_tests : Scenarios
    {
        [Scenario]
        public void Adding_two_numbers()
        {
            Given(a_calculator)
               .And(the_user_enters_50)
               .And(the_user_enters_70);
            When(adding);
            Then(the_result_is_120);
        }

        void a_calculator() {}

        void the_user_enters_50() {}

        void the_user_enters_70() {}

        void adding() { throw new NotImplementedException(); }

        void the_result_is_120() {}
    }

If we were to run this test (even though it fails) we get a nice Cucumber-style feature output:

        Scenario: Adding two numbers
        Given a calculator
            And the user enters 50
            And next the user enters 70
        When adding
        Then the result is 120

Add the implementation

    class Adding_two_numbers : Scenarios
    {
        Calculator _calculator;

        [Scenario]
        public void Adding_two_numbers()
        {
            Given(a_calculator)
               .And(the_user_enters_50)
               .And(the_user_enters_70);
            When(adding);
            Then(the_screen_displays_a_result_of_120);
        }

        void a_calculator()
        {
            _calculator = new Calculator();
        }

        void the_user_enters_50()
        {
            _calculator.Operand1 = 50;
        }

        void the_user_enters_70()
        {
            _calculator.Operand2 = 70;
        }

        void adding()
        {
            _calculator.Add();
        }

        void the_result_is_120()
        {
            Assert.AreEqual(120m, _calculator.Result);
        }
    }

    class Calculator
    {
        public decimal Operand1 { get; set; }
        public decimal Operand2 { get; set; }

        public decimal Result { get; set; }

        public void Add() { Result = Operand1 + Operand2; }
    }

Supported Naming Conventions

Kekiri supports both Pascal case conventions (e.g. WhenDoingTheThing) as it does underscore convention (e.g. When_doing_the_thing).


Scenario Output

Kekiri supports outputing the cucumber text. The output settings are controlled via the KEKIRI_OUTPUT environment variable.

Example:

   $env:KEKIRI_OUTPUT='console,files'

Output to Console

To output to the console, ensure that KEKIRI_OUTPUT contains console.

Output to Files

To output to .feature files in the test execution directory, ensure that KEKIRI_OUTPUT contains files.

The name of the feature file is based on the containing namespace of the scenario. For example, if Adding_two_numbers was defined in UnitTests.Features.Addition.Adding_two_numbers, the output would be written to Addition.feature.


Wiki

More detailed documentation can be found on the wiki.

Other common use cases

Expected Exceptions

    class Divide_by_zero : Scenarios
    {
        readonly Calculator _calculator = new Calculator();

        [Scenario]
        public Divide_by_zero()
        {
            Given(a_denominator_of_0);
            When(dividing).Throws();
            Then(an_exception_is_raised);
        }

        void a_denominator_of_0()
        {
            _calculator.Operand2 = 0;
        }

        void dividing()
        {
            _calculator.Divide();
        }

        void an_exception_is_raised()
        {
            Catch<DivideByZeroException>();
        }
    }

Notice, here we've used the Throws() method to inform that throwing an exception is the expected behavior. In 1 or more Then methods, the thrown type of exception must be caught (using the templated method Catch<>).

Examples (aka tabular tests)

    public class Subtracting_two_numbers : Scenarios
    {
        readonly Calculator _calculator = new Calculator();

        [Example(12, 5, 7)]
        [Example(20, 5, 15)]
        [ScenarioOutline]
        public Subtracting_two_numbers(double operand1, double operand2, double expectedResult)
        {
            Given(the_user_enters_OPERAND1, operand1)
                .And(the_user_enters_OPERAND2, operand2);
            When(subtracting);
            Then(the_result_is_EXPECTED, expectedResult);
        }

        void the_user_enters_OPERAND1(double operand1)
        {
            _calculator.Operand1 = operand1;
        }

        void the_user_enters_OPERAND2(double operand2)
        {
            _calculator.Operand2 = operand2;
        }

        void subtracting()
        {
            _calculator.Subtract();
        }

        void the_result_is_EXPECTED(double expected)
        {
            Assert.AreEqual(expected, _calculator.Result);
        }
    }
        Given the user enters 12
          And the user enters 5
        When subtracting
        Then the result is 7

Note: step method parameter names can be used as substitution macros by mentioning them in CAPS.

For more advanced topics, check out the wiki.

Acknowledgements

Kekiri uses and is influenced by the following open source projects:

kekiri's People

Contributors

andyalm avatar chris-peterson avatar gbasi avatar jorbor avatar kcamp avatar ylatuya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

kekiri's Issues

Add support for parameterized Fluent steps

Much like [Example] has a matching algorithm to tailor output based on parameter values, the Fluent Givens should support this as well (not sure if any other step types [When/Then]make sense).

add nunit 3.x support

Our testing projects vary between nunit 2.6.4 and nunit 3.5

It would be useful to be able to support the nunit 3.5 projects with Kekiri in a low-friction way.

Are there any thoughts or ideas to support this? The strong name of nunit.framework has changed between 2 and 3, so an assembly redirect is out. Simply subclassing Kekiri.Test and applying the OneTimeSetUp and OneTimeTearDown attributes [local to my own project] is an "almost" solution, but falls short in that the Kekiri.ThenAttribute can't be used because of the dependency on nunit 2.6.
Creating a new ThenAttribute class isn't an option because of the internal interface IStepAttribute [no access to the type to implement it.]

Any ideas? A straight update to nunit 3.5 in the Kekiri solution leaves a solid portion of tests in a failing state, so any update would require additional work, for certain.

Support lambdas

Surprisingly, when defining

When(() => {}) a test will fail:

[xUnit.net 00:00:00.41]     UnitTests.CustomTimestamp.DefaultBehavior [FAIL]
  Failed UnitTests.CustomTimestamp.DefaultBehavior [13 ms]
  Error Message:
   Kekiri.Impl.Exceptions.WhenFailed : Error in 'CustomTimestamp':
'<DefaultBehavior>b  1 0' threw an exception.  If this is expected behavior use .Throws() and add a step that uses Catch<>
---- System.Reflection.TargetException : Object does not match target type.
  Stack Trace:
     at Kekiri.Impl.ScenarioRunner.RunWhenAsync()
   at Kekiri.Impl.ScenarioRunner.RunAsync()
   at Kekiri.ScenarioBase.RunAsync()
   at Kekiri.ScenarioBase.RunAsync()
--- End of stack trace from previous location ---
----- Inner Stack Trace -----
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at Kekiri.Impl.StepMethodInvoker.InvokeAsync(ScenarioBase scenario)
   at Kekiri.Impl.ScenarioRunner.RunWhenAsync()

Failed!  - Failed:     1

Generally, we'd prefer if a method name is provided, but it seems desirable to provide lamdas, particularly for "do nothing"

Shorthand for `Throws`

Add a new extension whereby

When(xyz).Throws();
Catch<YourExceptionType>();

Could be abbreviated by When(xyz).Throws<YourException>()

Container.Register within an autofac Step<T> should allow "open" types

Example, I would have a Given<AMockDatastore>() which can register the datastore easily, but when I want to do a Given<MyController>(), I can't register the type itself, only an instance, which means I would end up having to Resolve, and therefor not being able to register anything else.

To break up the fixture trace file

[for the wiki, or the readme]

To break up the generated fixture file into individual files for use with tools like Pickles https://github.com/picklesdoc/pickles

Create a script file: SplitFixtureFile.ps1

# From http://rafdelgado.blogspot.com/2012/06/powershell-removing-special-characters.html
Function Convert-ToFriendlyName
{
    param ($Text)

    # Unwanted characters (includes spaces and '-') converted to a regex:
    $SpecChars = '!', '"', '£', '$', '%', '&', '^', '*', '(', ')', '@', '=', '+', '¬', '`', '\', '<', '>', '.', '?', '/', ':', ';', '#', '~', "'", '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', ' '
    $remspecchars = [string]::join('|', ($SpecChars | % {[regex]::escape($_)}))

    # Convert the text given to correct naming format (Uppercase)
    $name = (Get-Culture).textinfo.totitlecase(“$Text”.tolower())

    # Remove unwanted characters
    $name = $name -replace $remspecchars, ""
    $name
}

function SplitFixtureFile
{
    param([string]$filepath,[string]$deployDestination)

    if (Test-Path $filepath -PathType Leaf) {
        $reader = new-object System.IO.StreamReader($filepath)
        $filename = "NUL"

        if(!(Test-Path -Path $deployDestination )){
            New-Item -ItemType directory -Path $TARGETDIR
        }

        while(($line = $reader.ReadLine()) -ne $null)
        {
            if ($line.ToLower().StartsWith("fixture:") || )
            {
                $line -imatch "fixture: (?<content>.*)"
                $fixture_name = Convert-ToFriendlyName $matches['content']

                $filename = "{0}\{1}.fixture" -f ($deployDestination, $fixture_name)
            }
            Add-Content -path $fileName -value $line
        }

        $reader.Close()
    }
}

SplitFixtureFile $args[0] $args[1]

And a post-build task, as follows:

<Target Name="SplitFixtureFile" Condition=" '$(WriteLogEntry)'!='false' ">
  <PropertyGroup>
    <PowerShellExe Condition=" '$(PowerShellExe)'=='' "> 
      %WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe
    </PowerShellExe>
    <ScriptLocation Condition=" '$(ScriptLocation)'=='' ">
      $(MSBuildProjectDirectory)\SplitFixtureFile.ps1
    </ScriptLocation>
    <FixtureFileOutputPath Condition=" '$(FixtureFileOutputPath)'=='' ">
      $(MSBuildProjectDirectory)\Fixtures
    </FixtureFileOutputPath>
  </PropertyGroup>
  <Exec Command="$(PowerShellExe) -NonInteractive -executionpolicy Unrestricted 
                 -command &quot;&amp; { 
                          &amp;&apos;$(ScriptLocation)&apos; 
                          &apos;$(FixtureFileOutputPath)&apos; } &quot;" 
  /> 
</Target>

Update documentation

The documentation is for NUnit. Needs to be updated to XUnit (e.g. Scenario -> Scenarios).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.