How does t.Skip in a Go test differ from just returning early from the test function?
answer
- three outcomes, not two
- an early return still says pass
- Log plus SkipNow, and SkipNow uses Goexit
- the exit status stays zero
basics
~20 sReturning early leaves the test reported as a normal pass. t.Skip logs a reason, marks the test as skipped, and stops it immediately, so the run records visibly that the test did not actually check anything.
solid answer
~40 s`t.Skip` is `t.Log` plus `t.SkipNow`: it records the reason, marks the test as skipped, and stops the test function the same way `FailNow` does — via `runtime.Goexit` on the test's goroutine, so deferred calls run and nothing after the call does. Returning early instead produces an ordinary pass, and a green run then hides the fact that the body never executed. A skip is not a failure: the package still reports ok and `go test` exits zero, but verbose output shows the test as skipped along with the reason string you passed. `t.Skipf` formats, `t.Skipped()` reports the state, and — like `Fatal` — `SkipNow` must be called from the goroutine running the test, not from one the test started.
code
go · 6 linesfunc TestEvaluateAgainstLiveRules(t *testing.T) {
if os.Getenv("RULES_URL") == "" {
t.Skip("RULES_URL not set; skipping live rule evaluation")
}
// nothing below runs when the variable is unset: SkipNow ends the function
}go deeper
Know that a Go test can end as passed, failed or skipped, that t.Skip takes a reason string, and that an early return is reported as a pass rather than a skip.
Explain that t.Skip is t.Log plus t.SkipNow, that SkipNow ends the test function with runtime.Goexit so deferred calls still run, and that the package still reports ok with a zero exit status.
Demonstrate the operational judgment: skips are invisible in a green run, an always-true guard silently deletes coverage, and skipping a flaky test throws away the only signal you had about it.
Take a position on how the suite treats skips over time — who reviews the skip list, when a long-lived skip becomes a delete or an environment fix, and how the run surfaces a rising skip count before coverage quietly erodes.
## Three possible outcomes, not two A Go test function ends in one of three states: passed, failed, or skipped. Most people internalise the first two and reach for an early `return` when a precondition is missing, which quietly collapses "could not run" into "passed". `t.Skip(args...)` is `t.Log(args...)` followed by `t.SkipNow()`. `t.Skipf(format, args...)` is the formatted variant. `t.SkipNow()` on its own marks the test as skipped and stops it without recording a reason — which is almost always worse, because the reason is the entire value of the outcome. ## How it stops the function `SkipNow` uses the same mechanism as `FailNow`: it calls `runtime.Goexit()`, terminating the goroutine that runs the test function after executing that goroutine's deferred calls. So: - statements after the `t.Skip` call do not run; - `defer`red work inside the test still runs; - the process is not exited, and the binary moves on to the next test. And the same restriction applies as for `Fatal`: `Skip`, `Skipf` and `SkipNow` must be called from the goroutine running the test function. Called from a goroutine the test started, they end that goroutine and the test itself carries on regardless. ## What the run reports A skipped test is not a failure. If nothing else in the package failed, the package is reported as ok and `go test` exits zero. In verbose output the test appears with a SKIP result and the reason string underneath, which is why the reason should say *why*, precisely, and ideally what would have to be true for the test to run: `t.Skip("RULES_URL not set; skipping live rule evaluation")` beats `t.Skip("skipping")`. `t.Skipped()` reports whether the current test has been marked skipped, which is occasionally useful inside shared teardown logic that wants to behave differently for a test that never ran. ## Where the skip goes The skip belongs at the top of the test, in a guard, before any expensive setup: - an environment variable naming an external service is unset; - the platform is not the one the test's behaviour is specific to (`runtime.GOOS`); - the test is expensive and the run asked for a short one — the standard guard is `if testing.Short() { t.Skip("skipping expensive rule replay in short mode") }`. A skip inside a subtest skips only that subtest; the parent continues with the remaining cases. ## The risk a skip carries Skips are silent by default. A guard that is accidentally always true — the environment variable was renamed, the service moved, the condition inverted — turns a real test into a permanently green no-op, and nothing in the exit status objects. This is the failure mode worth naming out loud in an interview: skips must be *visible*, and a suite with a growing skip count is losing coverage without ever going red. Two habits contain it. First, make the reason specific enough that reading the verbose output tells you what to fix. Second, treat the skip list as something someone actually looks at — a skip that has been in place for six months is either a test that should be deleted or an environment that should be fixed, and leaving it is a decision, not a default. ## Skip versus fail The line is about *whose* fault the missing run is. A missing dependency of the test environment is a skip: the code under test is not implicated. A missing dependency the code under test was supposed to provide is a failure. Skipping something because it is flaky puts a defect on the wrong side of that line — the flakiness is still there, and the skip removes the only signal you had about it.
- What is go test's exit status for a package whose tests were all skipped?Zero. A skip is not a failure, so the package is reported as ok. That is precisely the danger: a guard that has silently become always-true leaves a permanently green package with no coverage, and nothing in the exit status tells you. Watching the skip count, and keeping reason strings specific, is what makes that visible.
- Can t.Skip be called from a goroutine the test started?No. Like `Fatal` and `FailNow`, `SkipNow` must be called from the goroutine running the test function. From another goroutine it ends only that goroutine, via `runtime.Goexit`, while the test function keeps running and reports whatever outcome it reaches on its own.
- How do you keep an expensive test out of a fast local run?Guard it with `if testing.Short() { t.Skip("skipping expensive rule replay in short mode") }`. `testing.Short` reports whether the run asked for a short pass, so the same test file serves both a quick inner loop and a full run, and the skip line says exactly why the test did not execute.
saying these in an interview costs you the question
- Thinks a skipped test makes the package fail
- Returns early instead of skipping, so the test silently passes
- Believes t.Skip only marks the test and lets the body finish
- Calls t.Skip from a spawned goroutine expecting the test to end
- Uses a skip to hide a flaky test rather than fixing it