Provide a useful implementation of something compatible with testing.T (#571)
* Attempting to provide a useful implementation of something compatible with testing.T * Handle Fail calls on the TestingT in the right place * Provide as much of testing.T as possible + tidy up * Add initial tests for testingT support * Check compatibility with testing.T and friends Co-authored-by: Piotr Bocheński <bochenski.piotr@gmail.com> * Update assert-godogs example to show new usage. Rename 'GetTestingT(ctx)' to 'T(ctx)' * Update changelog and readme with new usage * Improve test coverage * Review updates --------- Co-authored-by: Piotr Bocheński <bochenski.piotr@gmail.com>
Этот коммит содержится в:
родитель
10407bc5a9
коммит
7017c73ef8
12 изменённых файлов: 562 добавлений и 90 удалений
|
@ -8,6 +8,9 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
- Provide testing.T-compatible interface on test context, allowing usage of assertion libraries such as testify's assert/require - ([571](https://github.com/cucumber/godog/pull/571) - [mrsheepuk](https://github.com/mrsheepuk))
|
||||
|
||||
## [v0.14.0]
|
||||
### Added
|
||||
- Improve ErrSkip handling, add test for Summary and operations order ([584](https://github.com/cucumber/godog/pull/584) - [vearutop](https://github.com/vearutop))
|
||||
|
|
27
README.md
27
README.md
|
@ -495,31 +495,12 @@ If you want to filter scenarios by tags, you can use the `-t=<expression>` or `-
|
|||
A more extensive example can be [found here](/_examples/assert-godogs).
|
||||
|
||||
```go
|
||||
func thereShouldBeRemaining(remaining int) error {
|
||||
return assertExpectedAndActual(
|
||||
assert.Equal, Godogs, remaining,
|
||||
func thereShouldBeRemaining(ctx context.Context, remaining int) error {
|
||||
assert.Equal(
|
||||
godog.T(ctx), Godogs, remaining,
|
||||
"Expected %d godogs to be remaining, but there is %d", remaining, Godogs,
|
||||
)
|
||||
}
|
||||
|
||||
// assertExpectedAndActual is a helper function to allow the step function to call
|
||||
// assertion functions where you want to compare an expected and an actual value.
|
||||
func assertExpectedAndActual(a expectedAndActualAssertion, expected, actual interface{}, msgAndArgs ...interface{}) error {
|
||||
var t asserter
|
||||
a(&t, expected, actual, msgAndArgs...)
|
||||
return t.err
|
||||
}
|
||||
|
||||
type expectedAndActualAssertion func(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
|
||||
|
||||
// asserter is used to be able to retrieve the error reported by the called assertion
|
||||
type asserter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Errorf is used by the called assertion to report an error
|
||||
func (a *asserter) Errorf(format string, args ...interface{}) {
|
||||
a.err = fmt.Errorf(format, args...)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
@ -36,31 +35,22 @@ func thereAreGodogs(available int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func iEat(num int) error {
|
||||
err := assertExpectedAndActual(
|
||||
assert.GreaterOrEqual, Godogs, num,
|
||||
"You cannot eat %d godogs, there are %d available", num, Godogs,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
func iEat(ctx context.Context, num int) error {
|
||||
if !assert.GreaterOrEqual(godog.T(ctx), Godogs, num, "You cannot eat %d godogs, there are %d available", num, Godogs) {
|
||||
return nil
|
||||
}
|
||||
|
||||
Godogs -= num
|
||||
return nil
|
||||
}
|
||||
|
||||
func thereShouldBeRemaining(remaining int) error {
|
||||
return assertExpectedAndActual(
|
||||
assert.Equal, Godogs, remaining,
|
||||
"Expected %d godogs to be remaining, but there is %d", remaining, Godogs,
|
||||
)
|
||||
func thereShouldBeRemaining(ctx context.Context, remaining int) error {
|
||||
assert.Equal(godog.T(ctx), Godogs, remaining, "Expected %d godogs to be remaining, but there is %d", remaining, Godogs)
|
||||
return nil
|
||||
}
|
||||
|
||||
func thereShouldBeNoneRemaining() error {
|
||||
return assertActual(
|
||||
assert.Empty, Godogs,
|
||||
"Expected none godogs to be remaining, but there is %d", Godogs,
|
||||
)
|
||||
func thereShouldBeNoneRemaining(ctx context.Context) error {
|
||||
assert.Empty(godog.T(ctx), Godogs, "Expected none godogs to be remaining, but there is %d", Godogs)
|
||||
return nil
|
||||
}
|
||||
|
||||
func InitializeScenario(ctx *godog.ScenarioContext) {
|
||||
|
@ -74,34 +64,3 @@ func InitializeScenario(ctx *godog.ScenarioContext) {
|
|||
ctx.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||
ctx.Step(`^there should be none remaining$`, thereShouldBeNoneRemaining)
|
||||
}
|
||||
|
||||
// assertExpectedAndActual is a helper function to allow the step function to call
|
||||
// assertion functions where you want to compare an expected and an actual value.
|
||||
func assertExpectedAndActual(a expectedAndActualAssertion, expected, actual interface{}, msgAndArgs ...interface{}) error {
|
||||
var t asserter
|
||||
a(&t, expected, actual, msgAndArgs...)
|
||||
return t.err
|
||||
}
|
||||
|
||||
type expectedAndActualAssertion func(t assert.TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool
|
||||
|
||||
// assertActual is a helper function to allow the step function to call
|
||||
// assertion functions where you want to compare an actual value to a
|
||||
// predined state like nil, empty or true/false.
|
||||
func assertActual(a actualAssertion, actual interface{}, msgAndArgs ...interface{}) error {
|
||||
var t asserter
|
||||
a(&t, actual, msgAndArgs...)
|
||||
return t.err
|
||||
}
|
||||
|
||||
type actualAssertion func(t assert.TestingT, actual interface{}, msgAndArgs ...interface{}) bool
|
||||
|
||||
// asserter is used to be able to retrieve the error reported by the called assertion
|
||||
type asserter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// Errorf is used by the called assertion to report an error
|
||||
func (a *asserter) Errorf(format string, args ...interface{}) {
|
||||
a.err = fmt.Errorf(format, args...)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ Feature: event stream formatter
|
|||
"""
|
||||
|
||||
Scenario: should process simple scenario
|
||||
Given a feature path "features/load.feature:26"
|
||||
Given a feature path "features/load.feature:27"
|
||||
When I run feature suite with formatter "events"
|
||||
Then the following events should be fired:
|
||||
"""
|
||||
|
@ -34,7 +34,7 @@ Feature: event stream formatter
|
|||
"""
|
||||
|
||||
Scenario: should process outline scenario
|
||||
Given a feature path "features/load.feature:34"
|
||||
Given a feature path "features/load.feature:35"
|
||||
When I run feature suite with formatter "events"
|
||||
Then the following events should be fired:
|
||||
"""
|
||||
|
|
|
@ -350,7 +350,7 @@ Feature: pretty formatter
|
|||
Scenario: Should scenarios identified with path:line and preserve the order.
|
||||
Given a feature path "features/load.feature:6"
|
||||
And a feature path "features/multistep.feature:6"
|
||||
And a feature path "features/load.feature:26"
|
||||
And a feature path "features/load.feature:27"
|
||||
And a feature path "features/multistep.feature:23"
|
||||
When I run feature suite with formatter "pretty"
|
||||
Then the rendered output will be as follows:
|
||||
|
@ -363,7 +363,7 @@ Feature: pretty formatter
|
|||
Scenario: load features within path # features/load.feature:6
|
||||
Given a feature path "features" # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||
When I parse features # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||
Then I should have 13 feature files: # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||
Then I should have 14 feature files: # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||
\"\"\"
|
||||
features/background.feature
|
||||
features/events.feature
|
||||
|
@ -378,6 +378,7 @@ Feature: pretty formatter
|
|||
features/run.feature
|
||||
features/snippets.feature
|
||||
features/tags.feature
|
||||
features/testingt.feature
|
||||
\"\"\"
|
||||
|
||||
Feature: run features with nested steps
|
||||
|
@ -407,7 +408,7 @@ Feature: pretty formatter
|
|||
As a test suite
|
||||
I need to be able to load features
|
||||
|
||||
Scenario: load a specific feature file # features/load.feature:26
|
||||
Scenario: load a specific feature file # features/load.feature:27
|
||||
Given a feature path "features/load.feature" # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||
When I parse features # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||
Then I should have 1 feature file: # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||
|
|
|
@ -8,7 +8,7 @@ Savybė: užkrauti savybes
|
|||
Scenarijus: savybių užkrovimas iš aplanko
|
||||
Duota savybių aplankas "features"
|
||||
Kai aš išskaitau savybes
|
||||
Tada aš turėčiau turėti 13 savybių failus:
|
||||
Tada aš turėčiau turėti 14 savybių failus:
|
||||
"""
|
||||
features/background.feature
|
||||
features/events.feature
|
||||
|
@ -23,4 +23,5 @@ Savybė: užkrauti savybes
|
|||
features/run.feature
|
||||
features/snippets.feature
|
||||
features/tags.feature
|
||||
features/testingt.feature
|
||||
"""
|
||||
|
|
|
@ -6,7 +6,7 @@ Feature: load features
|
|||
Scenario: load features within path
|
||||
Given a feature path "features"
|
||||
When I parse features
|
||||
Then I should have 13 feature files:
|
||||
Then I should have 14 feature files:
|
||||
"""
|
||||
features/background.feature
|
||||
features/events.feature
|
||||
|
@ -21,6 +21,7 @@ Feature: load features
|
|||
features/run.feature
|
||||
features/snippets.feature
|
||||
features/tags.feature
|
||||
features/testingt.feature
|
||||
"""
|
||||
|
||||
Scenario: load a specific feature file
|
||||
|
|
194
features/testingt.feature
Обычный файл
194
features/testingt.feature
Обычный файл
|
@ -0,0 +1,194 @@
|
|||
Feature: providing testingT compatibility
|
||||
In order to test application behavior using standard go assertion techniques
|
||||
As a test suite
|
||||
I need to be able to provide a testing.T compatible interface
|
||||
|
||||
Scenario Outline: should fail test with no message if <op> called on testing T
|
||||
Given a feature "failed.feature" file:
|
||||
"""
|
||||
Feature: failed feature
|
||||
|
||||
Scenario: fail a scenario
|
||||
Given passing step
|
||||
When my step fails the test by calling <op> on testing T
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have failed
|
||||
And the following steps should be passed:
|
||||
"""
|
||||
passing step
|
||||
"""
|
||||
And the following step should be failed:
|
||||
"""
|
||||
my step fails the test by calling <op> on testing T
|
||||
"""
|
||||
Examples:
|
||||
| op |
|
||||
| Fail |
|
||||
| FailNow |
|
||||
|
||||
Scenario Outline: should fail test with message if <op> called on T
|
||||
Given a feature "failed.feature" file:
|
||||
"""
|
||||
Feature: failed feature
|
||||
|
||||
Scenario: fail a scenario
|
||||
Given passing step
|
||||
When my step fails the test by calling <op> on testing T with message "an unformatted message"
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have failed
|
||||
And the following steps should be passed:
|
||||
"""
|
||||
passing step
|
||||
"""
|
||||
And the following step should be failed:
|
||||
"""
|
||||
my step fails the test by calling <op> on testing T with message "an unformatted message"
|
||||
"""
|
||||
Examples:
|
||||
| op |
|
||||
| Error |
|
||||
| Fatal |
|
||||
|
||||
|
||||
Scenario Outline: should fail test with formatted message if <op> called on T
|
||||
Given a feature "failed.feature" file:
|
||||
"""
|
||||
Feature: failed feature
|
||||
|
||||
Scenario: fail a scenario
|
||||
Given passing step
|
||||
When my step fails the test by calling <op> on testing T with message "a formatted message %s" and argument "arg1"
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have failed
|
||||
And the following steps should be passed:
|
||||
"""
|
||||
passing step
|
||||
"""
|
||||
And the following step should be failed:
|
||||
"""
|
||||
my step fails the test by calling <op> on testing T with message "a formatted message %s" and argument "arg1"
|
||||
"""
|
||||
Examples:
|
||||
| op |
|
||||
| Errorf |
|
||||
| Fatalf |
|
||||
|
||||
Scenario: should pass test when testify assertions pass
|
||||
Given a feature "testify.feature" file:
|
||||
"""
|
||||
Feature: passed feature
|
||||
|
||||
Scenario: pass a scenario
|
||||
Given passing step
|
||||
When my step calls testify's assert.Equal with expected "exp" and actual "exp"
|
||||
When my step calls testify's require.Equal with expected "exp" and actual "exp"
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have passed
|
||||
And the following steps should be passed:
|
||||
"""
|
||||
passing step
|
||||
my step calls testify's assert.Equal with expected "exp" and actual "exp"
|
||||
my step calls testify's require.Equal with expected "exp" and actual "exp"
|
||||
"""
|
||||
|
||||
Scenario: should fail test when testify assertions do not pass
|
||||
Given a feature "testify.feature" file:
|
||||
"""
|
||||
Feature: failed feature
|
||||
|
||||
Scenario: fail a scenario
|
||||
Given passing step
|
||||
When my step calls testify's assert.Equal with expected "exp" and actual "not"
|
||||
And my step calls testify's assert.Equal with expected "exp2" and actual "not"
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have failed
|
||||
And the following steps should be passed:
|
||||
"""
|
||||
passing step
|
||||
"""
|
||||
And the following steps should be failed:
|
||||
"""
|
||||
my step calls testify's assert.Equal with expected "exp" and actual "not"
|
||||
"""
|
||||
And the following steps should be skipped:
|
||||
"""
|
||||
my step calls testify's assert.Equal with expected "exp2" and actual "not"
|
||||
"""
|
||||
|
||||
Scenario: should fail test when multiple testify assertions are used in a step
|
||||
Given a feature "testify.feature" file:
|
||||
"""
|
||||
Feature: failed feature
|
||||
|
||||
Scenario: fail a scenario
|
||||
Given passing step
|
||||
When my step calls testify's assert.Equal 3 times
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have failed
|
||||
And the following steps should be passed:
|
||||
"""
|
||||
passing step
|
||||
"""
|
||||
And the following steps should be failed:
|
||||
"""
|
||||
my step calls testify's assert.Equal 3 times
|
||||
"""
|
||||
|
||||
Scenario: should pass test when multiple testify assertions are used successfully in a step
|
||||
Given a feature "testify.feature" file:
|
||||
"""
|
||||
Feature: passed feature
|
||||
|
||||
Scenario: pass a scenario
|
||||
Given passing step
|
||||
When my step calls testify's assert.Equal 3 times with match
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have passed
|
||||
And the following steps should be passed:
|
||||
"""
|
||||
passing step
|
||||
my step calls testify's assert.Equal 3 times with match
|
||||
"""
|
||||
|
||||
Scenario Outline: should skip test when <op> is called on the testing.T
|
||||
Given a feature "testify.feature" file:
|
||||
"""
|
||||
Feature: skipped feature
|
||||
|
||||
Scenario: skip a scenario
|
||||
Given passing step
|
||||
When my step skips the test by calling <op> on testing T
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have passed
|
||||
And the following steps should be passed:
|
||||
"""
|
||||
passing step
|
||||
"""
|
||||
And the following steps should be skipped:
|
||||
"""
|
||||
my step skips the test by calling <op> on testing T
|
||||
"""
|
||||
Examples:
|
||||
| op |
|
||||
| Skip |
|
||||
| SkipNow |
|
||||
|
||||
Scenario: should log when Logf/Log called on testing.T
|
||||
When my step calls Logf on testing T with message "format this %s" and argument "formatparam1"
|
||||
And my step calls Log on testing T with message "log this message"
|
||||
Then the logged messages should include "format this formatparam1"
|
||||
And the logged messages should include "log this message"
|
||||
|
||||
Scenario: should log when godog.Logf/Log called
|
||||
When my step calls godog.Logf with message "format this %s" and argument "formatparam1"
|
||||
And my step calls godog.Log with message "log this message"
|
||||
Then the logged messages should include "format this formatparam1"
|
||||
And the logged messages should include "log this message"
|
14
run_test.go
14
run_test.go
|
@ -525,11 +525,12 @@ func Test_AllFeaturesRun(t *testing.T) {
|
|||
...................................................................... 210
|
||||
...................................................................... 280
|
||||
...................................................................... 350
|
||||
...... 356
|
||||
...................................................................... 420
|
||||
... 423
|
||||
|
||||
|
||||
94 scenarios (94 passed)
|
||||
356 steps (356 passed)
|
||||
108 scenarios (108 passed)
|
||||
423 steps (423 passed)
|
||||
0s
|
||||
`
|
||||
|
||||
|
@ -553,11 +554,12 @@ func Test_AllFeaturesRunAsSubtests(t *testing.T) {
|
|||
...................................................................... 210
|
||||
...................................................................... 280
|
||||
...................................................................... 350
|
||||
...... 356
|
||||
...................................................................... 420
|
||||
... 423
|
||||
|
||||
|
||||
94 scenarios (94 passed)
|
||||
356 steps (356 passed)
|
||||
108 scenarios (108 passed)
|
||||
423 steps (423 passed)
|
||||
0s
|
||||
`
|
||||
|
||||
|
|
20
suite.go
20
suite.go
|
@ -85,12 +85,18 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, scena
|
|||
// user multistep definitions may panic
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
if err != nil {
|
||||
pe, isErr := e.(error)
|
||||
switch {
|
||||
case isErr && errors.Is(pe, errStopNow):
|
||||
// FailNow or SkipNow called on dogTestingT, so clear the error to let the normal
|
||||
// below getTestingT(ctx).isFailed() call handle the reasons.
|
||||
err = nil
|
||||
case err != nil:
|
||||
err = &traceError{
|
||||
msg: fmt.Sprintf("%s: %v", err.Error(), e),
|
||||
stack: callStack(),
|
||||
}
|
||||
} else {
|
||||
default:
|
||||
err = &traceError{
|
||||
msg: fmt.Sprintf("%v", e),
|
||||
stack: callStack(),
|
||||
|
@ -100,6 +106,11 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, scena
|
|||
|
||||
earlyReturn := scenarioErr != nil || errors.Is(err, ErrUndefined)
|
||||
|
||||
// Check for any calls to Fail on dogT
|
||||
if err == nil {
|
||||
err = getTestingT(ctx).isFailed()
|
||||
}
|
||||
|
||||
switch {
|
||||
case errors.Is(err, ErrPending):
|
||||
status = StepPending
|
||||
|
@ -509,10 +520,15 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) {
|
|||
|
||||
s.fmt.Pickle(pickle)
|
||||
|
||||
dt := &testingT{
|
||||
name: pickle.Name,
|
||||
}
|
||||
ctx = setContextTestingT(ctx, dt)
|
||||
// scenario
|
||||
if s.testingT != nil {
|
||||
// Running scenario as a subtest.
|
||||
s.testingT.Run(pickle.Name, func(t *testing.T) {
|
||||
dt.t = t
|
||||
ctx, err = s.runSteps(ctx, pickle, pickle.Steps)
|
||||
if s.shouldFail(err) {
|
||||
t.Errorf("%+v", err)
|
||||
|
|
|
@ -161,6 +161,19 @@ func InitializeScenario(ctx *ScenarioContext) {
|
|||
}
|
||||
})
|
||||
|
||||
// introduced to test testingT
|
||||
ctx.Step(`^my step (?:fails|skips) the test by calling (FailNow|Fail|SkipNow|Skip) on testing T$`, tc.myStepCallsTFailErrorSkip)
|
||||
ctx.Step(`^my step fails the test by calling (Fatal|Error) on testing T with message "([^"]*)"$`, tc.myStepCallsTErrorFatal)
|
||||
ctx.Step(`^my step fails the test by calling (Fatalf|Errorf) on testing T with message "([^"]*)" and argument "([^"]*)"$`, tc.myStepCallsTErrorfFatalf)
|
||||
ctx.Step(`^my step calls Log on testing T with message "([^"]*)"$`, tc.myStepCallsTLog)
|
||||
ctx.Step(`^my step calls Logf on testing T with message "([^"]*)" and argument "([^"]*)"$`, tc.myStepCallsTLogf)
|
||||
ctx.Step(`^my step calls testify's assert.Equal with expected "([^"]*)" and actual "([^"]*)"$`, tc.myStepCallsTestifyAssertEqual)
|
||||
ctx.Step(`^my step calls testify's require.Equal with expected "([^"]*)" and actual "([^"]*)"$`, tc.myStepCallsTestifyRequireEqual)
|
||||
ctx.Step(`^my step calls testify's assert.Equal ([0-9]+) times(| with match)$`, tc.myStepCallsTestifyAssertEqualMultipleTimes)
|
||||
ctx.Step(`^my step calls godog.Log with message "([^"]*)"$`, tc.myStepCallsDogLog)
|
||||
ctx.Step(`^my step calls godog.Logf with message "([^"]*)" and argument "([^"]*)"$`, tc.myStepCallsDogLogf)
|
||||
ctx.Step(`^the logged messages should include "([^"]*)"$`, tc.theLoggedMessagesShouldInclude)
|
||||
|
||||
ctx.StepContext().Before(tc.inject)
|
||||
}
|
||||
|
||||
|
@ -385,6 +398,101 @@ func (tc *godogFeaturesScenario) iShouldSeeTheContextInTheNextStep(ctx context.C
|
|||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsTFailErrorSkip(ctx context.Context, op string) error {
|
||||
switch op {
|
||||
case "FailNow":
|
||||
T(ctx).FailNow()
|
||||
case "Fail":
|
||||
T(ctx).Fail()
|
||||
case "SkipNow":
|
||||
T(ctx).SkipNow()
|
||||
case "Skip":
|
||||
T(ctx).Skip()
|
||||
default:
|
||||
return fmt.Errorf("operation %s not supported by iCallTFailErrorSkip", op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsTErrorFatal(ctx context.Context, op string, message string) error {
|
||||
switch op {
|
||||
case "Error":
|
||||
T(ctx).Error(message)
|
||||
case "Fatal":
|
||||
T(ctx).Fatal(message)
|
||||
default:
|
||||
return fmt.Errorf("operation %s not supported by iCallTErrorFatal", op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsTErrorfFatalf(ctx context.Context, op string, message string, arg string) error {
|
||||
switch op {
|
||||
case "Errorf":
|
||||
T(ctx).Errorf(message, arg)
|
||||
case "Fatalf":
|
||||
T(ctx).Fatalf(message, arg)
|
||||
default:
|
||||
return fmt.Errorf("operation %s not supported by iCallTErrorfFatalf", op)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsTestifyAssertEqual(ctx context.Context, a string, b string) error {
|
||||
assert.Equal(T(ctx), a, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsTestifyAssertEqualMultipleTimes(ctx context.Context, times string, withMatch string) error {
|
||||
timesInt, err := strconv.Atoi(times)
|
||||
if err != nil {
|
||||
return fmt.Errorf("test step has invalid times value %s: %w", times, err)
|
||||
}
|
||||
for i := 0; i < timesInt; i++ {
|
||||
if withMatch == " with match" {
|
||||
assert.Equal(T(ctx), fmt.Sprintf("exp%v", i), fmt.Sprintf("exp%v", i))
|
||||
} else {
|
||||
assert.Equal(T(ctx), "exp", fmt.Sprintf("notexp%v", i))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsTestifyRequireEqual(ctx context.Context, a string, b string) error {
|
||||
require.Equal(T(ctx), a, b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsTLog(ctx context.Context, message string) error {
|
||||
T(ctx).Log(message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsTLogf(ctx context.Context, message string, arg string) error {
|
||||
T(ctx).Logf(message, arg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsDogLog(ctx context.Context, message string) error {
|
||||
Log(ctx, message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) myStepCallsDogLogf(ctx context.Context, message string, arg string) error {
|
||||
Logf(ctx, message, arg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) theLoggedMessagesShouldInclude(ctx context.Context, message string) error {
|
||||
messages := LoggedMessages(ctx)
|
||||
for _, m := range messages {
|
||||
if strings.Contains(m, message) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("the message %q was not logged (logged messages: %v)", message, messages)
|
||||
}
|
||||
|
||||
func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps *DocString) error {
|
||||
var expected = strings.Split(steps.Content, "\n")
|
||||
var actual, unmatched, matched []string
|
||||
|
|
206
testingt.go
Обычный файл
206
testingt.go
Обычный файл
|
@ -0,0 +1,206 @@
|
|||
package godog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// T returns a TestingT compatible interface from the current test context. It will return nil if
|
||||
// called outside the context of a test. This can be used with (for example) testify's assert and
|
||||
// require packages.
|
||||
func T(ctx context.Context) TestingT {
|
||||
return getTestingT(ctx)
|
||||
}
|
||||
|
||||
// TestingT is a subset of the public methods implemented by go's testing.T. It allows assertion
|
||||
// libraries to be used with godog, provided they depend only on this subset of methods.
|
||||
type TestingT interface {
|
||||
// Name returns the name of the current pickle under test
|
||||
Name() string
|
||||
// Log will log to the current testing.T log if set, otherwise it will log to stdout
|
||||
Log(args ...interface{})
|
||||
// Logf will log a formatted string to the current testing.T log if set, otherwise it will log
|
||||
// to stdout
|
||||
Logf(format string, args ...interface{})
|
||||
// Error fails the current test and logs the provided arguments. Equivalent to calling Log then
|
||||
// Fail.
|
||||
Error(args ...interface{})
|
||||
// Errorf fails the current test and logs the formatted message. Equivalent to calling Logf then
|
||||
// Fail.
|
||||
Errorf(format string, args ...interface{})
|
||||
// Fail marks the current test as failed, but does not halt execution of the step.
|
||||
Fail()
|
||||
// FailNow marks the current test as failed and halts execution of the step.
|
||||
FailNow()
|
||||
// Fatal logs the provided arguments, marks the test as failed and halts execution of the step.
|
||||
Fatal(args ...interface{})
|
||||
// Fatal logs the formatted message, marks the test as failed and halts execution of the step.
|
||||
Fatalf(format string, args ...interface{})
|
||||
// Skip logs the provided arguments and marks the test as skipped but does not halt execution
|
||||
// of the step.
|
||||
Skip(args ...interface{})
|
||||
// Skipf logs the formatted message and marks the test as skipped but does not halt execution
|
||||
// of the step.
|
||||
Skipf(format string, args ...interface{})
|
||||
// SkipNow marks the current test as skipped and halts execution of the step.
|
||||
SkipNow()
|
||||
// Skipped returns true if the test has been marked as skipped.
|
||||
Skipped() bool
|
||||
}
|
||||
|
||||
// Logf will log test output. If called in the context of a test and testing.T has been registered,
|
||||
// this will log using the step's testing.T, else it will simply log to stdout.
|
||||
func Logf(ctx context.Context, format string, args ...interface{}) {
|
||||
if t := getTestingT(ctx); t != nil {
|
||||
t.Logf(format, args...)
|
||||
return
|
||||
}
|
||||
fmt.Printf(format+"\n", args...)
|
||||
}
|
||||
|
||||
// Log will log test output. If called in the context of a test and testing.T has been registered,
|
||||
// this will log using the step's testing.T, else it will simply log to stdout.
|
||||
func Log(ctx context.Context, args ...interface{}) {
|
||||
if t := getTestingT(ctx); t != nil {
|
||||
t.Log(args...)
|
||||
return
|
||||
}
|
||||
fmt.Println(args...)
|
||||
}
|
||||
|
||||
// LoggedMessages returns an array of any logged messages that have been recorded during the test
|
||||
// through calls to godog.Log / godog.Logf or via operations against godog.T(ctx)
|
||||
func LoggedMessages(ctx context.Context) []string {
|
||||
if t := getTestingT(ctx); t != nil {
|
||||
return t.logMessages
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// errStopNow should be returned inside a panic within the test to immediately halt execution of that
|
||||
// test
|
||||
var errStopNow = fmt.Errorf("FailNow or SkipNow called")
|
||||
|
||||
type testingT struct {
|
||||
name string
|
||||
t *testing.T
|
||||
failed bool
|
||||
skipped bool
|
||||
failMessages []string
|
||||
logMessages []string
|
||||
}
|
||||
|
||||
// check interface against our testingT and the upstream testing.B/F/T:
|
||||
var (
|
||||
_ TestingT = &testingT{}
|
||||
_ TestingT = (*testing.T)(nil)
|
||||
)
|
||||
|
||||
func (dt *testingT) Name() string {
|
||||
if dt.t != nil {
|
||||
return dt.t.Name()
|
||||
}
|
||||
return dt.name
|
||||
}
|
||||
|
||||
func (dt *testingT) Log(args ...interface{}) {
|
||||
dt.logMessages = append(dt.logMessages, fmt.Sprint(args...))
|
||||
if dt.t != nil {
|
||||
dt.t.Log(args...)
|
||||
return
|
||||
}
|
||||
fmt.Println(args...)
|
||||
}
|
||||
|
||||
func (dt *testingT) Logf(format string, args ...interface{}) {
|
||||
dt.logMessages = append(dt.logMessages, fmt.Sprintf(format, args...))
|
||||
if dt.t != nil {
|
||||
dt.t.Logf(format, args...)
|
||||
return
|
||||
}
|
||||
fmt.Printf(format+"\n", args...)
|
||||
}
|
||||
|
||||
func (dt *testingT) Error(args ...interface{}) {
|
||||
dt.Log(args...)
|
||||
dt.failMessages = append(dt.failMessages, fmt.Sprintln(args...))
|
||||
dt.Fail()
|
||||
}
|
||||
|
||||
func (dt *testingT) Errorf(format string, args ...interface{}) {
|
||||
dt.Logf(format, args...)
|
||||
dt.failMessages = append(dt.failMessages, fmt.Sprintf(format, args...))
|
||||
dt.Fail()
|
||||
}
|
||||
|
||||
func (dt *testingT) Fail() {
|
||||
dt.failed = true
|
||||
}
|
||||
|
||||
func (dt *testingT) FailNow() {
|
||||
dt.Fail()
|
||||
panic(errStopNow)
|
||||
}
|
||||
|
||||
func (dt *testingT) Fatal(args ...interface{}) {
|
||||
dt.Log(args...)
|
||||
dt.FailNow()
|
||||
}
|
||||
|
||||
func (dt *testingT) Fatalf(format string, args ...interface{}) {
|
||||
dt.Logf(format, args...)
|
||||
dt.FailNow()
|
||||
}
|
||||
|
||||
func (dt *testingT) Skip(args ...interface{}) {
|
||||
dt.Log(args...)
|
||||
dt.skipped = true
|
||||
}
|
||||
|
||||
func (dt *testingT) Skipf(format string, args ...interface{}) {
|
||||
dt.Logf(format, args...)
|
||||
dt.skipped = true
|
||||
}
|
||||
|
||||
func (dt *testingT) SkipNow() {
|
||||
dt.skipped = true
|
||||
panic(errStopNow)
|
||||
}
|
||||
|
||||
func (dt *testingT) Skipped() bool {
|
||||
return dt.skipped
|
||||
}
|
||||
|
||||
// isFailed will return an error representing the calls to Fail made during this test
|
||||
func (dt *testingT) isFailed() error {
|
||||
if dt.skipped {
|
||||
return ErrSkip
|
||||
}
|
||||
if !dt.failed {
|
||||
return nil
|
||||
}
|
||||
switch len(dt.failMessages) {
|
||||
case 0:
|
||||
return fmt.Errorf("fail called on TestingT")
|
||||
case 1:
|
||||
return fmt.Errorf(dt.failMessages[0])
|
||||
default:
|
||||
return fmt.Errorf("checks failed:\n* %s", strings.Join(dt.failMessages, "\n* "))
|
||||
}
|
||||
}
|
||||
|
||||
type testingTCtxVal struct{}
|
||||
|
||||
func setContextTestingT(ctx context.Context, dt *testingT) context.Context {
|
||||
return context.WithValue(ctx, testingTCtxVal{}, dt)
|
||||
}
|
||||
|
||||
func getTestingT(ctx context.Context) *testingT {
|
||||
dt, ok := ctx.Value(testingTCtxVal{}).(*testingT)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return dt
|
||||
}
|
Загрузка…
Создание таблицы
Сослаться в новой задаче