update documentation
Этот коммит содержится в:
родитель
e8f6030616
коммит
bef768a5c3
4 изменённых файлов: 162 добавлений и 28 удалений
60
README.md
60
README.md
|
@ -46,24 +46,25 @@ You should see that the steps are undefined:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
**NOTE:** the undefined step templates are still in development and scheduled for **0.2.0**
|
It gives you undefined step snippets to implement in your test context. You may copy these snippets
|
||||||
|
into **godog_test.go** file.
|
||||||
|
|
||||||
|
Now if you run the tests again. You should see that the definition is now pending. You may change
|
||||||
|
**ErrPending** to **nil** and the scenario will pass successfully.
|
||||||
|
|
||||||
|
Since we need a working implementation, we may start by implementing what is necessary.
|
||||||
|
We only need a number of **godogs** for now.
|
||||||
|
|
||||||
``` go
|
``` go
|
||||||
/* file: /tmp/godog/godog.go */
|
/* file: /tmp/godog/godog.go */
|
||||||
package main
|
package main
|
||||||
|
|
||||||
type GodogCart struct {
|
var Godogs int
|
||||||
reserve int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *GodogCart) Eat(num int) { c.reserve -= num }
|
|
||||||
|
|
||||||
func (c *GodogCart) Available() int { return c.reserve }
|
|
||||||
|
|
||||||
func main() { /* usual main func */ }
|
func main() { /* usual main func */ }
|
||||||
```
|
```
|
||||||
|
|
||||||
Now lets describe all steps to test the application behavior:
|
Now lets finish our step implementations in order to test our feature requirements:
|
||||||
|
|
||||||
``` go
|
``` go
|
||||||
/* file: /tmp/godog/godog_test.go */
|
/* file: /tmp/godog/godog_test.go */
|
||||||
|
@ -75,35 +76,34 @@ import (
|
||||||
"github.com/DATA-DOG/godog"
|
"github.com/DATA-DOG/godog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *GodogCart) resetReserve(interface{}) {
|
func thereAreGodogs(available int) error {
|
||||||
c.reserve = 0
|
Godogs = available
|
||||||
}
|
|
||||||
|
|
||||||
func (c *GodogCart) thereAreNumGodogsInReserve(avail int) error {
|
|
||||||
c.reserve = avail
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GodogCart) iEatNum(num int) error {
|
func iEat(num int) error {
|
||||||
c.Eat(num)
|
if Godogs < num {
|
||||||
|
return fmt.Errorf("you cannot eat %d godogs, there are %d available", num, Godogs)
|
||||||
|
}
|
||||||
|
Godogs -= num
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GodogCart) thereShouldBeNumRemaining(left int) error {
|
func thereShouldBeRemaining(remaining int) error {
|
||||||
if c.Available() != left {
|
if Godogs != remaining {
|
||||||
return fmt.Errorf("expected %d godogs to be remaining, but there is %d", left, c.Available())
|
return fmt.Errorf("expected %d godogs to be remaining, but there is %d", remaining, Godogs)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func godogCartContext(s godog.Suite) {
|
func featureContext(s godog.Suite) {
|
||||||
c := &GodogCart{}
|
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
|
||||||
// each time before running scenario reset reserve
|
s.Step(`^I eat (\d+)$`, iEat)
|
||||||
s.BeforeScenario(c.resetReserve)
|
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||||
// register steps
|
|
||||||
s.Step(`^there are (\d+) godogs?$`, c.thereAreNumGodogsInReserve)
|
s.BeforeScenario(func(interface{}) {
|
||||||
s.Step(`^I eat (\d+)$`, c.iEatNum)
|
Godogs = 0 // clean the state before every scenario
|
||||||
s.Step(`^there should be (\d+) remaining$`, c.thereShouldBeNumRemaining)
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -111,6 +111,10 @@ Now when you run the `godog godog.feature` again, you should see:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
**Note:** we have hooked to **BeforeScenario** event in order to reset state. You may hook into
|
||||||
|
more events, like **AfterStep** to test against an error and print more details about the error
|
||||||
|
or state before failure. Or **BeforeSuite** to prepare a database.
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
See [godoc][godoc] for general API details.
|
See [godoc][godoc] for general API details.
|
||||||
|
|
130
fmt.go
130
fmt.go
|
@ -1,13 +1,45 @@
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/cucumber/gherkin-go"
|
"github.com/cucumber/gherkin-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// some snippet formatting regexps
|
||||||
|
var snippetExprCleanup = regexp.MustCompile("([\\/\\[\\]\\(\\)\\\\^\\$\\.\\|\\?\\*\\+\\'])")
|
||||||
|
var snippetExprQuoted = regexp.MustCompile("(\\s*|^)\"(?:[^\"]*)\"(\\s+|$)")
|
||||||
|
var snippetMethodName = regexp.MustCompile("[^a-zA-Z\\_\\ ]")
|
||||||
|
var snippetNumbers = regexp.MustCompile("(\\d+)")
|
||||||
|
|
||||||
|
var snippetHelperFuncs = template.FuncMap{
|
||||||
|
"backticked": func(s string) string {
|
||||||
|
return "`" + s + "`"
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var undefinedSnippetsTpl = template.Must(template.New("snippets").Funcs(snippetHelperFuncs).Parse(`
|
||||||
|
{{ range . }}func {{ .Method }}({{ .Args }}) error {
|
||||||
|
return godog.ErrPending
|
||||||
|
}
|
||||||
|
|
||||||
|
{{end}}func featureContext(s godog.Suite) { {{ range . }}
|
||||||
|
s.Step({{ backticked .Expr }}, {{ .Method }}){{end}}
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
|
||||||
|
type undefinedSnippet struct {
|
||||||
|
Method string
|
||||||
|
Expr string
|
||||||
|
argument interface{} // gherkin step argument
|
||||||
|
}
|
||||||
|
|
||||||
type registeredFormatter struct {
|
type registeredFormatter struct {
|
||||||
name string
|
name string
|
||||||
fmt Formatter
|
fmt Formatter
|
||||||
|
@ -180,6 +212,8 @@ func (f *basefmt) Summary() {
|
||||||
steps = append(steps, parts[len(parts)-1])
|
steps = append(steps, parts[len(parts)-1])
|
||||||
}
|
}
|
||||||
if len(f.pending) > 0 {
|
if len(f.pending) > 0 {
|
||||||
|
passed -= len(f.pending)
|
||||||
|
parts = append(parts, cl(fmt.Sprintf("%d pending", len(f.pending)), yellow))
|
||||||
steps = append(steps, cl(fmt.Sprintf("%d pending", len(f.pending)), yellow))
|
steps = append(steps, cl(fmt.Sprintf("%d pending", len(f.pending)), yellow))
|
||||||
}
|
}
|
||||||
if len(f.undefined) > 0 {
|
if len(f.undefined) > 0 {
|
||||||
|
@ -209,4 +243,100 @@ func (f *basefmt) Summary() {
|
||||||
fmt.Println(fmt.Sprintf("%d steps (%s)", nsteps, strings.Join(steps, ", ")))
|
fmt.Println(fmt.Sprintf("%d steps (%s)", nsteps, strings.Join(steps, ", ")))
|
||||||
}
|
}
|
||||||
fmt.Println(elapsed)
|
fmt.Println(elapsed)
|
||||||
|
|
||||||
|
f.snippets()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *undefinedSnippet) Args() string {
|
||||||
|
var args []string
|
||||||
|
var pos, idx int
|
||||||
|
var breakLoop bool
|
||||||
|
for !breakLoop {
|
||||||
|
part := s.Expr[pos:]
|
||||||
|
ipos := strings.Index(part, "(\\d+)")
|
||||||
|
spos := strings.Index(part, "\"([^\"]*)\"")
|
||||||
|
switch {
|
||||||
|
case spos == -1 && ipos == -1:
|
||||||
|
breakLoop = true
|
||||||
|
case spos == -1:
|
||||||
|
idx++
|
||||||
|
pos += ipos + len("(\\d+)")
|
||||||
|
args = append(args, fmt.Sprintf("arg%d int", idx))
|
||||||
|
case ipos == -1:
|
||||||
|
idx++
|
||||||
|
pos += spos + len("\"([^\"]*)\"")
|
||||||
|
args = append(args, fmt.Sprintf("arg%d string", idx))
|
||||||
|
case ipos < spos:
|
||||||
|
idx++
|
||||||
|
pos += ipos + len("(\\d+)")
|
||||||
|
args = append(args, fmt.Sprintf("arg%d int", idx))
|
||||||
|
case spos < ipos:
|
||||||
|
idx++
|
||||||
|
pos += spos + len("\"([^\"]*)\"")
|
||||||
|
args = append(args, fmt.Sprintf("arg%d string", idx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.argument != nil {
|
||||||
|
idx++
|
||||||
|
switch s.argument.(type) {
|
||||||
|
case *gherkin.DocString:
|
||||||
|
args = append(args, fmt.Sprintf("arg%d *gherkin.DocString", idx))
|
||||||
|
case *gherkin.DataTable:
|
||||||
|
args = append(args, fmt.Sprintf("arg%d *gherkin.DataTable", idx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(args, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *basefmt) snippets() {
|
||||||
|
if len(f.undefined) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(cl("\nYou can implement step definitions for undefined steps with these snippets:", yellow))
|
||||||
|
|
||||||
|
var index int
|
||||||
|
var snips []*undefinedSnippet
|
||||||
|
// build snippets
|
||||||
|
for _, u := range f.undefined {
|
||||||
|
expr := snippetExprCleanup.ReplaceAllString(u.step.Text, "\\\\$1")
|
||||||
|
expr = snippetNumbers.ReplaceAllString(expr, "(\\d+)")
|
||||||
|
expr = snippetExprQuoted.ReplaceAllString(expr, " \"([^\"]*)\" ")
|
||||||
|
expr = "^" + strings.TrimSpace(expr) + "$"
|
||||||
|
|
||||||
|
name := snippetNumbers.ReplaceAllString(u.step.Text, "")
|
||||||
|
name = snippetExprQuoted.ReplaceAllString(name, "")
|
||||||
|
name = snippetMethodName.ReplaceAllString(name, "")
|
||||||
|
var words []string
|
||||||
|
for i, w := range strings.Split(name, " ") {
|
||||||
|
if i != 0 {
|
||||||
|
w = strings.Title(w)
|
||||||
|
} else {
|
||||||
|
w = string(unicode.ToLower(rune(w[0]))) + w[1:]
|
||||||
|
}
|
||||||
|
words = append(words, w)
|
||||||
|
}
|
||||||
|
name = strings.Join(words, "")
|
||||||
|
if len(name) == 0 {
|
||||||
|
index++
|
||||||
|
name = fmt.Sprintf("stepDefinition%d", index)
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
for _, snip := range snips {
|
||||||
|
if snip.Expr == expr {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
snips = append(snips, &undefinedSnippet{Method: name, Expr: expr, argument: u.step.Argument})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := undefinedSnippetsTpl.Execute(&buf, snips); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(cl(buf.String(), yellow))
|
||||||
}
|
}
|
||||||
|
|
Двоичные данные
screenshots/passed.png
Двоичные данные
screenshots/passed.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 61 КиБ После Ширина: | Высота: | Размер: 54 КиБ |
Двоичные данные
screenshots/undefined.png
Двоичные данные
screenshots/undefined.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 47 КиБ После Ширина: | Высота: | Размер: 96 КиБ |
Загрузка…
Создание таблицы
Сослаться в новой задаче