testcase

package module
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2020 License: Apache-2.0 Imports: 7 Imported by: 0

README

Table of Contents

Mentioned in Awesome Go GoDoc Build Status Go Report Card codecov

testcase

The package considered stable and no changes expected to the package exported API.

The main documentation is kept in the GoDoc, and this README serves only as a high level introduction.

This package implements two approaches to help you to do nested BDD style testing in golang.

The package may seems inactive maybe, but it is used daily, I just don't plan to feature creep it, because it is totally efficient to achieve what I need, and If I need extra helper function or anything like that, I usually put it under the $PROJECT_ROOT/testing package. Then I include the helpers with . importing. I highly discourage the use of the dot notation based import outside of the testing files.

How much this project will be maintained ?

This project is based on the testing package T.Run idiom, so basically as long that is supported and maintained by the golang core team, this project is easily considered up to date.

I use it for my private projects, but I designed this project to be cost effective for my time. I only piggybacking the core golang team work basically.

The reason behind the package

What makes testcase different ?

Reference Project

Documentations

Documentation

Overview

Package testcase implements two approaches to help you to do nested BDD style testing in golang.

Spec Variables

in your spec, you can use the `*testcase.variables` object, for fetching values for your objects. Using them is gives you the ability to create value for them, only when you are in the right testing scope that responsible for providing an example for the expected value.

In test case scopes you will receive a structure ptr called `*testcase.variables` which will represent values that you configured for your test case with `Let`.

Values in `*testcase.variables` are safe to use during T#Parallel.

Spec Hooks

Hooks help you setup common things for each test case. For example clean ahead, clean up, mock expectation configuration, and similar things can be done in hooks, so your test case blocks with `Then` only represent the expected result(s).

In case you work with something that depends on side-effects, such as database tests, you can use the hooks, to create clean-ahead / clean-up blocks.

Also if you use gomock, you can use the spec#Around function, to set up the mock with a controller, and in the teardown function, call the gomock.Controller#Finish function, so your test cases will be only about what is the different behavior from the rest of the test cases.

It will panic if you use hooks or variable preparation in an ambiguous way, or when you try to access variable that doesn't exist in the context where you do so. It tries to panic with friendly and supportive messages, but that is highly subjective.

The reason behind the package

I needed something that cover and justify the project costs, so I made a list of requirements to decide if I should create my own, or continue using on of the already existing solutions.

  • low maintenance cost
  • core testing pkg close idioms
  • works perfectly well with `go test` command out of the box
  • includes `-run` option usability for testing one test edge case from many
  • The design of the testing lib should not weight more the value of fancy DSL, than golang idioms.
  • allow me to run test cases in concurrent execution for specification where I know that no side effect expected.
  • this is especially important me, because I love quick test feedback loops
  • allow me to define variables in a way that
  • they receive concrete value later
  • they can be safely overwritten with nested scopes
  • strictly regulated usage,
  • with early errors/panics about potential misuse
  • I want to use stretchr/testify(https://github.com/stretchr/testify), so assertions not necessary for me
  • or more precisely, I needed something that guaranteed to allow me the usage of that pkg

While I liked the existing solutions, I felt that the way I would use them would leave out one or more point from my requirements. So I ended up making a small design about how it would be great for me to test. I took inspiration from [rspec](https://github.com/rspec/rspec), This is how this pkg is made.

What makes testcase different

Using this pkg allow you to set up input variables for your test subject, in a way that the variables are belong to a certain test context scope only, and cannot leak out to other test executions implicitly.

This will allow you to create test cases, where if you forgot to set the context correctly, the tests will panic early on and warn you about.

Also if you run it in parallel, there is a guarantee that your variables will not be leaked out, and will not affect your other test cases, trough a shared variable, because each test case execution has its own dedicated set of variables.

To me fast feedback cycle from the test is really important, and go `*testing.T#Parallel` functionality is really liked. And I needed a solution that would allow me to create specifications, that are thread safe for concurrent execution.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Spec

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

Spec provides you a struct that makes building nested test context easy with the core T#Context function.

spec structure is a simple wrapping around the testing.T#Context. It doesn't use any global singleton cache object or anything like that. It doesn't force you to use global variables.

It uses the same idiom as the core go testing pkg also provide you. You can use the same way as the core testing pkg

go run ./... -variables -run "the/name/of/the/test/it/print/out/in/case/of/failure"

It allows you to do context preparation for each test in a way, that it will be safe for use with testing.T#Parallel.

func NewSpec

func NewSpec(t *testing.T) *Spec

NewSpec create new Spec struct that is ready for usage.

func (*Spec) After

func (spec *Spec) After(afterBlock testCaseBlock)

After give you the ability to run a block after each test case. This is ideal for running cleanups. The received *testing.T object is the same as the Then block *testing.T object This hook applied to this scope and anything that is nested from here. All setup block is stackable.

func (*Spec) And

func (spec *Spec) And(desc string, testContextBlock func(s *Spec))

And is an alias for testcase#Spec.Context And is used to represent additional requirement for reaching a certain testing runtime contexts.

func (*Spec) Around

func (spec *Spec) Around(aroundBlock hookBlock)

Around give you the ability to create "Before" setup for each test case, with the additional ability that the returned function will be deferred to run after the Then block is done. This is ideal for setting up mocks, and then return the assertion request calls in the return func. This hook applied to this scope and anything that is nested from here. All setup block is stackable.

func (*Spec) Before

func (spec *Spec) Before(beforeBlock testCaseBlock)

Before give you the ability to run a block before each test case. This is ideal for doing clean ahead before each test case. The received *testing.T object is the same as the Test block *testing.T object This hook applied to this scope and anything that is nested from here. All setup block is stackable.

func (*Spec) Context

func (spec *Spec) Context(desc string, testContextBlock func(s *Spec))

Context allow you to create a sub specification for a given spec. In the sub-specification it is expected to add more contextual information to the test in a form of hook of variable setting. With Context you can set your custom test description, without any forced prefix like describe/when/and.

It is basically piggybacking the testing#T.Context and create new subspec in that nested testing#T.Context scope. It is used to add more description context for the given subject. It is highly advised to always use When + Before/Around together, in which you should setup exactly what you wrote in the When description input. You can Context as many When/And within each other, as you want to achieve the most concrete edge case you want to test.

To verify easily your state-machine, you can count the `if`s in your implementation, and check that each `if` has 2 `When` block to represent the two possible path.

func (*Spec) Describe

func (spec *Spec) Describe(subjectTopic string, specification func(s *Spec))

Describe creates a new spec scope, where you usually describe a subject.

By convention it is highly advised to create a variable `subject` with function that share the return signature of the method you test on a structure, and take *testcase.variables as the only input value. If your method require input values, you should strictly set those values within a `When`/`And` scope. This ensures you have to think trough the possible state-machines paths that are based on the input values.

For functions where 2 value is returned, and the second one is an error, in order to avoid repetitive test cases in the `Then` I often define a `onSuccess` variable, with a function that takes `testcase#variables` as well and test error return value there with `testcase#variables.T()`.

func (*Spec) Let

func (spec *Spec) Let(varName string, letBlock func(t *T) interface{})

Let allow you to define a test case variable to a given scope, and below scopes. It cannot leak to higher level scopes, and between Concurrent test runs. Calling Let in a nested/sub scope will apply the new value for that value to that scope and below.

It will panic if it is used after a When/And/Then scope definition, because those scopes would have no clue about the later defined variable. In order to keep the specification reading mental model requirement low, it is intentionally not implemented to handle such case.

variables strictly belong to a given `Describe`/`When`/`And` scope, and configured before any hook would be applied, therefore hooks always receive the most latest version from the `Let` variables, regardless in which scope the hook that use the varable is define.

func (*Spec) Parallel

func (spec *Spec) Parallel()

Parallel allows you to set all test case for the context where this is being called, and below to nested contexts, to be executed in parallel (concurrently). Keep in mind that you can call Parallel even from nested specs to apply Parallel testing for that context and below. This is useful when your test suite has no side effects at all. Using values from *variables when Parallel is safe. It is a shortcut for executing *testing.T#Parallel() for each test

func (*Spec) Test

func (spec *Spec) Test(desc string, test testCaseBlock)

Test creates a test case block where you receive the fully configured `testcase#T` object. Hook contents that meant to run before the test edge cases will run before the function the Test receives, and hook contents that meant to run after the test edge cases will run after the function is done. After hooks are deferred after the received function block, so even in case of panic, it will still be executed.

It should not contain anything that modify the test subject input. It should focuses only on asserting the result of the subject.

func (*Spec) Then

func (spec *Spec) Then(desc string, test testCaseBlock)

Then is an alias for Test

func (*Spec) When

func (spec *Spec) When(desc string, testContextBlock func(s *Spec))

When is an alias for testcase#Spec.Context When is used usually to represent `if` based decision reasons about your testing subject.

type Steps

type Steps []func(*testing.T) func()

Steps provide you with the ability to create setup steps for your testing#T.Run based nested tests.

func (Steps) Around

func (s Steps) Around(step func(*testing.T) func()) Steps

Around create a new Steps object that should be stored in the current context with `:=` the function it receives should return a func() that will be used during `Setup` teardown.

func (Steps) Before

func (s Steps) Before(step func(t *testing.T)) Steps

Before create a new Steps object that should be stored in the current context with `:=`

func (Steps) Setup

func (s Steps) Setup(t *testing.T) func()

Setup execute all the hooks, and then return func that represent defers the returned function should be defered

type T

type T struct {
	*testing.T
	// contains filtered or unexported fields
}

T embeds both testcase variables, and testing#T functionality. This leave place open for extension and but define a stable foundation for the hooks and test edge case function signatures

Works as a drop in replacement for packages where they depend on one of the function of testing#T

func (*T) Defer added in v0.2.1

func (t *T) Defer(fn interface{})

Defer will execute a passed function object after the test block successfully ran. Deferred functions are guaranteed to run, regardless of panics

func (*T) I

func (t *T) I(varName string) interface{}

I will return a testcase variable. it is suggested to use interface casting right after to it, so you can work with concrete types. If there is no such value, then it will panic with a "friendly" message.

func (*T) Let added in v0.2.0

func (t *T) Let(varName string, value interface{})

Let will allow you to define/override a spec runtime bounded variable. The idiom is that if you cannot express the variable declaration with spec level let, or if you need to override in a sub scope a let's content using the previous variable state, or a result of a multi return variable needs to be stored at spec runtime level you can utilize this Let function to achieve this.

Typical use-case to this when you want to have a context.Context, with different values or states, but you don't want to rebuild from scratch at each layer.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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