gocuke

package module
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2024 License: Apache-2.0 Imports: 20 Imported by: 4

README

gocuke 🥒

Go Reference

gocuke is a Gherkin-based BDD testing library for golang.

Features

  • tight integration with *testing.T (use any standard assertion library or mocking framework)
  • support for passing context between steps using suites which offers better type safety than other generic context approaches
  • auto-discovery of step definitions defined as test suite methods and step definition suggestions for minimal configuration
  • property-based testing via https://github.com/flyingmutant/rapid
  • user-friendly wrapper for data tables
  • support for big integers and big decimals (via https://github.com/cockroachdb/apd)
  • parallel test execution by default
  • full support for all of the latest Gherkin features including rules (via the latest cucumber/gherkin-go and cucumber/messages-go)

Why another golang BDD library?

gocuke was inspired by godog and gobdd. I tried both of these libraries and wanted a specific developer UX that I couldn't achieve with either. godog was not a good fit for the same reasons as that gobdd was created (specifically tight integration with *testing.T). Looking at the source code for gobdd, it needed to be updated to a new versions of cucumber/gherkin-go and cucumber/messages-go and significant changes were needed to accommodate this API. So gocuke was written. We are happy to coordinate with the authors of either of these libraries at some point to align on common goals.

Quick Start

Step 1: Write some Gherkin

In a file features/simple.feature:

Feature: simple

  Scenario Outline: eat cukes
    Given I have <x> cukes
    When I eat <y>
    Then I have <z> left

    Examples:
      | x | y | z |
      | 5 | 3 | 2 |
      | 10 | 2 | 8 |
Step 2: Setup the test suite

In a file simple_test.go:

package simple

import (
	"github.com/regen-network/gocuke"
	"testing"
)

func TestMinimal(t *testing.T) {
	// a new step definition suite is constructed for every scenario
	gocuke.NewRunner(t, &suite{}).Run()
}

type suite struct {
	// special arguments like TestingT are injected automatically into exported fields
	gocuke.TestingT
}

When you run the tests, they should fail and suggest that you add these step definitions:

func (s *suite) IHaveCukes(a int64) {
    panic("PENDING")
}

func (s *suite) IEat(a int64) {
    panic("PENDING")
}

func (s *suite) IHaveLeft(a int64) {
    panic("PENDING")
}

Steps can be manually registered with the runner for customization using this code:
  Step(`^I\s+have\s+(-?\d+)\s+cukes$`, (*suite).IHaveCukes).
  Step(`^I\s+eat\s+(-?\d+)$`, (*suite).IEat).
  Step(`^I\s+have\s+(-?\d+)\s+left$`, (*suite).IHaveLeft)

Copy these definitions into simple_test.go.

Step 3: Implement Step Definitions

Now implement the step definitions in simple_test.go, adding the variable cukes int64 to suite which tracks state between tests.

NOTE: a new suite is constructed for every test case so it is safe to run tests in parallel, which is the default and what is happening in this example with each of the test cases in the Scenario Outline.

type suite struct {
	gocuke.TestingT
	cukes int64
}

func (s *suite) IHaveCukes(a int64) {
	s.cukes = a
}

func (s *suite) IEat(a int64) {
	s.cukes -= a
}

func (s *suite) IHaveLeft(a int64) {
	if a != s.cukes {
		s.Fatalf("expected %d cukes, have %d", a, s.cukes)
	}
}

Your tests should now pass!

Usage Details

Step Argument Types

gocuke supports the following step argument types for arguments captured from steps:

  • string
  • int64
  • *big.Int
  • *apd.Decimal

float64 support is not planned because it is lossy.

Doc Strings and Data Tables

gocuke.DocString or gocuke.DataTable should be used as the last argument in a step definition if the step uses a doc string or data table. gocuke.DataTable provides useful helper methods for working with data tables.

Special Step Argument Types

The following special argument types are supported:

These can be used in step definitions, hooks, and will be injected into the suite type if there are exported fields defined with these types.

Hooks Methods

If the methods Before, After, BeforeStep, or AfterStep are defined on the suite, they will be registered as hooks. After will always be called even when tests fail. AfterStep will always be called whenever a step started and failed.

It is generally not recommended to over-use hooks. Before should primarily be used for setting up generic resources and After should be used for cleaning up resources. Given and Background steps should generally be used for setting up specific test conditions. BeforeStep and AfterStep should only be used in very special circumstances.

Tag Expressions

Cucumber tag expressions can be used for selecting a subset of tests to run. The command-line option -gocuke.tags can be used to specify a subset of tests to run.

The Runner.Tags() method can be used to select a set of tags to run in unit tests. Runner.ShortTags method can be used to select a set of tags to

Custom options

Runner has the following methods for setting custom options

  • Path() sets custom paths, and accepts ** glob patterns making use of the doublestar library. The default is features/**/*.feature
  • Step() can be used to add custom steps with special regular expressions.
  • Before(), After(), BeforeStep(), or and AfterStep() can be used to register custom hooks.
  • Tags and ShortTags can be used with tag expressions as described above.
  • NonParallel() disables parallel tests.
  • --verbose or -v with go test will emit the current test step definition to stdout while your tests are running, this is useful for debugging failing tests.
Property-based testing using Rapid

Property-based tests using https://github.com/flyingmutant/rapid can be enabled by using *rapid.T as the first argument of test methods (after the suite receiver argument). Property-based test cases will be run as many times is rapid is configured to run tests.

Example:

Scenario: any int64 value
  Given any int64 string
  When when I convert it to an int64
  Then I get back the original value
type suite struct {
  TestingT

  x, parsed   int64
  str    string
}

func (s *valuesSuite) AnyInt64String(t *rapid.T) {
	s.x = rapid.Int64().Draw(t, "x").(int64)
	s.str = fmt.Sprintf("%d", s.x)
}

func (s *valuesSuite) WhenIConvertItToAnInt64() {
  s.parsed = toInt64(s, s.str)
}


func (s *suite) IGetBackTheOriginalValue() {
  assert.Equal(s, s.x, s.parsed)
}

Roadmap

Contributing

pre-commit hooks
  • We have some steps as defined in [.pre-commit-config.yaml] (.pre-commit-config.yaml) that run before commit

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Version = "v0.0.0-dev"

Version of package - based on Semantic Versioning 2.0.0 http://semver.org/

Functions

This section is empty.

Types

type Cell

type Cell struct {
	// contains filtered or unexported fields
}

Cell represents a data table cell.

func (Cell) BigInt

func (c Cell) BigInt() *big.Int

BigInt returns the cell as a *big.Int.

func (Cell) Decimal

func (c Cell) Decimal() *apd.Decimal

Decimal returns the cell value as an *apd.Decimal.

func (Cell) Int64

func (c Cell) Int64() int64

Int64 returns the cell as an int64.

func (Cell) String

func (c Cell) String() string

String returns the cell value as a string.

type DataTable

type DataTable struct {
	// contains filtered or unexported fields
}

DataTable wraps a data table step argument

func (DataTable) Cell

func (d DataTable) Cell(row, col int) *Cell

Cell returns the cell at the provided 0-based row and col offset.

func (DataTable) HeaderTable

func (d DataTable) HeaderTable() *HeaderTable

HeaderTable returns the data table as a header table which is a wrapper around the table which assumes that the first row is the table header.

func (DataTable) NumCols

func (d DataTable) NumCols() int

NumCols returns the number of columns in the data table.

func (DataTable) NumRows

func (d DataTable) NumRows() int

NumRows returns the number of rows in the data table.

type DocString

type DocString struct {
	MediaType string
	Content   string
}

DocString represents a doc string step argument.

type HeaderTable

type HeaderTable struct {
	DataTable
	// contains filtered or unexported fields
}

HeaderTable is a wrapper around a table which assumes that the first row is \ the table header.

func (*HeaderTable) Get

func (h *HeaderTable) Get(row int, col string) *Cell

Get returns the cell at the provided row offset (skipping the header row) and column name (as indicated in the header).

func (*HeaderTable) NumRows

func (h *HeaderTable) NumRows() int

NumRows returns the number of rows in the table (excluding the header row).

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner is a test runner.

func NewRunner

func NewRunner(t *testing.T, suiteType interface{}) *Runner

NewRunner constructs a new Runner with the provided suite type instance. Suite type is expected to be a pointer to a struct or a struct. A new instance of suiteType will be constructed for every scenario.

The following special argument types will be injected into exported fields of the suite type struct: TestingT, Scenario, Step, *rapid.T.

Methods defined on the suite type will be auto-registered as step definitions if they correspond to the expected method name for a step. Method parameters can start with the special argument types listed above and must be followed by step argument types for each captured step argument and DocString or DataTable at the end if the step uses one of these. Valid step argument types are int64, string, *big.Int and *apd.Decimal.

The methods Before, After, BeforeStep and AfterStep will be recognized as hooks and can take the special argument types listed above.

func (*Runner) After

func (r *Runner) After(hook interface{}) *Runner

After registers an after hook function which can take special step arguments. This hook will be called even when the test fails.

func (*Runner) AfterStep

func (r *Runner) AfterStep(hook interface{}) *Runner

AfterStep registers an after step hook function which can take special step arguments. This hook will be called even when the test fails.

func (*Runner) Before

func (r *Runner) Before(hook interface{}) *Runner

Before registers a before hook function which can take special step arguments.

func (*Runner) BeforeStep

func (r *Runner) BeforeStep(hook interface{}) *Runner

BeforeStep registers a before step hook function which can take special step arguments.

func (*Runner) NonParallel

func (r *Runner) NonParallel() *Runner

NonParallel instructs the runner not to run tests in parallel (which is the default).

func (*Runner) Path

func (r *Runner) Path(paths ...string) *Runner

Path specifies glob paths for the runner to look up .feature files. The default is `features/*.feature`.

func (*Runner) Run

func (r *Runner) Run()

Run runs the features registered with the runner.

func (*Runner) ShortTags

func (r *Runner) ShortTags(tagExpr string) *Runner

ShortTags specifies which tag expression will be used to select for tests when testing.Short() is true. This tag expression will be combined with any other tag expression that is applied with Tags() when running short tests.

func (*Runner) Step

func (r *Runner) Step(step interface{}, definition interface{}) *Runner

Step can be used to manually register a step with the runner. step should be a string or *regexp.Regexp instance. definition should be a function which takes special step arguments first and then regular step arguments next (with string, int64, *big.Int, and *apd.Decimal as valid types) and gocuke.DocString or gocuke.DataTable as the last argument if this step uses a doc string or data table respectively. Custom step definitions will always take priority of auto-discovered step definitions.

func (*Runner) Tags

func (r *Runner) Tags(tagExpr string) *Runner

Tags will run only the tests selected by the provided tag expression. Tags expressions use the keywords and, or and not and allow expressions in parentheses to allow expressions like "(@smoke or @ui) and (not @slow)".

type Scenario

type Scenario interface {
	Name() string
	Tags() []string
	URI() string
	// contains filtered or unexported methods
}

Scenario is a special step argument type which describes the running scenario and that can be used in a step definition or hook method.

type Step

type Step interface {
	Text() string
	// contains filtered or unexported methods
}

Step is a special step argument type which describes the running step and that can be used in a step definition or hook method.

type TestingT

type TestingT interface {
	Cleanup(func())
	Error(args ...interface{})
	Errorf(format string, args ...interface{})
	Fail()
	FailNow()
	Failed() bool
	Fatal(args ...interface{})
	Fatalf(format string, args ...interface{})
	Log(args ...interface{})
	Logf(format string, args ...interface{})
	Skip(args ...interface{})
	SkipNow()
	Skipf(format string, args ...interface{})
	Helper()
}

TestingT is the common subset of testing methods exposed to test suite instances and expected by common assertion and mocking libraries.

Directories

Path Synopsis
examples
api
Example - demonstrates REST API server implementation tests.
Example - demonstrates REST API server implementation tests.
internal
tag

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL