scenarigo

package module
v0.13.3-alpha.2 Latest Latest
Warning

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

Go to latest
Published: Feb 10, 2023 License: Apache-2.0 Imports: 24 Imported by: 0

README

Scenarigo

A scenario-based API testing tool for HTTP/gRPC server.

godoc test codecov go report codebeat License

Overview

Scenarigo is a scenario-based API testing tool for HTTP/gRPC server. It is written in Go and provides a plugin feature that enables you to extend by writing Go code. You can write test scenarios as YAML files and executes them.

title: get scenarigo repository
steps:
- title: GET https://api.github.com/repos/zoncoen/scenarigo
  vars:
    user: zoncoen
    repo: scenarigo
  protocol: http
  request:
    method: GET
    url: "https://api.github.com/repos/{{vars.user}}/{{vars.repo}}"
  expect:
    code: OK
    body:
      name: "{{vars.repo}}"

Installation

go install command (recommend)

$ go install github.com/bilus/scenarigo/cmd/scenarigo@v0.13.2

from release page

Go to the releases page and download the zip file. Unpack the zip file, and put the binary to a directory in your $PATH.

You can download the latest command into the ./scenarigo directory with the following one-liner code. Place the binary ./scenarigo/scenarigo into your $PATH.

$ version=$(curl -s https://api.github.com/repos/zoncoen/scenarigo/releases/latest | jq -r '.tag_name') && \
    go_version='go1.19.1' && \
    curl -sLJ https://github.com/bilus/scenarigo/releases/download/${version}/scenarigo_${version}_${go_version}_$(uname)_$(uname -m).tar.gz -o scenarigo.tar.gz && \
    mkdir ./scenarigo && tar -zxvf ./scenarigo.tar.gz -C ./scenarigo && rm scenarigo.tar.gz

Notes: If you use the plugin mechanism, the scenarigo command and plugins must be built using the same version of Go.

Setup

You can generate a configuration file scenarigo.yaml via the following command.

$ scenarigo config init
schemaVersion: config/v1

scenarios: [] # Specify test scenario files and directories.

pluginDirectory: ./gen    # Specify the root directory of plugins.
plugins:                  # Specify configurations to build plugins.
  plugin.so:              # Map keys specify plugin output file path from the root directory of plugins.
    src: ./path/to/plugin # Specify the source file, directory, or "go gettable" module path of the plugin.

output:
  verbose: false # Enable verbose output.
  colored: false # Enable colored output with ANSI color escape codes. It is enabled by default but disabled when a NO_COLOR environment variable is set (regardless of its value).
  report:
    json:
      filename: ./report.json # Specify a filename for test report output in JSON.
    junit:
      filename: ./junit.xml   # Specify a filename for test report output in JUnit XML format.

Usage

scenarigo run executes test scenarios based on the configuration file.

schemaVersion: config/v1

scenarios:
- github.yaml
title: get scenarigo repository
steps:
- title: GET https://api.github.com/repos/zoncoen/scenarigo
  vars:
    user: zoncoen
    repo: scenarigo
  protocol: http
  request:
    method: GET
    url: "https://api.github.com/repos/{{vars.user}}/{{vars.repo}}"
  expect:
    code: OK
    body:
      name: "{{vars.repo}}"
$ scenarigo run
ok      github.yaml     0.068s

You can see all commands and options by scenarigo help.

scenarigo is a scenario-based API testing tool.

Usage:
  scenarigo [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  config      manage the scenarigo configuration file
  help        Help about any command
  list        list the test scenario files
  plugin      provide operations for plugins
  run         run test scenarios
  version     print scenarigo version

Flags:
  -c, --config string   specify configuration file path (read configuration from stdin if specified "-")
  -h, --help            help for scenarigo
      --root string     specify root directory (default value is the directory of configuration file)

Use "scenarigo [command] --help" for more information about a command.

How to write test scenarios

You can write test scenarios easily in YAML.

Send HTTP requests

A test scenario consists of some steps. A step represents an API request. The scenario steps will be run from top to bottom sequentially. This simple example has a step that sends a GET request to http://example.com/message.

title: check /message
steps:
- title: GET /message
  protocol: http
  request:
    method: GET
    url: http://example.com/message

To send a query parameter, add it directly to the URL or use the query field.

title: check /message
steps:
- title: GET /message
  protocol: http
  request:
    method: GET
    url: http://example.com/message
    query:
      id: 1

You can use other methods to send data to your APIs.

title: check /message
steps:
- title: POST /message
  protocol: http
  request:
    method: POST
    url: http://example.com/message
    body:
      message: hello

By default, Scenarigo will send body data as JSON. If you want to use other formats, set the Content-Type header.

title: check /message
steps:
- title: POST /message
  protocol: http
  request:
    method: POST
    url: http://example.com/message
    header:
      Content-Type: application/x-www-form-urlencoded
    body:
      message: hello

Available Content-Type header to encode request body is the following.

  • application/json (default)
  • text/plain
  • application/x-www-form-urlencoded

Check HTTP responses

You can test your APIs by checking responses. If the result differs expected values, Scenarigo aborts the execution of the test scenario and notify the error.

title: check /message
steps:
- title: GET /message
  protocol: http
  request:
    method: GET
    url: http://example.com/message
    query:
      id: 1
  expect:
    code: OK
    header:
      Content-Type: application/json; charset=utf-8
    body:
      id: 1
      message: hello

Template string

Scenarigo provides the original template string feature. It enables to store and reuse values in test scenarios. The vars field defines variables that can be referred by template string like '{{vars.id}}'.

title: check /message
vars:
  id: 1
steps:
- title: GET /message
  protocol: http
  request:
    method: GET
    url: http://example.com/message
    query:
      id: '{{vars.id}}'

You can define "step" scope variables that can't be accessed from other steps.

title: check /message
steps:
- title: GET /message
  vars:
  - 1
  protocol: http
  request:
    method: GET
    url: http://example.com/message
    query:
      id: '{{vars[0]}}'

Plugin

Scenarigo has a plugin mechanism that enables you to add new functionalities you need by writing Go code. This feature is based on Go's standard library plugin, which has the following limitations.

  • Supported on Linux, FreeBSD, and macOS only.
  • All plugins (and installed scenarigo command) must be built with the same version of the Go compiler and dependent packages.

Scenarigo loads built plugins at runtime and accesses any exported variable or function via template string.

See the official document for details of the plugin package.

How to write plugins

A Go plugin is a main package with exported variables and functions.

package main

import "time"

var Layout = "2006-01-02"

func Today() string {
	return time.Now().Format(Layout)
}

You can use the variables and functions via template strings like below in your test scenarios.

  • {{plugins.date.Layout}} => "2006-01-02"
  • {{plugins.date.Today()}} => "2022-02-22"

Scenarigo allows functions to return a value or a value and an error. The template string execution will fail if the function returns a non-nil error.

package main

import "time"

var Layout = "2006-01-02"

func TodayIn(s string) (string, error) {
	loc, err := time.LoadLocation(s)
	if err != nil {
		return "", err
	}
	return time.Now().In(loc).Format(Layout), nil
}
  • {{plugins.date.TodayIn("UTC")}} => "2022-02-22"
  • {{plugins.date.TodayIn("INVALID")}} => failed to execute: {{plugins.date.TodayIn("INVALID")}}: unknown time zone INVALID

How to build plugins

Go plugin can be built with go build -buildmode=plugin, but we recommend you use scenarigo plugin build instead. The wrapper command requires go command installed in your machine.

Scenarigo builds plugins according to the configuration.

schemaVersion: config/v1

scenarios:
- scenarios

pluginDirectory: ./gen  # Specify the root directory of plugins.
plugins:                # Specify configurations to build plugins.
  date.so:              # Map keys specify plugin output file path from the root directory of plugins.
    src: ./plugins/date # Specify the source file, directory, or "go gettable" module path of the plugin.
.
├── plugins
│   └── date
│       └── main.go
├── scenarigo.yaml
└── scenarios
    └── echo.yaml

In this case, the plugin will be built and written to date.so.

$ scenarigo plugin build
.
├── gen
│   └── date.so     # built plugin
├── plugins
│   └── date
│       ├── go.mod  # generated automatically if not exists
│       └── main.go
├── scenarigo.yaml
└── scenarios
    └── echo.yaml

Scenarigo checks the dependent packages of each plugin before building. If the plugins depend on a different version of the same package, Scenarigo overrides go.mod files by the maximum version to avoid the build error.

Now you can use the plugin in test scenarios.

title: echo
plugins:
  date: date.so # relative path from "pluginDirectory"
steps:
- title: POST /echo
  protocol: http
  request:
    method: POST
    url: 'http://{{env.ECHO_ADDR}}/echo'
    body:
      message: '{{plugins.date.Today()}}'
  expect:
    code: 200

Scenarigo can download source codes from remote repositories and build it with go get-able module query.

plugins:
  uuid.so:
    src: github.com/zoncoen-sample/scenarigo-plugins/uuid@latest

Advanced features

Setup Funciton

plugin.RegisterSetup registers a setup function that will be called before running scenario tests once only. If the registered function returns a non-nil function as a second returned value, it will be executed after finished all tests.

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/bilus/scenarigo/plugin"

	secretmanager "cloud.google.com/go/secretmanager/apiv1"
	secretmanagerpb "google.golang.org/genproto/googleapis/cloud/secretmanager/v1"
)

const (
	projectName = "foo"
)

func init() {
	plugin.RegisterSetup(setupClient)
}

var client *secretmanager.Client

func setupClient(ctx *plugin.Context) (*plugin.Context, func(*plugin.Context)) {
	var err error
	client, err = secretmanager.NewClient(context.Background())
	if err != nil {
		ctx.Reporter().Fatalf("failed to create secretmanager client: %v", err)
	}
	return ctx, func(ctx *plugin.Context) {
		client.Close()
	}
}

func GetSecretString(name string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	resp, err := client.AccessSecretVersion(ctx, &secretmanagerpb.AccessSecretVersionRequest{
		Name: fmt.Sprintf("projects/%s/secrets/%s/versions/latest", projectName, name),
	})
	if err != nil {
		return "", fmt.Errorf("failed to get secret: %v", err)
	}
	return string(resp.Payload.Data), nil
}
plugins:
  setup.so:
    src: ./plugins/date # call "setupClient" before running test scenarios

Similarly, plugin.RegisterSetupEachScenario can register a setup function. The registered function will be called before each test scenario that uses the plugin.

package main

import (
	"github.com/bilus/scenarigo/plugin"

	"github.com/google/uuid"
)

func init() {
	plugin.RegisterSetupEachScenario(setRunID)
}

func setRunID(ctx *plugin.Context) (*plugin.Context, func(*plugin.Context)) {
	return ctx.WithVars(map[string]string{
		"runId": uuid.NewString(),
	}), nil
}
title: echo
plugins:
  setup: setup.so # call "setRunID" before running this test scenario
steps:
- title: POST /echo
  protocol: http
  request:
    method: POST
    url: 'http://{{env.ECHO_ADDR}}/echo'
    header:
      Run-Id: '{{vars.runId}}'
    body:
      message: hello
  expect:
    code: 200
Custom Step Function

Generally, a step represents sending a request in Scenarigo. However, you can use a Go's function as a step with the plugin.

package main

import (
	"github.com/bilus/scenarigo/plugin"
	"github.com/bilus/scenarigo/schema"
)

var Nop = plugin.StepFunc(func(ctx *plugin.Context, step *schema.Step) *plugin.Context {
	ctx.Reporter().Log("nop step")
	return ctx
})
title: nop
plugins:
  step: step.so
steps:
- title: nop step
  ref: '{{plugins.step.Nop}}'
Left Arrow Function (a function takes arguments in YAML)

Scenarigo enables you to define a function that takes arguments in YAML for readability. It is called the "Left Arrow Function" since its syntax {{funcName <-}}.

package main

import (
	"errors"
	"fmt"

	"github.com/bilus/scenarigo/plugin"
)

var CoolFunc plugin.LeftArrowFunc = &fn{}

type fn struct{}

type arg struct {
	Foo string `yaml:"foo"`
	Bar string `yaml:"bar"`
	Baz string `yaml:"baz"`
}

func (_ *fn) UnmarshalArg(unmarshal func(interface{}) error) (interface{}, error) {
	var a arg
	if err := unmarshal(&a); err != nil {
		return nil, err
	}
	return &a, nil
}

func (_ *fn) Exec(in interface{}) (interface{}, error) {
	a, ok := in.(*arg)
	if !ok {
		return nil, errors.New("arg must be a arg")
	}
	return fmt.Sprintf("foo: %s, bar: %s, baz: %s", a.Foo, a.Bar, a.Baz), nil
}
title: echo
plugins:
  cool: cool.so
steps:
- title: POST /echo
  protocol: http
  request:
    method: POST
    url: 'http://{{env.ECHO_ADDR}}/echo'
    body:
      message:
        '{{plugins.cool.CoolFunc <-}}':
          foo: 1
          bar: 2
          baz: 3
  expect:
    code: 200

Documentation

Overview

Package scenarigo provides end-to-end scenario testing framework for APIs. It loads YAML files as scenarios and executes them.

Index

Constants

This section is empty.

Variables

View Source
var GoModBytes []byte

Functions

func RunScenario

func RunScenario(ctx *context.Context, s *schema.Scenario) *context.Context

RunScenario runs a test scenario s.

func WithConfig

func WithConfig(config *schema.Config) func(*Runner) error

WithConfig returns a option which sets configuration.

func WithOptionsFromEnv

func WithOptionsFromEnv(isEnv bool) func(*Runner) error

WithOptionsFromEnv returns a option which sets flag whether accepts configuration from ENV. Currently Available ENV variables are the following.

  • SCENARIGO_COLOR=(1|true|TRUE)

func WithPluginDir

func WithPluginDir(path string) func(*Runner) error

WithPluginDir returns a option which sets plugin root directory.

func WithScenarios

func WithScenarios(paths ...string) func(*Runner) error

WithScenarios returns a option which finds and sets test scenario files.

func WithScenariosFromReader

func WithScenariosFromReader(readers ...io.Reader) func(*Runner) error

WithScenariosFromReader returns a option which sets readers to read scenario contents.

Types

type Runner

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

Runner represents a test runner.

func NewRunner

func NewRunner(opts ...func(*Runner) error) (*Runner, error)

NewRunner returns a new test runner.

func (*Runner) CreateTestReport

func (r *Runner) CreateTestReport(rptr reporter.Reporter) error

CreateTestReport creates test reports.

func (*Runner) Run

func (r *Runner) Run(ctx *context.Context)

Run runs all tests.

func (*Runner) ScenarioFiles

func (r *Runner) ScenarioFiles() []string

ScenarioFiles returns all scenario file paths.

Directories

Path Synopsis
Package assert provides value assertions.
Package assert provides value assertions.
cmd
Package context provides the test context of scenarigo.
Package context provides the test context of scenarigo.
internal
Package protocol provides defines APIs of protocol.
Package protocol provides defines APIs of protocol.
Package reporter provides test result reporters.
Package reporter provides test result reporters.
Package schema provides the test scenario data schema for scenarigo.
Package schema provides the test scenario data schema for scenarigo.
scripts
cross-build Module
Package template implements data-driven templates for generating a value.
Package template implements data-driven templates for generating a value.
ast
Package ast declares the types used to represent syntax trees.
Package ast declares the types used to represent syntax trees.
parser
Package parser implements a parser for a template string.
Package parser implements a parser for a template string.
token
Package token defines constants representing the lexical tokens.
Package token defines constants representing the lexical tokens.

Jump to

Keyboard shortcuts

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