Mental Omega String Manager Failed

0 views
Skip to first unread message

Amilcar Labrosse

unread,
Aug 4, 2024, 2:09:14 PM8/4/24
to recphotero
Ginkgois a testing framework for Go designed to help you write expressive tests. It is best paired with the Gomega matcher library. When combined, Ginkgo and Gomega provide a rich and expressive DSL (Domain-specific Language) for writing tests.

Ginkgo is sometimes described as a "Behavior Driven Development" (BDD) framework. In reality, Ginkgo is a general purpose testing framework in active use across a wide variety of testing contexts: unit tests, integration tests, acceptance test, performance tests, etc.


The narrative docs you are reading here are supplemented by the godoc API-level docs. We suggest starting here to build a mental model for how Ginkgo works and understand how the Ginkgo DSL can be used to solve real-world testing scenarios. These docs are written assuming you are familiar with Go and the Go toolchain and that you are using Ginkgo V2 (V1 is no longer supported - see here for the migration guide).


The first commit to Ginkgo was made by @onsi on August 19th, 2013 (to put that timeframe in perspective, it's roughly three months before Go 1.2 was released!) Ginkgo came together in the highly collaborative environment fostered by Pivotal, a software company and consultancy that advocated for outcome-oriented software development built by balanced teams that embrace test-driven development.


Specifically, Pivotal was one of the lead contributors to Cloud Foundry. A sprawling distributed system, originally written in Ruby, that was slowly migrating towards the emerging distributed systems language of choice: Go. At the time (and, arguably, to this day) the landscape of Go's testing infrastructure was somewhat anemic. For engineers coming from the rich ecosystems of testing frameworks such as Jasmine, rspec, and Cedar there was a need for a comprehensive testing framework with a mature set of matchers in Go.


The need was twofold: organizational and technical. As a growing organization Pivotal would benefit from a shared testing framework to be used across its many teams writing Go. Engineers jumping from one team to another needed to be able to hit the ground running; we needed fewer testing bikesheds and more shared testing patterns. And our test-driven development culture put a premium on tests as first-class citizens: they needed to be easy to write, easy to read, and easy to maintain.


Moreover, the nature of the code being built - complex distributed systems - required a testing framework that could provide for the needs unique to unit-testing and integration-testing such a system. We needed to make testing asynchronous behavior ubiquitous and straightforward. We needed to have parallelizable integration tests to ensure our test run-times didn't get out of control. We needed a test framework that helped us suss out flaky tests and fix them.


This was the context that led to Ginkgo. Over the years the Go testing ecosystem has grown and evolved - sometimes bringing it closer to Ginkgo. Throughout, the community's reactions to Ginkgo have been... interesting. Some enjoy the expressive framework and rich set of matchers - for many the DSL is familiar and the CLI is productive. Others have found the DSL off-putting, arguing that Ginkgo is not "the Go way" and that Go developers should eschew third party libraries in general. That's OK; the world is plenty large enough for options to abound :)


This fetches Ginkgo and installs the ginkgo executable under $GOBIN - you'll want that on your $PATH. It also fetches the core Gomega matcher library and its set of supporting libraries. Note that the current supported major version of Ginkgo is v2.


Note you must make sure the version of the ginkgo cli you install is the same as the version of Ginkgo in your go.mod file. You can do this by running go install github.com/onsi/ginkgo/v2/ginkgo from your package.


Ginkgo adheres to semantic versioning - the intent is for there to be no breaking changes along the 2.m.p line with new functionality landing as minor releases and bug-fixes landing as patch releases (fixes are never back-ported). We work hard to maintain this policy however exceptions (while rare and typically minor) are possible, especially for brand new/emerging features.


Ginkgo hooks into Go's existing testing infrastructure. That means that Ginkgo specs live in *_test.go files, just like standard go tests. However, instead of using func TestX(t *testing.T) to write your tests you use the Ginkgo and Gomega DSLs.


We call a collection of Ginkgo specs in a given package a Ginkgo suite; and we use the word spec to talk about individual Ginkgo tests contained in the suite. Though they're functionally interchangeable, we'll use the word "spec" instead of "test" to make a distinction between Ginkgo tests and traditional testing tests.


First, ginkgo bootstrap generates a new test file and places it in the books_test package. That small detail is actually quite important so let's take a brief detour to discuss how Go organizes code in general, and test packages in particular.


Go code is organized into modules. A module is typically associated with a version controlled repository and is comprised of a series of versioned packages. Each package is typically associated with a single directory within the module's file tree containing a series of source code files. When testing Go code, unit tests for a package typically reside within the same directory as the package and are named *_test.go. Ginkgo follows this convention. It's also possible to construct test-only packages comprised solely of *_test.go files. For example, module-level integration tests typically live in their own test-only package directory and exercise the various packages of the module as a whole. As Ginkgo simply builds on top of Go's existing test infrastructure, this usecase is supported and encouraged as well.


Normally, Go only allows one package to live in a given directory (in our case, it would be a package named books). There is, however, one exception to this rule: a package ending in _test is allowed to live in the same directory as the package being tested. Doing so instructs Go to compile the package's test suite as a separate package. This means your test suite will not have access to the internals of the books package and will need to import the books package to access its external interface. Ginkgo defaults to setting up the suite as a *_test package to encourage you to only test the external behavior of your package, not its internal implementation details.


OK back to our bootstrap file. After the package books_test declaration we import the ginkgo and gomega packages into the test's top-level namespace by performing a . dot-import. Since Ginkgo and Gomega are DSLs this makes the tests more natural to read. If you prefer, you can avoid the dot-import via ginkgo bootstrap --nodot. Throughout this documentation we'll assume dot-imports.


RegisterFailHandler(Fail) is the single line of glue code connecting Ginkgo to Gomega. If we were to avoid dot-imports this would read as gomega.RegisterFailHandler(ginkgo.Fail) - what we're doing here is telling our matcher library (Gomega) which function to call (Ginkgo's Fail) in the event a failure is detected.


Finally the RunSpecs() call tells Ginkgo to start the test suite, passing it the *testing.T instance and a description of the suite. You should only ever call RunSpecs once and you can let Ginkgo worry about calling *testing.T for you.


Under the hood, ginkgo is simply calling go test. While you can run go test instead of the ginkgo CLI, Ginkgo has several capabilities that can only be accessed via ginkgo. We generally recommend users embrace the ginkgo CLI and treat it as a first-class member of their testing toolchain.


While you can add all your specs directly into books_suite_test.go you'll generally prefer to place your specs in separate files. This is particularly true if you have packages with multiple files that need to be tested. Let's say our book package includes a book.go model and we'd like to test its behavior. We can generate a test file like so:


As with the bootstrapped suite file, this test file is in the separate books_test package and dot-imports both ginkgo and gomega. Since we're testing the external interface of books Ginkgo adds an import statement to pull the books package into the test.


Ginkgo then adds an empty top-level Describe container node. Describe is part of the Ginkgo DSL and takes a description and a closure function. Describe("book", func() ) generates a container that will contain specs that describe the behavior of "Books".


By default, Go does not allow you to invoke bare functions at the top-level of a file. Ginkgo gets around this by having its node DSL functions return a value that is intended to be discarded. This allows us to write var _ = Describe(...) at the top-level which satisfies Go's top-level policies.


We use container nodes like Describe and Context to organize the different aspects of code that we are testing hierarchically. In this case we are describing our book model's ability to categorize books across two different contexts - the behavior for large books "With more than 300 pages" and small books "With fewer than 300 pages".


Finally, we use subject nodes like It to write a spec that makes assertions about the subject under test. In this case, we are ensuring that book.Category() returns the correct category enum based on the length of the book. We make these assertions using Gomega's Equal matcher and Expect syntax. You can learn much more about Gomega here - the examples used throughout these docs should be self-explanatory.


The container, setup, and subject nodes form a spec tree. Ginkgo uses the tree to construct a flattened list of specs where each spec can have multiple setup nodes but will only have one subject node.


Because there are two subject nodes, Ginkgo will identify two specs to run. For each spec, Ginkgo will run the closures attached to any associated setup nodes and then run the closure attached to the subject node. In order to share state between the setup node and subject node we define closure variables like lesMis and foxInSocks. This is a common pattern and the main way that tests are organized in Ginkgo.

3a8082e126
Reply all
Reply to author
Forward
0 new messages