ozontech / allure-go

Complete Allure provider in Go which doesn't overload the interface usage
https://t.me/allure_go_chat
Apache License 2.0
317 stars 34 forks source link
allure allure-framework go golang qa qaautomation qatools reporting reporting-tool testing

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.

:mortar_board: Head of contents

:zap: 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:

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

Release v0.6.16

:zap: Parametrized tests

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

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

FAQ about table tests:

SuiteResult

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

:information_source: SuiteResult contains information about suite Container and each test's Container and Result.

:sparkles: pkg/allure

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

:sparkles: pkg/framework

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

:white_check_mark: Allure support

:white_check_mark: Suite support

:white_check_mark: Parallel running

:beginner: 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/... 

:wrench: Configure your environment!

Configure Behavior

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

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


:zap: 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:

:zap: 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")
    })

}

:zap: 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")
    })

}

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

:information_source: 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.


:zap: ALLURE_TESTPLAN_PATH - describes path to your test plan json.

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

:smirk: Going Deeper...

pkg/allure

:page_facing_up: pkg/allure documentation

pkg/framework

:page_facing_up: pkg/framework documentation

cute

:full_moon_with_face: You can find cute here!

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

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

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

:school_satchel: Few more examples

Async test

:rocket: YES! We really can run parallel tests in suites.
:rocket: Even with async steps.
:rocket: 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 with nested steps:

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 with Attachment

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:

Run few parallel suites

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:

Setup hooks

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:

XSkip

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:

Parametrized Test

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:

Setup test

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))
}