Skip to content

ozontech/allure-go

Repository files navigation

Allure-Go

Allure-Go - the project that provides a complete allure provider in go, without overloading the interface of usage.
The project started as a fork of testify, but over time it got its own runner and its own features.

🎓 Head of contents

⚡ Features

Providing a separate package allows you to customize your work with allure.

What's new?

Release v0.6.17

WithTestSetup/WithTestTeardown methods

Now you can improve your reports with t.WithTestSetup(func (provider.T)) or t.WithTeardown(func (provider.T)).
If you need some more setup customization, now you can pass your steps to Set up and Tear down right from the test!

Check out our example for more information, or check out example below:

ℹ️ Feature works only with Test. If you try to run it in Before/After hook or inside itself - nothing will happen.
ℹ️ Yes, it works with t.Parallel, but HIGHLY recommended to run t.Parallel AFTER TestSetup.
ℹ️ Yes, it works with TableTest.

Release v0.6.16

⚡ Parametrized tests

New absolutely amazing way to build your table tests. Have a look at here or here.

ℹ️ you need just create parameter field in suite struct and add argument to the test signature (pretty cool, hah?).

FAQ about table tests:
  • ❓ Can I use this amazing feature with my common tests?

  • ℹ️ YES, it works with any other type of suite's tests.

  • ❓Can I use structs, pointers or interfaces as parameters?

  • ℹ️ YES you can (and, I guess, you should) use it with structs and interfaces as params.

  • ❓ Can I use it with TestRunner object?

  • ℹ️ NO, TestRunner object doesn't support this.

SuiteResult

Now suite.RunSuite and runner.RunTests returns new adorable struct SuiteResult to customize your integrations.

ℹ️ SuiteResult contains information about suite Container and each test's Container and Result.

✨ pkg/allure

The package containing the data model for Allure.
Complete list of allure objects:

  • Attachment
  • Container
  • Label
  • Link
  • Parameter
  • Result
  • Step

✨ pkg/framework

The package provides a fully integrated with Allure JUNIT-like framework for working with tests.
Main features:

Allure support

  • Test plan support (Allure TestOps feature)
  • Tests as code
  • Extensive configuration options for test steps
  • Testify's asserts already wrapped with allure.Step!
  • xSkip support (you can mark test as t.XSkip() and it will be skipped on fail)

Suite support

  • Before/After feature
  • Suite as go-struct
  • Suite as sub-test

Parallel running

  • Parallel tests in suite structs
  • Parallel steps in test functions

🔰 Getting Started with framework!

Step 0. Install package

go get github.com/ozontech/allure-go/pkg/framework

No Suite tests

NOTE: No suite tests doesn't support before after hooks

Step 1. Describe tests

package test

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/runner"
)

func TestRunner(t *testing.T) {
	runner.Run(t, "My first test", func(t provider.T) {
		t.NewStep("My First Step!")
	})
	runner.Run(t, "My second test", func(t provider.T) {
		t.WithNewStep("My Second Step!", func(sCtx provider.StepCtx) {
			sCtx.NewStep("My First SubStep!")
		})
	})
}

Step 2. Run it!

go test ./test/... 

Suite

Step 1. Make your first test suite

package tests

import (
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type MyFirstSuite struct {
	suite.Suite
}

Step 2. Extend it with tests

package tests

import (
	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type MyFirstSuite struct {
	suite.Suite
}

func (s *MyFirstSuite) TestMyFirstTest(t provider.T) {
	t.NewStep("My First Step!")
}

func (s *MyFirstSuite) TestMySecondTest(t provider.T) {
	t.WithNewStep("My Second Step!", func(sCtx provider.StepCtx) {
		sCtx.NewStep("My First SubStep!")
	})
}

Step 3. Describe suite runner function

package test

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type MyFirstSuite struct {
	suite.Suite
}

func (s *MyFirstSuite) TestMyFirstTest(t provider.T) {
	t.NewStep("My First Step!")
}

func (s *MyFirstSuite) TestMySecondTest(t provider.T) {
	t.WithNewStep("My Second Step!", func(sCtx provider.StepCtx) {
		sCtx.NewStep("My First SubStep!")
	})
}

func TestSuiteRunner(t *testing.T) {
	suite.RunSuite(t, new(MyFirstSuite))
}

Step 4. Run it!

go test ./test/... 

Runner

Step 1. Init runner object

package test

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/runner"
)

func TestRunner(t *testing.T) {
	r := runner.NewRunner(t, "My First Suite!")
}

Step 2. Extend it with tests

package test

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/runner"
)

func TestRunner(t *testing.T) {
	r := runner.NewRunner(t, "My First Suite!")
	r.NewTest("My first test", func(t provider.T) {
		t.NewStep("My First Step!")
	})

	r.NewTest("My second test", func(t provider.T) {
		t.WithNewStep("My Second Step!", func(sCtx provider.StepCtx) {
			sCtx.NewStep("My First SubStep!")
		})
	})
}

Step 3. Call RunTests function from runner

package test

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/runner"
)

func TestRunner(t *testing.T) {
	r := runner.NewRunner(t, "My First Suite!")
	r.NewTest("My first test", func(t provider.T) {
		t.NewStep("My First Step!")
	})

	r.NewTest("My second test", func(t provider.T) {
		t.WithNewStep("My Second Step!", func(sCtx provider.StepCtx) {
			sCtx.NewStep("My First SubStep!")
		})
	})
	r.RunTests()
}

Step 4. Run it!

go test ./test/... 

🔧 Configure your environment!

Configure Behavior

The path to allure reports is gathered from the two global variables: ${ALLURE_OUTPUT_FOLDER}/${ALLURE_OUTPUT_PATH}

⚡ The ALLURE_OUTPUT_FOLDER is the name of the folder where the allure reports will be stored (by default, allure-results).


⚡ The ALLURE_OUTPUT_PATH is the path where the ALLURE_OUTPUT_FOLDER will be created (by default this is the root folder root folder of the test launcher).


You can also specify several global configurations to integrate with your TMS or Task Tracker:

ALLURE_ISSUE_PATTERN - Specifies the url pattern for your Issues. Has no default value. Mandatory. Must contain %s.

If ALLURE_ISSUE_PATTERN is not specified, the link will be read in its entirety.

Example:

package provider_demo

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/runner"
)

func TestSampleDemo(t *testing.T) {
	runner.Run(t, "Just Link", func(t provider.T) {
		t.SetIssue("https://pkg.go.dev/github.com/stretchr/testify")
	})

	runner.Run(t, "With Pattern", func(t provider.T) {
		_ = os.Setenv("ALLURE_ISSUE_PATTERN", "https://pkg.go.dev/github.com/stretchr/%s")
		t.SetIssue("testify")
	})

}

ALLURE_TESTCASE_PATTERN - Specifies the url pattern for your TestCases. Has no default value. Mandatory. Must contain %s.

If ALLURE_TESTCASE_PATTERN is not specified, the link will be read in its entirety.

Example:

package provider_demo

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/runner"
)

func TestSampleDemo(t *testing.T) {
	runner.Run(t, "Just Link", func(t provider.T) {
		t.SetTestCase("https://pkg.go.dev/github.com/stretchr/testify")
	})

	runner.Run(t, "With Pattern", func(t provider.T) {
		_ = os.Setenv("ALLURE_TESTCASE_PATTERN", "https://pkg.go.dev/github.com/stretchr/%s")
		t.SetTestCase("testify")
	})

}

ALLURE_LAUNCH_TAGS - Sheds a list of tags that will be applied to each test by default. It has no default value.

ℹ️ Tip: ALLURE_LAUNCH_TAGS - Very handy to use with CI/CD. For example, you can define test groups in it by your ci-jobs, or you can roll the name of a branch.


ALLURE_TESTPLAN_PATH - describes path to your test plan json.

ℹ️ Tip: To use this feature you need to work with Allure TestOps

😏 Going Deeper...

pkg/allure

📄 pkg/allure documentation

pkg/framework

📄 pkg/framework documentation

cute

🌝 You can find cute here!

Cute - is library for simply creating HTTP tests in Go with Allure reports.

Main features:
⚡ Full integration with Allure
⚡ Expressive and intuitive syntax
⚡ Built-in JSON support
⚡ Custom asserts
⚡ One step to BDD

Cute can simply improve your allure-go testing experience! Try it 😉

🎒 Few more examples

🚀 YES! We really can run parallel tests in suites.
🚀 Even with async steps.
🚀 Example below.

Test code:

package async

import (
  "fmt"
  "time"
  
  "github.com/ozontech/allure-go/pkg/framework/provider"
  "github.com/ozontech/allure-go/pkg/framework/suite"
)

type AsyncSuiteStepDemo struct {
  suite.Suite
}

func (s *AsyncSuiteStepDemo) BeforeEach(t provider.T) {
  t.Epic("Async")
  t.Feature("Async Steps")
  t.Tags("async", "suite", "steps")
}

func (s *AsyncSuiteStepDemo) TestAsyncStepDemo1(t provider.T) {
  t.Title("Async Test with async steps 1")

  t.Parallel()

  t.WithNewAsyncStep("Async Step 1", func(ctx provider.StepCtx) {
    ctx.WithNewParameters("Start", fmt.Sprintf("%s", time.Now()))
    time.Sleep(3 * time.Second)
    ctx.WithNewParameters("Stop", fmt.Sprintf("%s", time.Now()))
  })

  t.WithNewAsyncStep("Async Step 2", func(ctx provider.StepCtx) {
    ctx.WithNewParameters("Start", fmt.Sprintf("%s", time.Now()))
    time.Sleep(3 * time.Second)
    ctx.Logf("Step 2 Stopped At: %s", fmt.Sprintf("%s", time.Now()))
    ctx.WithNewParameters("Stop", fmt.Sprintf("%s", time.Now()))
  })
}

Allure output:

Test code:

package examples

import (
	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type StepTreeDemoSuite struct {
	suite.Suite
}

func (s *StepTreeDemoSuite) TestInnerSteps(t provider.T) {
	t.Epic("Demo")
	t.Feature("Inner Steps")
	t.Title("Simple Nesting")
	t.Description(`
		Step A is parent step for Step B and Step C
		Call order will be saved in allure report
		A -> (B, C)`)

	t.Tags("Steps", "Nesting")

	t.WithNewStep("Step A", func(ctx provider.StepCtx) {
		ctx.NewStep("Step B")
		ctx.NewStep("Step C")
	})
}

Output to Allure:

Test code:

package examples

import (
	"encoding/json"

	"github.com/ozontech/allure-go/pkg/allure"
	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type JSONStruct struct {
	Message string `json:"message"`
}

type AttachmentTestDemoSuite struct {
	suite.Suite
}

func (s *AttachmentTestDemoSuite) TestAttachment(t provider.T) {
	t.Epic("Demo")
	t.Feature("Attachments")
	t.Title("Test Attachments")
	t.Description(`
		Test's test body and all steps inside can contain attachments`)

	t.Tags("Attachments", "BeforeAfter", "Steps")

	attachmentText := `THIS IS A TEXT ATTACHMENT`
	t.Attachment(allure.NewAttachment("Text Attachment if TestAttachment", allure.Text, []byte(attachmentText)))

	step := allure.NewSimpleStep("Step A")
	var ExampleJson = JSONStruct{"this is JSON message"}
	attachmentJSON, _ := json.Marshal(ExampleJson)
	step.Attachment(allure.NewAttachment("Json Attachment for Step A", allure.JSON, attachmentJSON))
	t.Step(step)
}

Output to Allure:

Test code:

package examples

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type TestRunningDemoSuite struct {
	suite.Suite
}

func (s *TestRunningDemoSuite) TestBeforesAfters(t provider.T) {
	t.Parallel()
	// use RunInner to run suite of tests
	s.RunSuite(t, new(BeforeAfterDemoSuite))
}

func (s *TestRunningDemoSuite) TestFails(t provider.T) {
	t.Parallel()
	s.RunSuite(t, new(FailsDemoSuite))
}

func (s *TestRunningDemoSuite) TestLabels(t provider.T) {
	t.Parallel()
	s.RunSuite(t, new(LabelsDemoSuite))
}

func TestRunDemo(t *testing.T) {
	// use RunSuites to run suite of suites
	suite.RunSuite(t, new(TestRunningDemoSuite))
}

Output to Allure:

Test code:

package examples

import (
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type BeforeAfterDemoSuite struct {
	suite.Suite
}

func (s *BeforeAfterDemoSuite) BeforeEach(t provider.T) {
	t.NewStep("Before Test Step")
}

func (s *BeforeAfterDemoSuite) AfterEach(t provider.T) {
	t.NewStep("After Test Step")
}

func (s *BeforeAfterDemoSuite) BeforeAll(t provider.T) {
	t.NewStep("Before suite Step")
}

func (s *BeforeAfterDemoSuite) AfterAll(t provider.T) {
	t.NewStep("After suite Step")
}

func (s *BeforeAfterDemoSuite) TestBeforeAfterTest(t provider.T) {
	t.Epic("Demo")
	t.Feature("BeforeAfter")
	t.Title("Test wrapped with SetUp & TearDown")
	t.Description(`
		This test wrapped with SetUp and TearDown containert.`)

	t.Tags("BeforeAfter")
}

func TestBeforesAfters(t *testing.T) {
	t.Parallel()
	suite.RunSuite(t, new(BeforeAfterDemoSuite))
}

Output to Allure:

Test code:

package examples

import (
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type DemoSuite struct {
	suite.Suite
}

func (s *DemoSuite) TestXSkipFail(t provider.T) {
	t.Title("This test skipped by assert with message")
	t.Description(`
		This Test will be skipped with assert Error.
		Error text: Assertion Failed`)
	t.Tags("fail", "xskip", "assertions")

	t.XSkip()
	t.Require().Equal(1, 2, "Assertion Failed")
}

func TestDemoSuite(t *testing.T) {
	t.Parallel()
	suite.RunSuite(t, new(DemoSuite))
}

Output to Allure:

Test code:

package suite_demo

import (
  "testing"

  "github.com/jackc/fake"
  "github.com/ozontech/allure-go/pkg/framework/provider"
  "github.com/ozontech/allure-go/pkg/framework/suite"
)

type ParametrizedSuite struct {
  suite.Suite
  ParamCities []string
}

func (s *ParametrizedSuite) BeforeAll(t provider.T) {
  for i := 0; i < 10; i++ {
    s.ParamCities = append(s.ParamCities, fake.City())
  }
}

func (s *ParametrizedSuite) TableTestCities(t provider.T, city string) {
  t.Parallel()
  t.Require().NotEmpty(city)
}

func TestNewParametrizedDemo(t *testing.T) {
  suite.RunSuite(t, new(ParametrizedSuite))
}

Output to Allure:

This feature allows you to extend your test setting up/tearing down functional

Test code:

package suite_demo

import (
	"context"
	"testing"

	"github.com/ozontech/allure-go/pkg/framework/provider"
	"github.com/ozontech/allure-go/pkg/framework/suite"
)

type SetupSuite struct {
	suite.Suite
}

func (s *SetupSuite) TestMyTest(t provider.T) {
	var (
		v1  string
		v2  int
		ctx context.Context
	)
	t.WithTestSetup(func(t provider.T) {
		t.WithNewStep("init v1", func(sCtx provider.StepCtx) {
			v1 = "string"
			sCtx.WithNewParameters("v1", v1)
		})
		t.WithNewStep("init v2", func(sCtx provider.StepCtx) {
			v2 = 123
			sCtx.WithNewParameters("v2", v2)
		})
		t.WithNewStep("init ctx", func(sCtx provider.StepCtx) {
			ctx = context.Background()
			sCtx.WithNewParameters("ctx", ctx)
		})
	})
	defer t.WithTestTeardown(func(t provider.T) {
		t.WithNewStep("Close ctx", func(sCtx provider.StepCtx) {
			ctx.Done()
			sCtx.WithNewParameters("ctx", ctx)
		})
	})
}

func TestRunner(t *testing.T) {
	suite.RunSuite(t, new(SetupSuite))
}

Allure output:

Prevent loosing allureID in test results

When suit fails at the setup stage (beforeAll), report will not contain ALLURE_ID field. To prevent it you can use GetAllureID(testName string) string method for common tests and InitializeTestsParams() method for parametrized tests.

Example for GetAllureID method:

package suite_demo

import (
  "testing"

  "github.com/ozontech/allure-go/pkg/framework/provider"
  "github.com/ozontech/allure-go/pkg/framework/suite"
)

type AllureIDSuite struct {
  suite.Suite
}

func (testSuit *AllureIDSuite) GetAllureID(testName string) string {
  switch testName {
  case "TestWithAllureIDFirst":
    return "9001"
  case "TestWithAllureIDSecond":
    return "9002"
  default:
    return ""
  }
}

func (s *AllureIDSuite) BeforeAll(t provider.T) {
  // code that can fail here
}

func (s *AllureIDSuite) TestWithAllureIDFirst(t provider.T) {
  // code of your test here
}

func (s *AllureIDSuite) TestWithAllureIDSecond(t provider.T) {
  // code of your test here
}

func TestNewDemo(t *testing.T) {
  suite.RunSuite(t, new(AllureIDSuite))
}

Example for InitializeTestsParams method:

package suite_demo

import (
  "testing"

  "github.com/jackc/fake"
  "github.com/ozontech/allure-go/pkg/framework/provider"
  "github.com/ozontech/allure-go/pkg/framework/suite"
)

type CitiesParam struct {
	allureID    string
	title       string
	value       string
}

func (p CitiesParam) GetAllureID() string {
  return p.allureID
}
func (p CitiesParam) GetAllureTitle() string {
  return p.title
}

type ParametrizedSuite struct {
  suite.Suite
  ParamCities []CitiesParam
}

func (s *ParametrizedSuite) InitializeTestsParams() {
  s.ParamCities = make([]CitiesParam, 2)
  s.ParamCities[0] = CitiesParam{
    title:    "Title for city test #1",
    allureID: "101",
    value:    fake.City(),
  }

  s.ParamCities[1] = CitiesParam{
    title:    "Title for city test #2",
    allureID: "102",
    value:    fake.City(),
  }
}


func (s *ParametrizedSuite) BeforeAll(t provider.T) {
  // setup suit here
}

func (s *ParametrizedSuite) TableTestCities(t provider.T, city CitiesParam) {
  t.Parallel()
  t.Require().NotEmpty(city.value)
}

func TestNewParametrizedDemo(t *testing.T) {
  suite.RunSuite(t, new(ParametrizedSuite))
}