Test-gating for Go via Google Spreadsheet. Enable or disable tests without changing code — just update a date in a Google Sheet.
A Go port of get-skipper/skipper, supporting the standard testing package, testify, and Ginkgo.
A Google Spreadsheet stores test IDs with optional disabledUntil dates:
| testId | disabledUntil | notes |
|---|---|---|
tests/auth_test.go > TestLogin |
||
tests/payment_test.go > TestCheckout |
2099-12-31 |
Flaky on CI |
tests/auth_test.go > Auth > Login > can log in |
2026-06-01 |
Under investigation |
- Empty
disabledUntil→ test runs normally - Past date → test runs normally
- Future date → test is skipped automatically
Tests not listed in the spreadsheet always run (opt-out model).
| Package | Import path | Framework |
|---|---|---|
core |
github.com/get-skipper/skipper-go/core |
Shared core (resolver, client, cache) |
testing |
github.com/get-skipper/skipper-go/testing |
Standard library testing |
testify |
github.com/get-skipper/skipper-go/testify |
testify/suite |
ginkgo |
github.com/get-skipper/skipper-go/ginkgo |
Ginkgo v2 |
# Standard testing package
go get github.com/get-skipper/skipper-go/testing
# testify
go get github.com/get-skipper/skipper-go/testify
# Ginkgo
go get github.com/get-skipper/skipper-go/ginkgo-
Create a Google Spreadsheet with the following columns in row 1:
testIddisabledUntilnotes(optional)
-
Create a Google Cloud service account and download the JSON key file.
-
Share the spreadsheet with the service account's email (
client_emailin the JSON). -
Note the spreadsheet ID from the URL:
https://docs.google.com/spreadsheets/d/YOUR_SPREADSHEET_ID/edit
Three formats are accepted:
| Format | Type | Use case |
|---|---|---|
| File path | core.FileCredentials{Path: "./service-account.json"} |
Local development |
| Base64 string | core.Base64Credentials{Encoded: os.Getenv("GOOGLE_CREDS")} |
CI/CD env vars |
| Inline struct | core.ServiceAccountCredentials{...} |
Programmatic use |
package mypackage_test
import (
"os"
"testing"
"github.com/get-skipper/skipper-go/core"
skippertest "github.com/get-skipper/skipper-go/testing"
)
func TestMain(m *testing.M) {
s := &skippertest.SkipperTestMain{
Config: core.SkipperConfig{
SpreadsheetID: "your-spreadsheet-id",
Credentials: core.FileCredentials{Path: "./service-account.json"},
},
}
os.Exit(s.Run(m))
}
func TestLogin(t *testing.T) {
skippertest.SkipIfDisabled(t)
// ... test body
}
func TestCheckout(t *testing.T) {
skippertest.SkipIfDisabled(t)
t.Run("with valid card", func(t *testing.T) {
skippertest.SkipIfDisabled(t)
// ... subtest body
})
}Test ID format: tests/auth_test.go > TestLogin
Subtests: tests/auth_test.go > TestCheckout > with valid card
package mypackage_test
import (
"testing"
"github.com/get-skipper/skipper-go/core"
skippertestify "github.com/get-skipper/skipper-go/testify"
"github.com/stretchr/testify/suite"
)
type AuthSuite struct {
skippertestify.SkipperSuite
}
func (s *AuthSuite) TestLogin() {
// SetupTest already called t.Skip() if disabled — just write the test
s.Equal(200, 200)
}
func (s *AuthSuite) TestLogout() {
s.True(true)
}
func TestAuthSuite(t *testing.T) {
s := &AuthSuite{
SkipperSuite: skippertestify.SkipperSuite{
Config: core.SkipperConfig{
SpreadsheetID: "your-spreadsheet-id",
Credentials: core.FileCredentials{Path: "./service-account.json"},
},
},
}
suite.Run(t, s)
}Test ID format: tests/auth_suite_test.go > AuthSuite > TestLogin
package mypackage_test
import (
"testing"
"github.com/get-skipper/skipper-go/core"
skipperginkgo "github.com/get-skipper/skipper-go/ginkgo"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestAuth(t *testing.T) {
RegisterFailHandler(Fail)
skipperginkgo.RegisterSkipperHooks(core.SkipperConfig{
SpreadsheetID: "your-spreadsheet-id",
Credentials: core.FileCredentials{Path: "./service-account.json"},
})
RunSpecs(t, "Auth Suite")
}
var _ = Describe("Auth", func() {
Context("Login", func() {
It("can log in with valid credentials", func() {
Expect(true).To(BeTrue())
})
})
})Test ID format: tests/auth_test.go > Auth > Login > can log in with valid credentials
| Mode | Env var | Behavior |
|---|---|---|
read-only (default) |
— | Fetch spreadsheet, skip disabled tests |
sync |
SKIPPER_MODE=sync |
Same as above, then reconcile spreadsheet with discovered tests |
In sync mode, new tests are added to the spreadsheet. Deletion of orphaned rows requires SKIPPER_SYNC_ALLOW_DELETE=true.
| Variable | Default | Description |
|---|---|---|
SKIPPER_MODE |
read-only |
Set to sync to enable sync mode |
SKIPPER_DEBUG |
— | Any non-empty value enables verbose debug logging |
SKIPPER_FAIL_OPEN |
true |
When true, Initialize returns nil instead of an error if the API is unreachable and no usable disk cache exists, so all tests are allowed to run rather than blocking CI/CD |
SKIPPER_CACHE_TTL |
300 |
Seconds to keep the on-disk cache (.skipper-cache.json) as a fallback when the API is unavailable. Set to 0 to disable caching |
SKIPPER_SYNC_ALLOW_DELETE |
false |
In sync mode, orphaned rows are not deleted by default. Set to true to restore automatic pruning of tests that no longer exist |
Test IDs follow the pattern: {relative/path/to/file_test.go} > {title parts...}
- Paths are relative to the working directory
- Title parts are separated by
> - IDs are case-insensitive and whitespace-collapsed for matching
You can include additional read-only sheets to inherit disabled tests from:
core.SkipperConfig{
SpreadsheetID: "your-spreadsheet-id",
Credentials: core.FileCredentials{Path: "./service-account.json"},
ReferenceSheets: []string{"GlobalDisabled", "FlakyTests"},
}When the same test ID appears in multiple sheets, the most restrictive (latest) disabledUntil date wins.
- name: Run tests
run: go test ./...
env:
GOOGLE_CREDS_B64: ${{ secrets.GOOGLE_CREDS_B64 }}Use Base64Credentials to pass credentials via environment variable:
core.Base64Credentials{Encoded: os.Getenv("GOOGLE_CREDS_B64")}