Swift Testing basics
Unit 12 · Swift Testing. This is the framework Segue itself uses to grade every exercise you submit, so you've been reading its output since Unit 1 — now you write it.
Swift Testing is the modern, first-party testing framework: a test is just a function marked
@Test, and an assertion is just #expect(...) wrapped around an ordinary boolean
expression. There's no base class to subclass, no test-prefix naming rule, no ceremony.
If you can write a function and a comparison, you can write a test.
The smallest possible test
import Testing @Test func additionWorks() { #expect(1 + 1 == 2) }
Three things are load-bearing: you import Testing, you mark the function @Test, and you
state what you expect with #expect. The macro takes any Bool expression. When it's
false, the test fails and — because #expect is a macro, not a plain function — the report
shows you the actual values on each side of the ==, not just "expected true, got false."
#expect is a macro (the # prefix), which is why it can see into your expression and
report 1 + 1 → 2, expected 3. That richer failure message is a real day-to-day advantage.
Testing your own code
A test exercises a function and asserts on the result. Say you have this in your module:
func greeting(for name: String) -> String { "Hello, \(name)!" }
The test names the behavior and checks it:
import Testing @Test("greeting includes the name") func greetingIncludesName() { #expect(greeting(for: "Ada") == "Hello, Ada!") }
The string in @Test("…") is a display name. Use it — a test list that reads
"greeting includes the name" beats one that reads greetingIncludesName(). The function
name still exists for the compiler; the display name is for humans reading results.
Grouping with @Suite
Related tests live together in a suite. A struct (or actor) marked @Suite groups
its @Test methods, and — this is the quiet superpower — Swift Testing makes a fresh
instance of the suite for every test, so tests can't leak state into each other.
@Suite("Greeting") struct GreetingTests { let name = "Ada" // rebuilt fresh for each test below @Test("includes the name") func includesName() { #expect(greeting(for: name) == "Hello, Ada!") } @Test("is never empty") func neverEmpty() { #expect(!greeting(for: name).isEmpty) } }
Stored properties become your per-test setup; the initializer runs before each test, and
(if you add one) deinit runs after. No shared mutable state, no ordering surprises.
You don't always need a @Suite. Free @Test functions at file scope are perfectly
valid and are what the Segue runner uses for most exercises. Reach for a suite when tests
share setup (a common fixture) or when grouping makes the results easier to read.
How Segue grades you with this
Every graded exercise ships a hidden @Test suite you never see. When you press Run, your
submission is compiled into a library module named Solution, and the hidden tests do
@testable import Solution and call your code:
import Testing @testable import Solution // your submission, exposed to the tests @Test("fizzbuzz(15) is FizzBuzz") func fizzbuzzFifteen() { #expect(fizzbuzz(15) == "FizzBuzz") }
@testable import exposes your internal (default-visibility) declarations to the test
target, which is why exercise starters never mark the graded function private. Pass all the
hidden @Tests and the exercise is green. That's the whole grader — the same framework in
this lesson, pointed at your code.
Your turn
Implement FizzBuzz as a pure function and watch a small suite check each rule.
Knowledge check
Q: What are the three things every Swift Testing file needs to run one check?
import Testing, a function marked @Test, and an assertion — #expect(someBool).
Q: Why can two tests in the same @Suite not corrupt each other's state?
Swift Testing constructs a brand-new instance of the suite type for each test, so stored
properties are fresh every time. There's no shared instance to leak through.