Этот коммит содержится в:
gedi 2015-06-29 17:06:55 +03:00
родитель e8f6030616
коммит bef768a5c3
4 изменённых файлов: 162 добавлений и 28 удалений

Просмотреть файл

@ -46,24 +46,25 @@ You should see that the steps are undefined:
![Screenshot](https://raw.github.com/DATA-DOG/godog/master/screenshots/undefined.png)
**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
/* file: /tmp/godog/godog.go */
package main
type GodogCart struct {
reserve int
}
func (c *GodogCart) Eat(num int) { c.reserve -= num }
func (c *GodogCart) Available() int { return c.reserve }
var Godogs int
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
/* file: /tmp/godog/godog_test.go */
@ -75,35 +76,34 @@ import (
"github.com/DATA-DOG/godog"
)
func (c *GodogCart) resetReserve(interface{}) {
c.reserve = 0
}
func (c *GodogCart) thereAreNumGodogsInReserve(avail int) error {
c.reserve = avail
func thereAreGodogs(available int) error {
Godogs = available
return nil
}
func (c *GodogCart) iEatNum(num int) error {
c.Eat(num)
func iEat(num int) error {
if Godogs < num {
return fmt.Errorf("you cannot eat %d godogs, there are %d available", num, Godogs)
}
Godogs -= num
return nil
}
func (c *GodogCart) thereShouldBeNumRemaining(left int) error {
if c.Available() != left {
return fmt.Errorf("expected %d godogs to be remaining, but there is %d", left, c.Available())
func thereShouldBeRemaining(remaining int) error {
if Godogs != remaining {
return fmt.Errorf("expected %d godogs to be remaining, but there is %d", remaining, Godogs)
}
return nil
}
func godogCartContext(s godog.Suite) {
c := &GodogCart{}
// each time before running scenario reset reserve
s.BeforeScenario(c.resetReserve)
// register steps
s.Step(`^there are (\d+) godogs?$`, c.thereAreNumGodogsInReserve)
s.Step(`^I eat (\d+)$`, c.iEatNum)
s.Step(`^there should be (\d+) remaining$`, c.thereShouldBeNumRemaining)
func featureContext(s godog.Suite) {
s.Step(`^there are (\d+) godogs$`, thereAreGodogs)
s.Step(`^I eat (\d+)$`, iEat)
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
s.BeforeScenario(func(interface{}) {
Godogs = 0 // clean the state before every scenario
})
}
```
@ -111,6 +111,10 @@ Now when you run the `godog godog.feature` again, you should see:
![Screenshot](https://raw.github.com/DATA-DOG/godog/master/screenshots/passed.png)
**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
See [godoc][godoc] for general API details.

130
fmt.go
Просмотреть файл

@ -1,13 +1,45 @@
package godog
import (
"bytes"
"fmt"
"regexp"
"strings"
"text/template"
"time"
"unicode"
"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 {
name string
fmt Formatter
@ -180,6 +212,8 @@ func (f *basefmt) Summary() {
steps = append(steps, parts[len(parts)-1])
}
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))
}
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(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

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 61 КиБ

После

Ширина:  |  Высота:  |  Размер: 54 КиБ

Двоичные данные
screenshots/undefined.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 47 КиБ

После

Ширина:  |  Высота:  |  Размер: 96 КиБ