Web UI and server work together well, parsing of go test output is robust.
Cleanup (removed TODO items).
4 files changed
tree: 73ac13869018193be6de2899809a14b28525f079
  1. assertions/
  2. convey/
  3. examples/
  4. execution/
  5. gotest/
  6. printing/
  7. reporting/
  8. scripts/
  9. web/
  10. .gitignore
  11. license.txt
  12. README.md
  13. run_webserver.sh
README.md

GoConvey is awesome BDD in Go

Welcome to GoConvey, a yummy BDD tool for gophers. Use it in the browser or in the terminal. Either way you choose, the output is a pleasure to read and work with.

Features:

  • Huge suite of regression tests
  • Directly integrates with go test
  • Readable, colorized console output (understandable by any manager, IT or not)
  • Fully-automatic web UI (works with native Go tests, too)
  • Test code generator
  • Idler script automatically runs tests in the terminal
  • Immediately open problem lines in Sublime Text (some assembly required)

Jump to:

Installation

$ go get github.com/smartystreets/goconvey
$ go install github.com/smartystreets/goconvey/web/server

Quick start

Make a test, for example:

func TestSpec(t *testing.T) {
	Convey("Given some integer with a starting value", t, func() {
		x := 1

		Convey("When the integer is incremented", func() {
			x++

			Convey("The value should be greater by one", func() {
				So(x, ShouldEqual, 2)
			})
		})
	})
}

Start up the GoConvey web server at your project's path:

$ $GOPATH/bin/server

Then open your browser to:

http://localhost:8080

There you have it. As long as GoConvey is running, test results will automatically update in your browser window. The design is responsive, so you can squish the browser real tight if you need to put it beside your code.

Want to use the terminal instead? Just do what you do best:

$ go test

Or if you want the output to include the story:

$ go test -v

If not all your tests are yet written in GoConvey, the web UI you opened in your browser also works for traditional Go tests! (But it's not quite as cool that way.)

Web UI

GoConvey rocks

Terminal output (with go test -v):

Tests pass:

Pass

Test fail:

Fail

Test panic:

Panic

Writing tests

See the examples folder. We recommend reviewing isolated_execution_test.go for a more thorough understanding of how tests are composed, and how they actually work.

For an easier way: open your browser to the web UI and click “Code Gen” in the top-right. Then describe your program's behavior in a natural, flowing way, for example (make sure you convert indents to tabs, as GitHub transformed them to spaces):

TestSpec
	Subject: Integer incrementation and decrementation
		Given a starting integer value
			When incremented
				The value should be greater by one
				The value should NOT be what it used to be
			When decremented
				The value should be lesser by one
				The value should NOT be what it used to be

The skeleton of your test file will be stubbed out automatically as you type. There are a few things to notice about this:

  • Lines starting with “Test” (case-sensitive), without indentation, are treated as the name of the function in which all nested tests will be included
  • Indentation defines scope
  • Assertions are not made here; you'll do that later after pasting the generated code into your _test.go file.

Documentation:

Public functions are documented using GoDoc conventions. See the [godoc.org page for this project] (http://godoc.org/github.com/smartystreets/goconvey) for the details.

Execution:

Concise mode (default):

$ cd $GOPATH/src/github.com/smartystreets/goconvey/examples
$ go test
.....

5 assertions and counting

....

9 assertions and counting

PASS
ok  	github.com/smartystreets/goconvey/examples	0.022s

Verbose mode:

$ cd $GOPATH/src/github.com/smartystreets/goconvey/examples
$ go test -v
=== RUN TestScoring

  Subject: Bowling Game Scoring 
    Given a fresh score card 
      When all gutter balls are thrown 
        The score should be zero ✔
      When all throws knock down only one pin 
        The score should be 20 ✔
      When a spare is thrown 
        The score should include a spare bonus. ✔
      When a strike is thrown 
        The score should include a strike bonus. ✔
      When all strikes are thrown 
        The score should be 300. ✔

5 assertions and counting

--- PASS: TestScoring (0.00 seconds)
=== RUN TestSpec

  Subject: Integer incrementation and decrementation 
    Given a starting integer value 
      When incremented 
        The value should be greater by one ✔
        The value should NOT be what it used to be ✔
      When decremented 
        The value should be lesser by one ✔
        The value should NOT be what it used to be ✔

9 assertions and counting

--- PASS: TestSpec (0.00 seconds)
PASS
ok  	github.com/smartystreets/goconvey/examples	0.023s

Happy? Well, it gets even better with the auto-reload script. This script detects changes below the directory where it is run and recurses that location, running go test wherever it finds *_test.go files. This means that once it‘s running you don’t ever have to leave your editor to run tests. It also accepts the -v argument for verbose mode. Check it out:

$ cd $GOPATH/src/github.com/smartystreets/goconvey/examples
$ $GOPATH/src/github.com/smartystreets/goconvey/scripts/idle.py

-------------------------------------- 1 --------------------------------------

.....

5 assertions and counting

....

9 assertions and counting

PASS
ok  	github.com/smartystreets/goconvey/examples	0.022s

(now waiting for you to save changes to the application under test...)

Assertions:

Here's the listing of assertions that this project aims to implement (see the examples folder for actual usage):

Assertions
General Equality
So(thing, ShouldEqual, thing2)
So(thing, ShouldNotEqual, thing2)
So(thing, ShouldResemble, thing2)
So(thing, ShouldNotResemble, thing2)
So(thing, ShouldPointTo, thing2)
So(thing, ShouldNotPointTo, thing2)
So(thing, ShouldBeNil, thing2)
So(thing, ShouldNotBeNil, thing2)
So(thing, ShouldBeTrue)
So(thing, ShouldBeFalse)
Numeric Quantity comparison
So(1, ShouldBeGreaterThan, 0)
So(1, ShouldBeGreaterThanOrEqualTo, 0)
So(1, ShouldBeLessThan, 2)
So(1, ShouldBeLessThanOrEqualTo, 2)
So(1.1, ShouldBeBetween, .8, 1.2)
So(1.1, ShouldNotBeBetween, 2, 3)
So(1.1, ShouldBeBetweenOrEqual, .9, 1.1)
So(1.1, ShouldNotBeBetweenOrEqual, 1000, 2000)
Collections
So([]int{2, 4, 6}, ShouldContain, 4)
So([]int{2, 4, 6}, ShouldNotContain, 5)
So(4, ShouldBeIn, ...[]int{2, 4, 6})
So(4, ShouldNotBeIn, ...[]int{1, 3, 5})
Strings (and []byte?)
So(“asdf”, ShouldStartWith, “as”)
So(“asdf”, ShouldNotStartWith, “df”)
So(“asdf”, ShouldEndWith, “df”)
So(“asdf”, ShouldNotEndWith, “df”)
So(“asdf”, ShouldContain, “sd”) // optional ‘expected occurences’ arguments?
So(“asdf”, ShouldNotContain, “er”)
So(“adsf”, ShouldBeBlank)
So(“asdf”, ShouldNotBeBlank)
panics
So(func(), ShouldPanic)
So(func(), ShouldNotPanic)
So(func(), ShouldPanicWith, "") // or errors.New(“something”)
So(func(), ShouldNotPanicWith, "") // or errors.New(“something”)
Type checking
So(1, ShouldHaveSameTypeAs, 0)
So(1, ShouldNotHaveSameTypeAs, “asdf”)
time
So(time.Now(), ShouldHappenBefore, time.Now())
So(time.Now(), ShouldHappenOnOrBefore, time.Now())
So(time.Now(), ShouldHappenAfter, time.Now())
So(time.Now(), ShouldHappenOnOrAfter, time.Now())
So(time.Now(), ShouldHappenBetween, time.Now(), time.Now())
So(time.Now(), ShouldHappenOnOrBetween, time.Now(), time.Now())
So(time.Now(), ShouldNotHappenOnOrBetween, time.Now(), time.Now())
So(time.New(), ShouldHappenWithin, duration, time.Now())
So(time.New(), ShouldNotHappenWithin, duration, time.Now())

Thanks to github.com/jacobsa for his excellent oglematchers library, which is what many of these methods make use of to do their jobs.

Writing your own assertions:

Sometimes a test suite will might need an assertion that is too specific to be included in this tool. Not to worry, simply implement a function with the following signature (fill in the bracketed parts and string values):

func should<do-something>(actual interface, expected ...interface{}) string {
    if <some-important-condition-is-met(actual, expected)> {
        return "" // empty string means the assertion passed
    } else {
        return "some descriptive message detailing why the assertion failed..."
    }
}

Suppose I implemented the following assertion:

func shouldScareGophersMoreThan(actual interface, expected ...interface{}) string {
    if actual == "BOO!" && expected[0] == "boo" {
        return ""
    } else {
        return "Ha! You'll have to get a lot friendlier with the capslock if you want to scare a gopher!"
    }
}

I can then make use of the assertion function when calling the So(...) method in the tests:

Convey("All caps always makes text more meaningful", func() {
    So("BOO!", shouldScareGophersMoreThan, "boo")
})

Skipping Convey Registrations:

Changing a Convey to SkipConvey prevents the func() associated with that call from running. This also has the consequence of preventing any nested Convey registrations from running. The reporter will indicate that the registration was skipped.

SkipConvey("Important stuff", func() { // This func() will not be executed!

    Convey("More important stuff", func() {
        So("asdf", ShouldEqual, "asdf")
    })

})

Using SkipConvey has nearly the same effect as commenting out the test entirely. However, this is preferred over commenting out tests to avoid the usual “declared/imported but not used” errors. Usage of SkipConvey is intended for temporary code alterations.

Unimplemented Convey Registrations:

When composing Convey registrations sometimes it's convenient to use nil instead of an actual func(). This allows you to do that and it also provides an indication in the report that the registration is not complete.

Convey("Some stuff", func() {

    // This will show up as 'skipped' in the report
    Convey("Should go boink", nil)

}

Skipping So Assertions:

Similar to SkipConvey, changing a So to SkipSo prevents the execution of that assertion. The report will show that the assertion was skipped.

Convey("1 Should Equal 2", func() {
    
    // This assertion will not be executed and will show up as 'skipped' in the report
    SkipSo(1, ShouldEqual, 2)

})

And like SkipConvey, this function is only intended for use during temporary code alterations.

Contributors (Thanks!):

Michael Whatcott

Matt Holt