admaDIC App Development & IT Solutions

Swift Testing Framework: Parameterized Tests for Game Logic

by Annett Schwarze | 2026-03-27

Why Parameterized Testing

The new Swift Testing framework (introduced in Swift 5.9 and enhanced in Swift 6) brings modern testing capabilities to iOS development. One standout feature is "parameterized tests" using the `@Test` macro with arguments, allowing you to test multiple scenarios with a single, clean test function. This is especially powerful for game development where you need to validate logic across different states, values, and configurations.

Unlike XCTest's verbose approach requiring separate test methods or loops with manual assertions, Swift Testing's parameterized tests are declarative, expressive, and produce detailed failure reports for each parameter combination.

The Code

Here is a real-world example from a SpriteKit garden game that validates plant growth goals across multiple scenarios:

        
import Testing
@testable import MindGrowGarden

struct GardenModelTests {
    @Test("Grow specific plants goal validation",
          arguments: [
            (planted: 3, fullyGrown: 3, required: 2, types: [GardenFlower.flower0, GardenFlower.flower1] as [GardenFlower], shouldPass: true),
            (planted: 3, fullyGrown: 2, required: 3, types: [GardenFlower.flower0, GardenFlower.flower1] as [GardenFlower], shouldPass: false),
            (planted: 5, fullyGrown: 5, required: 3, types: [GardenFlower.flower0, GardenFlower.flower1, GardenFlower.flower2] as [GardenFlower], shouldPass: true),
            (planted: 3, fullyGrown: 3, required: 3, types: [GardenFlower.flower0] as [GardenFlower], shouldPass: false),  // Wrong types
            (planted: 0, fullyGrown: 0, required: 1, types: [GardenFlower.flower0] as [GardenFlower], shouldPass: false)
          ])
    func testGrowSpecificPlants(
        planted: Int,
        fullyGrown: Int,
        required: Int,
        types: [GardenFlower],
        shouldPass: Bool
    ) {
        // Setup
        let model = GardenModel()
        model.prepare(width: 5, height: 5)

        // Plant flowers of required types
        for i in 0 ..< planted {
                             let flowerType = types[i % types.count]
                             model.cells[i].flower = flowerType
                             model.cells[i].flowerGrowthState = FlowerGrowthState()

            // Grow to maturity if needed
            if i < fullyGrown {
                model.cells[i].flowerGrowthState.grow = .grow4
            }
        }

        // Note: Using growDifferent as a proxy since growSpecific doesn't exist in LevelGoal
        // This test validates the logic for growing different plant types
        let goal = LevelGoal.growDifferent(count: required)

        // Validate
        #expect(model.checkGoalCompletion(for: goal) == shouldPass)
    }
}
    

Better Overview in Xcode

The Test navigator in Xcode nicely shows the tests with the arguments right in the tree. No need to deep dive into the source code to get a quick overview.

Confluence - Project Structure

The Traditional Way

In comparison traditional XCTest approach would require either a list of test functions, which are prone to copy&paste errors:

        
func testGrowSpecificPlants_Scenario1() { ... }
func testGrowSpecificPlants_Scenario2() { ... }
func testGrowSpecificPlants_Scenario3() { ... }
// ... and so on
    

Or the developer needs to implement a loop over a list of arguments, where the one is responsible for producing meaningful log messages:

        
func testGrowSpecificPlants() {
    let scenarios = [...]
    for (index, scenario) in scenarios.enumerated() {
        XCTAssertEqual(result, expected, "Failed at scenario \(index)")
    }
}
    

Summary

Swift Testing's parametrized tests give you:

In short, parameterized tests in Swift Testing are used to transform what was previously repetitive and error-prone boilerplate into a concise, expressive, and scalable testing strategy, an approach that is especially valuable in game logic, where combinations of state can grow rapidly. By defining scenarios as data rather than duplicating code, readability is improved, more reliable diagnostics are produced, and extensibility is simplified as the game evolves. Whether growth systems, scoring rules, or AI behavior are being validated, the focus is shifted toward what should happen instead of how tests are structured, resulting in a test suite that is more robust and easier to maintain.

 

www.admadic.de | webmaster@admadic.de | Legal Notice and Trademarks | Privacy
© 2005-2007 - admaDIC | All Rights Reserved
All other trademarks and/or registered trademarks are the property of their respective owners
Last Change: Fri Mar 27 09:03:35 2026 GMT