testo

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: May 13, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

README

Banner

Testo

Go Reference

Testo is a modular testing framework for Go built on top of testing.T. It is focused on suite-based tests and has an extensive plugin system.

Testo (/tɛstɒ/) is a play on words "test" and "тесто", meaning "dough". Just like you can cook anything from dough, you can test anything with Testo!

Add some flavor to your tests with toppings - a collection of small, miscellaneous plugins for Testo framework.

Features

  • Plugins - adapt your tests to any scenario with features you need.
  • Parametrized tests - describe a test once, repeat it with different parameters.
  • Parallel tests - make your tests faster by running them all at once.
  • Lifecycle hooks - before and after any suite, test & sub-test.
  • Test annotations - attach static options to any test.
  • Informative errors and traces - no need to guess what went wrong.
  • Sub-tests - support for nested tests.
  • Test reflection - deeply inspect test's meta-information.
  • Caching - key-value storage persistent between test runs.

Why Testo

At Ozon, Testo powers thousands of end-to-end tests daily in production.

With plugins, it is flexible enough to adapt to diverse requirements, without leaving the Go ecosystem - just a layer over testing.T.

If your needs are outgrowing standard testing package, Testo is a great choice.

Quick Start

go get github.com/ozontech/testo

Your first test with Testo:

// file: main_test.go
package main

import (
    "testing"

    "github.com/ozontech/testo"
)

// A special construct that describes what plugins to use.
// Here we use the base T without plugins.
type T struct { *testo.T }

type Suite struct{ testo.Suite[T] }

func (Suite) TestHelloWorld(t T) {
    t.Log("hello from testo!")
}

func Test(t *testing.T) {
    testo.RunSuite(t, new(Suite))
}

And run it with go test as usual:

go test .

See also VS Code extension for Testo.

Next steps

Plugins

Testo features a powerful plugin system.

Plugins can:

  • Provide BeforeAll/AfterAll, BeforeEach/AfterEach & BeforeSubEach/AfterSubEach hooks.
  • Plan tests for execution - filter, duplicate & reorder.
  • Override built-in T methods, such as Log, Error and etc.
  • Extend T by adding new methods.
  • Allow users to configure their behavior through options.
  • Communicate with other plugins.
  • Add command line flags for go test command.

Examples:

VS Code Extension

Testo has its own VS Code extension.

Makes it easier to run and debug individual suite tests.

VSCode extension functionality screenshot

Minimum supported Go version

Testo guarantees to support at least 3 latest major Go releases.

Currently, minimum supported Go version is 1.24

License

This project is released under the Apache-2.0 license.

Documentation

Overview

Package testo is a modular testing framework built on top of testing.T. It is focused on suite based tests and has an extensive plugin system.

Quick Start

A minimal working example looks like this:

package main

import (
	"testing"

	"github.com/ozontech/testo"
)

type T struct { *testo.T }
type Suite struct { testo.Suite[T] }

func (Suite) Test(t T) { t.Log("Hello, world!") }

func Test(t *testing.T) { testo.RunSuite(t, new(Suite)) }

Notice the `Test(t *testing.T)` - since RunSuite requires an instance of `testing.T` we must declare a regular go test first, and only inside it we will be able to actually call our Testo suite.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func For

func For[Suite suite[T], T CommonT](
	test func(Suite, T),
	options ...testoplugin.Option,
) struct{}

For applies static options (annotations) for a given test.

Test annotations is a slice of options to be passed for this test. Test annotations are available to plugins before running an actual test, thus enhancing test planning features.

Multiple annotation calls to the same test will append options.

testo.For(MySuite.TestFoo, someplugin.WithThis(...), otherplugin.WithThat(...))
testo.For((*Suite).TestFoo, myplugin.WithRetry())

NOTE: it returns an empty struct{} value to enable the following usage:

var _ = testo.For(MySuite.TestFoo, someplugin.WithSomeOption(true))

func (MySuite) TestFoo(t T) { ... }

That "var _ =" construction would not be possible otherwise. This is slightly less verbose than using an init function:

func init() {
	testo.For(MySuite.TestFoo, someplugin.WithSomeOption(true))
}

func ForEach

func ForEach[Suite suite[T], T CommonT, P any](
	test func(Suite, T, P),
	options ...testoplugin.Option,
) struct{}

ForEach applies static options (annotations) for each case of a given parametrized test.

Similar to For, but for parametrized tests.

testo.ForEach(MySuite.TestFoo, someplugin.WithThis(...), otherplugin.WithThat(...))
testo.ForEach((*Suite).TestFoo, myplugin.WithRetry())

func Options

func Options(options ...testoplugin.Option)

Options appends given options to the global options.

Global options are prepended to each RunSuite call.

func init() {
    testo.Options(myplugin.OutputDir("..."))
}

func Reflect

func Reflect(t CommonT) testoreflect.Reflection

Reflect returns meta information about given t.

You can reflect over any test by accessing its T instance:

func (Suite) TestFoo(t T) {
	r := testo.Reflect(t)
	// r stores Reflection struct.
}

Same logic applies for plugins. If a plugin embeds `*testo.T` it can call the same testo.Reflect function:

type Plugin struct{ *testo.T }

func (p *Plugin) Plugin(parent testoplugin.Plugin, options ...testoplugin.Options) testoplugin.Spec {
	return testoplugin.Spec{
		Hooks: testoplugin.Hooks{
 			BeforeEach: testoplugin.Hook{
 				Func: func() { testo.Reflect(p) }
			}
		}
	 }
}

func Run

func Run[T CommonT](
	t T,
	name string,
	f func(t T),
	options ...testoplugin.Option,
) bool

Run runs f as a subtest of t called name. It runs f in a separate goroutine and blocks until f returns or calls t.Parallel to become a parallel test. Run reports whether f succeeded (or at least did not fail before calling t.Parallel).

Run may be called simultaneously from multiple goroutines, but all such calls must return before the outer test function for t returns.

WARN: Running this function during t.Cleanup panics.

func RunSuite

func RunSuite[Suite suite[T], T CommonT](
	testingT TestingT,
	suite Suite,
	options ...testoplugin.Option,
) bool

RunSuite will run the tests under the given suite.

Test is defined as a suite method in the form of "TestXXX" or "Test" which accepts a single parameter of the same type as T passed to this function.

It also accepts options for the plugins which can be used to configure those plugins. See testoplugin.Option.

RunSuite reports whether all suite tests succeeded.

Types

type CommonT

type CommonT interface {
	// contains filtered or unexported methods
}

CommonT is the interface common for all T derivatives.

type Suite

type Suite[T CommonT] struct {
	// contains filtered or unexported fields
}

Suite is the base suite that all user-defined suites must embed.

Suite may optionally provide hooks by implementing their methods:

  • BeforeAll(T) - is called before all suite tests once.
  • BeforeEach(T) - is called before each suite test. T is shared with an actual test.
  • AfterEach(T) - is called after each suite test. T is shared with an actual test.
  • AfterAll(T) - is called after all suite tests once.

Example:

type MySuite struct {
	testo.Suite[MyT]
}

func (Suite[T]) AfterAll

func (Suite[T]) AfterAll(t T)

AfterAll hook.

func (Suite[T]) AfterEach

func (Suite[T]) AfterEach(t T)

AfterEach hook.

func (Suite[T]) BeforeAll

func (Suite[T]) BeforeAll(t T)

BeforeAll hook.

func (Suite[T]) BeforeEach

func (Suite[T]) BeforeEach(t T)

BeforeEach hook.

type T

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

T is a wrapper for testing.T. This is a core entity in testo and used as a testing.T replacement.

The common pattern is to embed it into new struct type:

type MyT struct {
	*testo.T
	*SomePlugin
}

Plugins can also optionally embed it - testo will automatically initialize it by sharing the same value as an actual currently running test's T.

type SomePlugin struct { *testo.T }

func (*T) Context

func (t *T) Context() context.Context

Context returns a context that is canceled just before Cleanup-registered functions are called.

Cleanup functions can wait for any resources that shut down on context.Context.Done before the test completes.

func (*T) Deadline

func (t *T) Deadline() (time.Time, bool)

Deadline reports the time at which the test binary will have exceeded the timeout specified by the -timeout flag.

By default, the ok result is false if the -timeout flag indicates "no timeout" (0).

func (*T) Error

func (t *T) Error(args ...any)

Error is equivalent to Log followed by Fail.

func (*T) Errorf

func (t *T) Errorf(format string, args ...any)

Errorf is equivalent to Error with formatted message.

func (*T) Fail

func (t *T) Fail()

Fail marks the function as having failed but continues execution.

func (*T) FailNow

func (t *T) FailNow()

FailNow marks the function as having failed and stops its execution by calling runtime.Goexit (which then runs all deferred calls in the current goroutine). Execution will continue at the next test or benchmark. FailNow must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test. Calling FailNow does not stop those other goroutines.

func (*T) Failed

func (t *T) Failed() bool

Failed reports whether the function has failed.

func (*T) Fatal

func (t *T) Fatal(args ...any)

Fatal is equivalent to Log followed by FailNow.

func (*T) Fatalf

func (t *T) Fatalf(format string, args ...any)

Fatalf is equivalent to Fatal with formatted message.

func (*T) Log

func (t *T) Log(args ...any)

Log formats its arguments using default formatting, analogous to Println, and records the text in the error log. For tests, the text will be printed only if the test fails or the -test.v flag is set. For benchmarks, the text is always printed to avoid having performance depend on the value of the -test.v flag.

func (*T) Logf

func (t *T) Logf(format string, args ...any)

Logf formats its arguments according to the format, analogous to Printf, and records the text in the error log. A final newline is added if not provided. For tests, the text will be printed only if the test fails or the -test.v flag is set. For benchmarks, the text is always printed to avoid having performance depend on the value of the -test.v flag.

func (*T) Name

func (t *T) Name() string

Name returns the name of the running (sub-) test or benchmark.

The name will include the name of the test along with the names of any nested sub-tests. If two sibling sub-tests have the same name, Name will append a suffix to guarantee the returned name is unique.

func (*T) Parallel

func (t *T) Parallel()

Parallel signals that this test is to be run in parallel with (and only with) other parallel tests. When a test is run multiple times due to use of -test.count or -test.cpu, multiple instances of a single test never run in parallel with each other.

func (*T) Plugin

Plugin implements testoplugin.Plugin.

This is a placeholder to prevent other .Plugin methods being promoted.

func (*T) Setenv

func (t *T) Setenv(key, value string)

Setenv calls os.Setenv(key, value) and uses Cleanup to restore the environment variable to its original value after the test.

Because Setenv affects the whole process, it cannot be used in parallel tests or tests with parallel ancestors.

func (*T) Skip

func (t *T) Skip(args ...any)

Skip is equivalent to Log followed by SkipNow.

func (*T) SkipNow

func (t *T) SkipNow()

SkipNow marks the test as having been skipped and stops its execution by calling runtime.Goexit. If a test fails (see Error, Errorf, Fail) and is then skipped, it is still considered to have failed. Execution will continue at the next test or benchmark. See also FailNow. SkipNow must be called from the goroutine running the test, not from other goroutines created during the test. Calling SkipNow does not stop those other goroutines.

func (*T) Skipf

func (t *T) Skipf(format string, args ...any)

Skipf is equivalent to Skip with formatted message.

func (*T) Skipped

func (t *T) Skipped() bool

Skipped reports whether the test was skipped.

func (*T) TempDir

func (t *T) TempDir() string

TempDir returns a temporary directory for the test to use. The directory is automatically removed when the test and all its subtests complete. Each subsequent call to t.TempDir returns a unique directory; if the directory creation fails, TempDir terminates the test by calling Fatal.

type TestingT

type TestingT interface {
	Run(name string, f func(t *testing.T)) bool
	// contains filtered or unexported methods
}

TestingT is an interface for testing.T.

Directories

Path Synopsis
internal
pragma
Package pragma provides types that can be embedded into a struct to statically enforce or prevent certain language properties.
Package pragma provides types that can be embedded into a struct to statically enforce or prevent certain language properties.
testnamer
Package testnamer copies test naming functionality as seen in go test.
Package testnamer copies test naming functionality as seen in go test.
Package testocache provides caching primitives to be used by external plugins.
Package testocache provides caching primitives to be used by external plugins.
Package testoplugin provides plugin primitives for using plugins in testo.
Package testoplugin provides plugin primitives for using plugins in testo.
Package testoreflect provides reflection primitives for T.
Package testoreflect provides reflection primitives for T.

Jump to

Keyboard shortcuts

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