Unit Test vs Integration Test
iOSテスト戦略の基盤: 単体を分離するユニットテストか、コンポーネント間のやり取りを検証する統合テストか。テストピラミッドを正しく構築する方法とは?
Appleの新しいオープンソーステストライブラリ——マクロベースで、Swift Concurrencyにネイティブ統合
約13年の実績を持つ、実戦で鍛えられたフレームワーク——UIテストとパフォーマンステストの唯一の選択肢
明確な傾向がある一方、『一括移行』は不要なリスクだ。新規の単体・統合テストはSwift Testingで書こう——コード量が減り、値の自動キャプチャ、組み込みのパラメータ化テスト、並列実行が手に入る。UI・パフォーマンステストはXCTestに残す。Appleのドキュメントがそれを求めている。両フレームワークは同一ターゲット内で共存でき、Swift 6.4がクロスフレームワークのレポーティングをもたらしたため、一括移行は不要——ファイル単位で進めよう。
| カテゴリー | Swift Testing | XCTest |
|---|---|---|
| パフォーマンス | 8/10 | 8/10 |
| 学習のしやすさ | 7/10 | 6/10 |
| エコシステム | 6/10 | 8/10 |
| コミュニティ | 6/10 | 8/10 |
| 求人市場 | 5/10 | 7/10 |
| 将来性 | 9/10 | 6/10 |
// Swift Testing — パラメータ化された非同期単体テスト
import Testing
@testable import PaymentKit
@Suite("価格計算")
struct PriceCalculatorTests {
@Test("有効なクーポンコードで割引が正しく計算される",
arguments: [
("SAVE10", 100.0, 90.0),
("SAVE20", 100.0, 80.0),
("NONE", 100.0, 100.0)
])
func discountIsApplied(code: String, base: Double, expected: Double) async throws {
let calculator = PriceCalculator()
let result = try await calculator.applyDiscount(code: code, to: base)
#expect(result == expected, "\(code) の期待値は \(expected)、実際は \(result)")
}
@Test("無効なクーポンはエラーを投げる")
func invalidCouponThrows() async throws {
let calculator = PriceCalculator()
await #expect(throws: CouponError.invalid) {
try await calculator.applyDiscount(code: "XXX", to: 100.0)
}
}
@Test("カート合計が必要", .disabled("カートサービスはまだ移行されていない"))
func cartTotalRequired() async throws {
let calculator = PriceCalculator()
try #require(calculator.cartTotal > 0)
}
}// XCTest — 同じシナリオ(価格計算)+ UIテスト
import XCTest
@testable import PaymentKit
final class PriceCalculatorTests: XCTestCase {
var calculator: PriceCalculator!
override func setUpWithError() throws {
calculator = PriceCalculator()
}
func testDiscountIsAppliedForKnownCoupons() async throws {
let cases: [(String, Double, Double)] = [
("SAVE10", 100.0, 90.0),
("SAVE20", 100.0, 80.0),
("NONE", 100.0, 100.0)
]
for (code, base, expected) in cases {
let result = try await calculator.applyDiscount(code: code, to: base)
XCTAssertEqual(result, expected, "クーポン: \(code)")
}
}
// XCTAssertThrowsErrorは同期的なので、非同期呼び出しではdo/catch + XCTFailを使用する
func testInvalidCouponThrows() async throws {
do {
_ = try await calculator.applyDiscount(code: "XXX", to: 100.0)
XCTFail("エラーが期待されていた")
} catch {
XCTAssertEqual(error as? CouponError, .invalid)
}
}
}
// UIテスト — XCTest/XCUITestでのみ可能
final class CheckoutUITests: XCTestCase {
func testApplyCouponButtonUpdatesTotal() throws {
let app = XCUIApplication()
app.launch()
app.textFields["couponField"].tap()
app.textFields["couponField"].typeText("SAVE10")
app.buttons["applyCouponButton"].tap()
XCTAssertEqual(app.staticTexts["totalLabel"].label, "$90.00")
}
}明確な傾向がある一方、『一括移行』は不要なリスクだ。新規の単体・統合テストはSwift Testingで書こう——コード量が減り、値の自動キャプチャ、組み込みのパラメータ化テスト、並列実行が手に入る。UI・パフォーマンステストはXCTestに残す。Appleのドキュメントがそれを求めている。両フレームワークは同一ターゲット内で共存でき、Swift 6.4がクロスフレームワークのレポーティングをもたらしたため、一括移行は不要——ファイル単位で進めよう。
無料相談を受けるいいえ。Swift Testingは新規の単体・統合テストにおいてAppleが推奨するデフォルトですが、XCTestが廃止されたわけではありません。XCUITest(UIオートメーション)とXCTMetric(パフォーマンステスト)は依然としてXCTestのみに存在します。両者は同一ターゲット内で共存でき、Swift 6.4以降はクロスフレームワークのレポーティングによって連携できます。