What I would like is a tool that allowed testers and QA people to define tests using BDD for RESTful APIs.
Something along the lines of this syntax:
Using https://example.com/myapi
With access_token [accessToken]
With content_type application/json
When I create:
{
"somejson": true
"foo": "bar"
}
Expect status 200
Expect returnData.somejson = true
Expect returnData.foo = "bar"
Store returnData.id as myid
And
Using https://example.com/myapi/{{myid}}
When I get
Expect status 200
Expect returnData.somejson = true
Expect returnData.foo = "bar"
And
With access_token [accessToken]
With content_type application/json
When I update:
{
"somejson": true
"foo": "foo"
}
Expect status 200
Expect returnData.somejson = true
Expect returnData.foo = "foo"
Store returnData.id as myid
And
With access_token [accessToken]
When I delete
Expect status 200
And
When I get
Expect status 404
I've seen a few things like this written in NodeJS, such as apieasy and vows. But nothing with this level of succinctness.And I'd like it to be in Go as I'd like to just give a single binary to the testers (rather than an "install node, get project, fetch dependencies, resolve any issues, run the project").
When I read the headline, this is roughly what I hoped for. If I can find the time (unlikely), I'd build this. But then... maybe someone reading this knows something very close to this.
This feels weird. I have a gut reaction to this, and it's conflicted. Perhaps the same feeling `redbad` has, perhaps not.
On the one hand, I feel like this is a million miles away from the Go I know and love. I've used Ruby, Python, C#, Java, C, Obj-C, JavaScript etc in the past. In terms of mental processes, the way I write go is closest the way I would write C or Java.
I write tests that do the same thing as this, that have pre and post conditions and assertions and explanations in comments. The whole point of static type systems, surely, is that you represent things (constraints, state, possible values, etc) in code rather than comments and rely on the language to enforce them rather than the next developer to read your comments, so the explosions happen at compile time rather than production. So, by that token, this kind of DSL seems like a step in the direction of static typing, rather than the other way round.
I've not looked at the source, but the line `Expect(scoreKeeper.Stats["Manning"]["Touchdowns"]).To(Equal(1))` might use all kinds of clever closure stuff but ultimately wouldn't compile if the right interfaces weren't satisfied. Unless there are `interface{}`s all over the place, which would be regrettable.
On the surface, this seems like a step toward Ruby with its endless DSLs which feels like the antithesis of static typing. That naturally feels a bit weird and a bit of a culture lurch. But perhaps the DSLs are closer to those of Scala than Ruby.
Both feelings I get are fuzzy and not particularly arguable, but I think peoples' relationships with languages are very personal and that's such a fundamental quality of the PL landscape that it's worth not discounting.
Those things aren't all bad, and _some_ of their lessons can be successfully "ported" to languages and ecosystems that don't suffer the same fundamental shortcomings as e.g. Ruby or Python. But when I see developers take e.g. the BDD ethos as axiomatic and just run with it, it makes me feel like they don't really understand what BDD is designed to address. Likewise with hyper-expressive testing DSLs, or the concept of "mocking" as it's normally used.
Forgive the loaded language, but bringing BDD et. al. to languages like Go feels, to me, like cargo-cult development.
I prefer to use Gherkin syntax for by bdd testing, but the matchers are nice. The bootstrapper and helpers seem to fail hopelessly on windows, and the console writer outputs a lot of control characters which do not work in windows command prompt.
1. Mao: https://github.com/azer/mao
2. Zen: https://github.com/pranavraja/zen
3. GoConvey: https://github.com/smartystreets/goconvey
Are you planning to add selenium support?
- Godoc documentation is coming soon. Godoc is great for API-style documentation but not particularly appropriate for tutorial/structured/narrative-style documentation. Think golang.org/pkg vs golang.org/doc. I wanted to write the /doc first as I believe it to be more valuable for beginners and is often overlooked by the go community. /pkg is coming soon (a few days) and is much easier to write.
- @buro9: Golang is BDD-style in the same way that Cedar and Jasmine are BDD-style. This is not Cucumber (way too much DSL for me!) With that said, asynchronous testing support is baked right into Ginkgo and Gomega so the API-testing that you'd like would be easily expressed in Ginkgo and Gomega.
- Assertions that I have fundamentally misunderstood Golang or the distinction between dynamic and static typed languages are interesting and I'd like to address some of them.
BDD-style testing need not be limited to dynamically-typed language. Yes dynamically-typed languages need far more comprehensive test coverage to make up for the missing compiler. That's why I tend to prefer statically-typed languages. To me BDD-style (vs the XUnit style) isn't primarily about addressing these deficiencies in dynamic languages -- it's about expressiveness.
To that end, I think that BDD and Golang go hand in hand. Let me explain. BDD is exceptionally good at describing the behavior of branching code. Golang is filled with branching code that needs to be described in test. Here's a classic example in pseudo-go:
func DoSomethingAwesome(dependencyA DepA, dependencyB DepB) awesome Awesome, err error {
stuffA, err := dependencyA.Gimme()
if err != nil {
return Awesome{}, err
}
stuffB, err := dependencyB.Gimme()
if err != nil {
return Awesome{}, err
}
....
return awesome, nil
}
With Ginkgo you can cover these branches expressively: Describe("Doing something Awesome", func() {
BeforeEach(...) //common happy case setup
Context("When all is well", func() {
It("should be awesome", ...)
})
Context("When dependencyA fails", func() {
BeforeEach(...) //set dependencA up for failure
It("should return a zero Awesome", func() {
...
Expect(awesome).To(BeZero())
})
It("should error", func() {
...
Expect(err).To(HaveOccured())
})
})
Context("When dependencyB fails", func() {
//etc...
})
})
Compare this to the XUnit style: func TestDoingSomethingAwesomeWhenAllIsWell() {
//setup
//some sort of assertion
}
func TestDoingSomethingAwesomeWhenDependencyAFails() {
//setup + tweak
//some sort of assertion that awesome is zero
//some sort of assertion about err
}
func TestDoingSomethingAwesomeWhenDependencyBFails() {
//setup + tweak
//some sort of assertion that awesome is zero
//some sort of assertion about err
}
I prefer the former (note: that's a subjective statement). IMO there's nothing fundamentally more Golangish about the latter (if anything the fact that func's that begin with Test are special is kinda weird), and since Gomega's matchers are fluent in Go they know about things like zero values and errors having occured. Moreover, both Ginkgo and Gomega have been built to make testing asynchronous code (read: concurrent goroutines) easy and expressive. This isn't a carbon copy of RSpec/Cedar/Jasmine, it's a synthesis of the best ideas from those testing frameworks expressed in ways that cater specifically to Golang.- Concerns about using interface{} and reflection seem odd to me. First off, the Go authors provide both of these things to precisely solve the sort of problem that Gomega is trying to solve. Nobody wants to write and maintain matchers that look like:
Expect(foo).To(EqualInt64(3))
Expect(bar).To(EqualString("blarp"))
Expect(baz).To(EqualMyCustomAwesomeType(awesome))
besides the reflect package's `DeepEqual` does a great job comparing `interface{}` to `interface{}` correctly.Most importantly of all: we're all using interface{} all the time: `fmt.Sprintf("%d %f %s %v", ...)`!
And thanks for seeing the point @karma_fountain: Gomega's matchers have excellent reporting about what precisely went wrong when a matcher fails. Bare assertions lack this and a look through the Go tests shows a lot of reinventing-the-wheel to provide what probably amounts to inconsistent error output to the developer. Why not put that all in one place, call it a matcher library, and make it dead easy to write custom matchers?
- And @shoo: yes, thanks for pointing it out: the example is somewhat fabricated and isn't a compelling argument for BDD vs XUnit. It's hard to cook up a good BDD example in very short space!
> Nobody wants to write and maintain matchers that
> look like: Expect(foo).To(EqualInt64(3))
And nobody does do that. Idiomatic Go is to write if foo != 3 {
t.Errorf("foo: expected 3, got %d", foo)
}
I truly cannot explain why _so many people_ find this style of testing _so objectionable_ that they invent entire DSLs to avoid it.t.Errorf("foo: expected 3, got %d", foo)
gets old quickly.