Untuk memutar ulang input pengujian Swift yang diacak di Mac Anda, berikan seed eksplisit ke generator baru untuk setiap kasus pengujian. Pertahankan generator, pemetaan input, dan urutan pengambilan nilai, lalu catat seed bersama setiap kegagalan.

Panduan ini membuat paket Swift lokal kecil yang memeriksa perhitungan aritmetika sintetis. Panduan ini menyertakan contoh Swift Testing lengkap dan alternatif XCTest. Semua data pengujian berasal dari perhitungan lokal; paket ini tidak memiliki dependensi eksternal.

Apa yang sebenarnya dibuat dapat diulang oleh seed?

Ada dua sumber variasi yang terpisah: nilai yang dihasilkan pengujian dan urutan runner pengujian menjadwalkan pengujian. Seed yang ditentukan aplikasi hanya mengendalikan yang pertama jika setiap nilai yang dihasilkan berasal dari generator terkait. Apple mendokumentasikan bahwa operasi acak biasa menggunakan SystemRandomNumberGenerator yang di-seed secara otomatis secara default. Inisialisator yang didokumentasikan tidak menerima seed. SystemRandomNumberGenerator

Xcode juga menyediakan urutan eksekusi acak untuk mengungkap dependensi antar-pengujian. Dokumentasi test plan-nya menjelaskan urutan eksekusi alfabetis atau acak, tetapi tidak mendokumentasikan seed pemutaran ulang yang diberikan pengguna untuk pengaturan tersebut. Karena itu, cara yang didukung di sini adalah generator input dengan seed eksplisit di dalam pengujian independen. Cara ini tidak menjanjikan reproduksi penjadwalan runner. Meningkatkan penilaian kode dengan mengatur pengujian ke dalam test plan

Gunakan panduan keputusan ini sebelum mengubah pengujian yang tidak stabil:

Apa yang bervariasi antar-eksekusi? Apa yang perlu dikendalikan
Input numerik yang dihasilkan Seed, implementasi generator, pemetaan input, dan urutan pengambilan nilai
Hasil setelah pengujian lain dijalankan Status yang dapat berubah dan inisialisasi per pengujian
Hasil hanya saat eksekusi bersamaan Sumber daya bersama yang dapat berubah dan pekerjaan yang tumpang tindih
Input setelah pembaruan toolchain Implementasi pemetaan input dan input konkret yang dicatat

Swift secara eksplisit mendukung generator khusus melalui RandomNumberGenerator, yang operasi wajibnya adalah next() mutating yang mengembalikan UInt64. Dokumentasi sumber proyek Swift menyebutkan urutan berulang untuk pengujian sebagai salah satu alasan untuk menyediakan generator yang dapat di-seed. Random.swift

Buat paket pengujian lokal kecil

Gunakan Mac dengan Xcode dan toolchain Swift 6 atau yang lebih baru. Periksa toolchain baris perintah yang dipilih di Terminal, lalu buat direktori baru dan inisialisasi paket pustaka:

swift --version
mkdir SeedReplay
cd SeedReplay
swift package init --type library

Pengujian integrasi proyek Swift menjalankan pembuatan paket pustaka dengan swift package init --type library. Swift 6 menyertakan Swift Testing secara langsung, jadi contoh ini tidak memerlukan dependensi paket untuk import Testing. Swift Package Manager menjalankan pengujian Swift Testing dan XCTest jika keduanya ada. Pengujian integrasi inisialisasi paket Swift, Mengumumkan Swift 6

Pertahankan manifes paket dan sumber pustaka yang dihasilkan. Ganti berkas pengujian contoh yang dihasilkan dengan berkas pengujian di bawah ini, semuanya di dalam Tests/SeedReplayTests. Fungsi aritmetika berada di berkas dukungan pengujian agar demonstrasi ini tetap mandiri. Dalam proyek yang sudah ada, gunakan fungsi yang benar-benar sedang diuji.

Tetapkan kontrak seed dan pembuatan input

Buat Tests/SeedReplayTests/ReplaySupport.swift dengan isi lengkap berikut:

import Foundation

// Local synthetic test-data generator.
// Mutable value state: give each test case its own instance.
// Its output sequence is part of this example's replay contract.
struct ReplayGenerator: RandomNumberGenerator {
    private var state: UInt64

    init(seed: UInt64) {
        state = seed
    }

    mutating func next() -> UInt64 {
        state &+= 0x9E3779B97F4A7C15
        var value = state
        value = (value ^ (value >> 30)) &* 0xBF58476D1CE4E5B9
        value = (value ^ (value >> 27)) &* 0x94D049BB133111EB
        return value ^ (value >> 31)
    }
}

enum ReplayConfigurationError: Error {
    case invalidSeed(String)
}

func replaySeeds() throws -> [UInt64] {
    guard let text = ProcessInfo.processInfo.environment["TEST_SEED"] else {
        return [0, 42, 2026]
    }

    guard !text.isEmpty,
          text.utf8.allSatisfy({ $0 >= 48 && $0 <= 57 }),
          let seed = UInt64(text) else {
        throw ReplayConfigurationError.invalidSeed(text)
    }

    return [seed]
}

func generatedInputs(seed: UInt64) -> [Int] {
    var generator = ReplayGenerator(seed: seed)
    return (0..<256).map { _ in
        // Exactly one draw per input; retain the upper ten bits.
        Int(generator.next() >> 54)
    }
}

func triangularNumber(_ n: Int) -> Int {
    precondition((0...1023).contains(n))
    return n * (n + 1) / 2
}

func referenceTotal(_ n: Int) -> Int {
    precondition((0...1023).contains(n))
    return (0...n).reduce(0, +)
}

Konfigurasi menerima UInt64 desimal, termasuk nol. Jika TEST_SEED tidak ditetapkan, tiga seed eksplisit akan dipilih; nilai kosong, negatif, nonnumerik, atau yang melampaui batas akan menghasilkan galat. Dengan begitu, seed yang salah ketik tidak akan diam-diam memilih eksekusi lain.

Generator menggunakan aritmetika wraparound eksplisit. Setiap input yang dihasilkan mengambil sepuluh bit teratas dari satu keluaran, sehingga menghasilkan nilai dari 0 hingga 1023. Pemetaan tersebut sengaja dibuat tetap di kode sumber. Aritmetikanya tetap sederhana: hasil maksimum yang diharapkan adalah 523776. Ini adalah resep input sintetis yang dibatasi, tanpa klaim mengenai cakupan pengujian statistik.

Mengapa tidak menggunakan Int.random(in:using:) di sini? API itu mendukung generator yang diberikan dan berguna untuk pemutaran ulang dengan toolchain tetap. Namun, Apple secara eksplisit memperingatkan bahwa algoritma pembuatan nilainya dapat berubah antarversi Swift, bahkan jika generator menghasilkan urutan dasar yang sama. Dengan mengelola sendiri pemetaan kecil dalam contoh ini, kita menghilangkan dependensi tersebut. Int.random(in:using:)

Jalankan dan putar ulang versi Swift Testing

Buat Tests/SeedReplayTests/SeedReplayTests.swift:

import Testing

@Test(arguments: try replaySeeds())
func triangularTotals(seed: UInt64) {
    print("triangularTotals seed=\(seed) recipe=upper10-v1 count=256")

    for (index, n) in generatedInputs(seed: seed).enumerated() {
        let actual = triangularNumber(n)
        let expected = referenceTotal(n)

        #expect(
            actual == expected,
            "seed=\(seed) case=\(index) n=\(n) actual=\(actual) expected=\(expected)"
        )

        if actual != expected {
            return
        }
    }
}

@Test
func replayRecipeIsStable() {
    var generator = ReplayGenerator(seed: 0)
    let firstWord = generator.next()
    #expect(firstWord == 0xE220A8397B1DCDAF)

    let firstInputs = Array(generatedInputs(seed: 0).prefix(3))
    #expect(firstInputs == [904, 441, 27])
}

Pengujian berparameter menjadikan setiap seed sebagai kasus pengujian terpisah. Apple mendokumentasikan bahwa arguments: memasok elemen koleksi ke pemanggilan pengujian dan mengizinkan ekspresi yang melempar galat. Dengan demikian, pemilihan seed terlihat dalam model pengujian. Indeks perulangan eksplisit kemudian mengidentifikasi input tertentu di dalam seed tersebut. Menerapkan pengujian berparameter

Jalankan koleksi default, lalu putar ulang satu seed dua kali:

swift test
TEST_SEED=42 swift test
TEST_SEED=42 swift test

Dua eksekusi dengan seed ini seharusnya menghasilkan input numerik yang sama selama kode sumber tidak berubah. Untuk seed 42, tiga input pertamanya adalah 759, 163, dan 285. Pengujian memeriksa 256 input untuk seed tersebut. Pengujian stabilitas terpisah sengaja tetap menggunakan seed nol: pengujian ini memeriksa generator dan pemetaan apa pun seed pengujian aritmetika yang dipilih.

Hasil yang diharapkan dari implementasi yang disediakan adalah semua asersi lolos. Swift tidak tersedia di lingkungan riset, jadi berkas Swift diperiksa tanpa dikompilasi. Perhitungan Python independen memeriksa semua 768 perbandingan aritmetika untuk tiga seed default dan nilai-nilai awal yang ditampilkan; pemeriksaan itu tidak menggantikan kompilasi paket Swift.

Berlatih memutar ulang kesalahan perhitungan yang disengaja

Untuk melihat jalur diagnostik, ganti sementara ekspresi return di triangularNumber(_:) dengan potongan berlabel berikut:

// Temporary replacement inside triangularNumber(_:).
return n * n / 2

Jalankan TEST_SEED=42 swift test lagi. Input pertama yang dihasilkan adalah 759. Ekspresi yang diubah menghasilkan 288040; total acuannya adalah 288420. Pesan asersi mengidentifikasi seed 42, indeks kasus 0, dan input 759. Return lebih awal menghentikan pemanggilan itu setelah ketidakcocokan pertama tercatat, sehingga diagnostik tetap terfokus.

Pulihkan return n * (n + 1) / 2 dan jalankan kembali perintah yang sama. Pertahankan seed yang sama saat mengevaluasi perbaikan. Setelah input yang dihasilkan mengungkap cacat yang berguna, simpan juga input konkret tersebut dalam pengujian regresi bernama; input itu tetap mudah dipahami meskipun resep pembuatannya kelak berubah.

Terapkan siklus hidup yang sama pada XCTest

Untuk target berbasis XCTest, pertahankan ReplaySupport.swift dan gunakan berkas pengujian lengkap berikut sebagai pengganti berkas Swift Testing. Beri nama Tests/SeedReplayTests/SeedReplayXCTests.swift:

import XCTest

final class SeedReplayXCTests: XCTestCase {
    func testTriangularTotals() throws {
        for seed in try replaySeeds() {
            print("testTriangularTotals seed=\(seed) recipe=upper10-v1 count=256")

            for (index, n) in generatedInputs(seed: seed).enumerated() {
                let actual = triangularNumber(n)
                let expected = referenceTotal(n)

                XCTAssertEqual(
                    actual,
                    expected,
                    "seed=\(seed) case=\(index) n=\(n)"
                )

                if actual != expected {
                    return
                }
            }
        }
    }

    func testReplayRecipeIsStable() {
        var generator = ReplayGenerator(seed: 0)
        XCTAssertEqual(generator.next(), 0xE220A8397B1DCDAF)
        XCTAssertEqual(
            Array(generatedInputs(seed: 0).prefix(3)),
            [904, 441, 27]
        )
    }
}

Perintah Terminal yang sama berlaku. Setiap pemanggilan generatedInputs(seed:) membuat generator baru, sehingga tidak ada pengujian yang mengonsumsi aliran acak milik pengujian lain. Tidak ada status generator yang perlu dibersihkan setelah pengujian.

Jika fixture XCTest yang sudah ada menyimpan generator sebagai properti, atur ulang sebelum setiap metode pengujian. Apple mendokumentasikan metode penyiapan instance untuk inisialisasi per pengujian dan penyiapan kelas untuk status yang digunakan bersama di seluruh kelas. Aliran acak yang dapat berubah termasuk dalam inisialisasi per pengujian jika pengujian harus dapat diputar ulang secara independen. Menyiapkan status pengujian berikutnya di teardown tidak andal karena teardown tidak dijamin berjalan setelah crash. Menyiapkan dan Membongkar Status dalam Pengujian Anda

Pisahkan penjadwalan Xcode dari pemutaran ulang input

Untuk menjalankan paket ini dari Xcode, buka Package.swift-nya. Untuk proyek yang menggunakan test plan, pilih Product > Scheme > Edit Test Plan, pilih konfigurasi yang sesuai, lalu tambahkan TEST_SEED dengan nilai desimal 42 di bagian Environment Variables. Jalankan dengan Product > Test, lalu pastikan seed yang dicetak muncul di keluaran pengujian. Apple mendokumentasikan konfigurasi lingkungan test plan dan pemilihan plan aktif sebelum menjalankan pengujian. Meningkatkan penilaian kode dengan mengatur pengujian ke dalam test plan

Pastikan seed yang dicetak setiap kali Anda beralih antara Terminal dan Xcode. Variabel yang ditetapkan untuk satu perintah Terminal bukanlah pengaturan test plan Xcode. Baris log memberi tahu konfigurasi yang benar-benar dibaca oleh proses pengujian.

Swift Testing menjalankan pengujian secara paralel secara default. Trait .serialized dapat menserialkan kasus dalam pengujian berparameter atau pengujian dalam sebuah suite, tetapi tidak mengatur pengujian yang tidak terkait. Apple juga mendokumentasikan swift test --no-parallel untuk menonaktifkan paralelisasi secara global. Kedua kontrol tersebut tidak menyediakan seed input. Contoh ini tidak memerlukan serialisasi karena setiap kasus memiliki generator dan input numeriknya sendiri. Menjalankan pengujian secara serial atau paralel

Jika sebuah pengujian lolos saat dijalankan sendiri tetapi gagal bersama pengujian lain meskipun input yang dihasilkan sama, periksa status bersama. Eksekusi serial dapat membantu menyelidiki pekerjaan yang tumpang tindih, tetapi seed tidak dapat mengatur ulang objek lain yang dapat berubah atau mereproduksi seluruh jadwal konkurensi.

Apa yang perlu disimpan bersama seed yang gagal?

Simpan informasi yang cukup untuk merekonstruksi urutan input:

  • Nama pengujian dan seed desimal.
  • Versi generator dan pemetaan, yaitu upper10-v1.
  • Indeks kasus dan input konkret dari pesan asersi.
  • Revisi sumber, versi Swift, dan versi Xcode yang dipilih.
  • Jumlah input dan perubahan apa pun pada cara pengambilan nilai acak.

Dalam contoh ini, menambahkan satu pengambilan nilai generator sebelum perulangan akan menggeser setiap input berikutnya. Mengubah jumlah bit yang digeser akan mengubah pemetaan. Menaikkan jumlah input mempertahankan prefiks yang ada hanya selama generator dan pemetaan tidak berubah. Hal-hal ini merupakan konsekuensi dari kontrak pemutaran ulang kode tersebut.

Pertahankan koleksi kecil seed tetap untuk eksekusi rutin, coba seed eksplisit tambahan saat mengeksplorasi, dan simpan input regresi konkret ketika input tersebut mengungkap cacat. Pemutaran ulang yang berguna dimulai dengan nilai yang sama dan status awal yang diketahui; seed menjadi andal karena pengujian mengendalikan cara seed itu digunakan.

Sumber