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
|
## 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]
|
## [v0.14.0]
|
||||||
### Added
|
### Added
|
||||||
- Improve ErrSkip handling, add test for Summary and operations order ([584](https://github.com/cucumber/godog/pull/584) - [vearutop](https://github.com/vearutop))
|
- 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).
|
A more extensive example can be [found here](/_examples/assert-godogs).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func thereShouldBeRemaining(remaining int) error {
|
func thereShouldBeRemaining(ctx context.Context, remaining int) error {
|
||||||
return assertExpectedAndActual(
|
assert.Equal(
|
||||||
assert.Equal, Godogs, remaining,
|
godog.T(ctx), Godogs, remaining,
|
||||||
"Expected %d godogs to be remaining, but there is %d", remaining, Godogs,
|
"Expected %d godogs to be remaining, but there is %d", remaining, Godogs,
|
||||||
)
|
)
|
||||||
}
|
return nil
|
||||||
|
|
||||||
// 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...)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -36,31 +35,22 @@ func thereAreGodogs(available int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func iEat(num int) error {
|
func iEat(ctx context.Context, num int) error {
|
||||||
err := assertExpectedAndActual(
|
if !assert.GreaterOrEqual(godog.T(ctx), Godogs, num, "You cannot eat %d godogs, there are %d available", num, Godogs) {
|
||||||
assert.GreaterOrEqual, Godogs, num,
|
return nil
|
||||||
"You cannot eat %d godogs, there are %d available", num, Godogs,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Godogs -= num
|
Godogs -= num
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func thereShouldBeRemaining(remaining int) error {
|
func thereShouldBeRemaining(ctx context.Context, remaining int) error {
|
||||||
return assertExpectedAndActual(
|
assert.Equal(godog.T(ctx), Godogs, remaining, "Expected %d godogs to be remaining, but there is %d", remaining, Godogs)
|
||||||
assert.Equal, Godogs, remaining,
|
return nil
|
||||||
"Expected %d godogs to be remaining, but there is %d", remaining, Godogs,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func thereShouldBeNoneRemaining() error {
|
func thereShouldBeNoneRemaining(ctx context.Context) error {
|
||||||
return assertActual(
|
assert.Empty(godog.T(ctx), Godogs, "Expected none godogs to be remaining, but there is %d", Godogs)
|
||||||
assert.Empty, Godogs,
|
return nil
|
||||||
"Expected none godogs to be remaining, but there is %d", Godogs,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeScenario(ctx *godog.ScenarioContext) {
|
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 (\d+) remaining$`, thereShouldBeRemaining)
|
||||||
ctx.Step(`^there should be none remaining$`, thereShouldBeNoneRemaining)
|
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
|
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"
|
When I run feature suite with formatter "events"
|
||||||
Then the following events should be fired:
|
Then the following events should be fired:
|
||||||
"""
|
"""
|
||||||
|
@ -34,7 +34,7 @@ Feature: event stream formatter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Scenario: should process outline scenario
|
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"
|
When I run feature suite with formatter "events"
|
||||||
Then the following events should be fired:
|
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.
|
Scenario: Should scenarios identified with path:line and preserve the order.
|
||||||
Given a feature path "features/load.feature:6"
|
Given a feature path "features/load.feature:6"
|
||||||
And a feature path "features/multistep.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"
|
And a feature path "features/multistep.feature:23"
|
||||||
When I run feature suite with formatter "pretty"
|
When I run feature suite with formatter "pretty"
|
||||||
Then the rendered output will be as follows:
|
Then the rendered output will be as follows:
|
||||||
|
@ -363,7 +363,7 @@ Feature: pretty formatter
|
||||||
Scenario: load features within path # features/load.feature:6
|
Scenario: load features within path # features/load.feature:6
|
||||||
Given a feature path "features" # suite_context_test.go:0 -> *godogFeaturesScenario
|
Given a feature path "features" # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||||
When I parse 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/background.feature
|
||||||
features/events.feature
|
features/events.feature
|
||||||
|
@ -378,6 +378,7 @@ Feature: pretty formatter
|
||||||
features/run.feature
|
features/run.feature
|
||||||
features/snippets.feature
|
features/snippets.feature
|
||||||
features/tags.feature
|
features/tags.feature
|
||||||
|
features/testingt.feature
|
||||||
\"\"\"
|
\"\"\"
|
||||||
|
|
||||||
Feature: run features with nested steps
|
Feature: run features with nested steps
|
||||||
|
@ -407,7 +408,7 @@ Feature: pretty formatter
|
||||||
As a test suite
|
As a test suite
|
||||||
I need to be able to load features
|
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
|
Given a feature path "features/load.feature" # suite_context_test.go:0 -> *godogFeaturesScenario
|
||||||
When I parse features # 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
|
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
|
Scenarijus: savybių užkrovimas iš aplanko
|
||||||
Duota savybių aplankas "features"
|
Duota savybių aplankas "features"
|
||||||
Kai aš išskaitau savybes
|
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/background.feature
|
||||||
features/events.feature
|
features/events.feature
|
||||||
|
@ -23,4 +23,5 @@ Savybė: užkrauti savybes
|
||||||
features/run.feature
|
features/run.feature
|
||||||
features/snippets.feature
|
features/snippets.feature
|
||||||
features/tags.feature
|
features/tags.feature
|
||||||
|
features/testingt.feature
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -6,7 +6,7 @@ Feature: load features
|
||||||
Scenario: load features within path
|
Scenario: load features within path
|
||||||
Given a feature path "features"
|
Given a feature path "features"
|
||||||
When I parse features
|
When I parse features
|
||||||
Then I should have 13 feature files:
|
Then I should have 14 feature files:
|
||||||
"""
|
"""
|
||||||
features/background.feature
|
features/background.feature
|
||||||
features/events.feature
|
features/events.feature
|
||||||
|
@ -21,6 +21,7 @@ Feature: load features
|
||||||
features/run.feature
|
features/run.feature
|
||||||
features/snippets.feature
|
features/snippets.feature
|
||||||
features/tags.feature
|
features/tags.feature
|
||||||
|
features/testingt.feature
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Scenario: load a specific feature file
|
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
|
...................................................................... 210
|
||||||
...................................................................... 280
|
...................................................................... 280
|
||||||
...................................................................... 350
|
...................................................................... 350
|
||||||
...... 356
|
...................................................................... 420
|
||||||
|
... 423
|
||||||
|
|
||||||
|
|
||||||
94 scenarios (94 passed)
|
108 scenarios (108 passed)
|
||||||
356 steps (356 passed)
|
423 steps (423 passed)
|
||||||
0s
|
0s
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -553,11 +554,12 @@ func Test_AllFeaturesRunAsSubtests(t *testing.T) {
|
||||||
...................................................................... 210
|
...................................................................... 210
|
||||||
...................................................................... 280
|
...................................................................... 280
|
||||||
...................................................................... 350
|
...................................................................... 350
|
||||||
...... 356
|
...................................................................... 420
|
||||||
|
... 423
|
||||||
|
|
||||||
|
|
||||||
94 scenarios (94 passed)
|
108 scenarios (108 passed)
|
||||||
356 steps (356 passed)
|
423 steps (423 passed)
|
||||||
0s
|
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
|
// user multistep definitions may panic
|
||||||
defer func() {
|
defer func() {
|
||||||
if e := recover(); e != nil {
|
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{
|
err = &traceError{
|
||||||
msg: fmt.Sprintf("%s: %v", err.Error(), e),
|
msg: fmt.Sprintf("%s: %v", err.Error(), e),
|
||||||
stack: callStack(),
|
stack: callStack(),
|
||||||
}
|
}
|
||||||
} else {
|
default:
|
||||||
err = &traceError{
|
err = &traceError{
|
||||||
msg: fmt.Sprintf("%v", e),
|
msg: fmt.Sprintf("%v", e),
|
||||||
stack: callStack(),
|
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)
|
earlyReturn := scenarioErr != nil || errors.Is(err, ErrUndefined)
|
||||||
|
|
||||||
|
// Check for any calls to Fail on dogT
|
||||||
|
if err == nil {
|
||||||
|
err = getTestingT(ctx).isFailed()
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, ErrPending):
|
case errors.Is(err, ErrPending):
|
||||||
status = StepPending
|
status = StepPending
|
||||||
|
@ -509,10 +520,15 @@ func (s *suite) runPickle(pickle *messages.Pickle) (err error) {
|
||||||
|
|
||||||
s.fmt.Pickle(pickle)
|
s.fmt.Pickle(pickle)
|
||||||
|
|
||||||
|
dt := &testingT{
|
||||||
|
name: pickle.Name,
|
||||||
|
}
|
||||||
|
ctx = setContextTestingT(ctx, dt)
|
||||||
// scenario
|
// scenario
|
||||||
if s.testingT != nil {
|
if s.testingT != nil {
|
||||||
// Running scenario as a subtest.
|
// Running scenario as a subtest.
|
||||||
s.testingT.Run(pickle.Name, func(t *testing.T) {
|
s.testingT.Run(pickle.Name, func(t *testing.T) {
|
||||||
|
dt.t = t
|
||||||
ctx, err = s.runSteps(ctx, pickle, pickle.Steps)
|
ctx, err = s.runSteps(ctx, pickle, pickle.Steps)
|
||||||
if s.shouldFail(err) {
|
if s.shouldFail(err) {
|
||||||
t.Errorf("%+v", 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)
|
ctx.StepContext().Before(tc.inject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,6 +398,101 @@ func (tc *godogFeaturesScenario) iShouldSeeTheContextInTheNextStep(ctx context.C
|
||||||
return nil
|
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 {
|
func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps *DocString) error {
|
||||||
var expected = strings.Split(steps.Content, "\n")
|
var expected = strings.Split(steps.Content, "\n")
|
||||||
var actual, unmatched, matched []string
|
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
|
||||||
|
}
|
Загрузка…
Создание таблицы
Сослаться в новой задаче