ios-unit-test

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

iOS Unit Testing Expert

iOS单元测试专家

Expert in iOS testing with XCTest framework and best practices.
精通基于XCTest框架的iOS测试及最佳实践。

Core Testing Principles

核心测试原则

Test Structure and Organization

测试结构与组织

  • Follow the Arrange-Act-Assert (AAA) pattern
  • Use descriptive test method names explaining scenario and expected outcome
  • Group related tests using nested test classes or test suites
  • Maintain test independence - each test should run in isolation
  • 遵循Arrange-Act-Assert(AAA)模式
  • 使用描述性测试方法名,说明场景与预期结果
  • 使用嵌套测试类或测试套件对相关测试进行分组
  • 保持测试独立性——每个测试应独立运行

XCTest Framework Fundamentals

XCTest框架基础

swift
import XCTest
@testable import YourApp

class UserServiceTests: XCTestCase {
    // System Under Test
    var sut: UserService!
    var mockNetworkManager: MockNetworkManager!

    override func setUpWithError() throws {
        try super.setUpWithError()
        mockNetworkManager = MockNetworkManager()
        sut = UserService(networkManager: mockNetworkManager)
    }

    override func tearDownWithError() throws {
        sut = nil
        mockNetworkManager = nil
        try super.tearDownWithError()
    }

    // MARK: - fetchUser Tests

    func test_fetchUser_withValidId_returnsUser() async throws {
        // Arrange
        let expectedUser = User(id: "123", name: "John Doe")
        mockNetworkManager.fetchUserResult = .success(expectedUser)

        // Act
        let result = try await sut.fetchUser(id: "123")

        // Assert
        XCTAssertEqual(result.id, expectedUser.id)
        XCTAssertEqual(result.name, expectedUser.name)
        XCTAssertEqual(mockNetworkManager.fetchUserCallCount, 1)
        XCTAssertEqual(mockNetworkManager.lastFetchedUserId, "123")
    }

    func test_fetchUser_withInvalidId_throwsError() async {
        // Arrange
        mockNetworkManager.fetchUserResult = .failure(NetworkError.notFound)

        // Act & Assert
        do {
            _ = try await sut.fetchUser(id: "invalid")
            XCTFail("Expected error to be thrown")
        } catch {
            XCTAssertTrue(error is NetworkError)
            XCTAssertEqual(error as? NetworkError, .notFound)
        }
    }
}
swift
import XCTest
@testable import YourApp

class UserServiceTests: XCTestCase {
    // 被测系统
    var sut: UserService!
    var mockNetworkManager: MockNetworkManager!

    override func setUpWithError() throws {
        try super.setUpWithError()
        mockNetworkManager = MockNetworkManager()
        sut = UserService(networkManager: mockNetworkManager)
    }

    override func tearDownWithError() throws {
        sut = nil
        mockNetworkManager = nil
        try super.tearDownWithError()
    }

    // MARK: - fetchUser 测试

    func test_fetchUser_withValidId_returnsUser() async throws {
        // 准备
        let expectedUser = User(id: "123", name: "John Doe")
        mockNetworkManager.fetchUserResult = .success(expectedUser)

        // 执行
        let result = try await sut.fetchUser(id: "123")

        // 断言
        XCTAssertEqual(result.id, expectedUser.id)
        XCTAssertEqual(result.name, expectedUser.name)
        XCTAssertEqual(mockNetworkManager.fetchUserCallCount, 1)
        XCTAssertEqual(mockNetworkManager.lastFetchedUserId, "123")
    }

    func test_fetchUser_withInvalidId_throwsError() async {
        // 准备
        mockNetworkManager.fetchUserResult = .failure(NetworkError.notFound)

        // 执行 & 断言
        do {
            _ = try await sut.fetchUser(id: "invalid")
            XCTFail("Expected error to be thrown")
        } catch {
            XCTAssertTrue(error is NetworkError)
            XCTAssertEqual(error as? NetworkError, .notFound)
        }
    }
}

Mocking and Dependency Injection

模拟与依赖注入

Protocol-Based Mocking

基于协议的Mock

swift
// Protocol definition
protocol NetworkManagerProtocol {
    func fetchUser(id: String) async throws -> User
    func saveUser(_ user: User) async throws
}

// Mock implementation
class MockNetworkManager: NetworkManagerProtocol {
    // Call tracking
    var fetchUserCallCount = 0
    var lastFetchedUserId: String?
    var saveUserCallCount = 0
    var lastSavedUser: User?

    // Configurable results
    var fetchUserResult: Result<User, Error>?
    var saveUserResult: Result<Void, Error> = .success(())

    func fetchUser(id: String) async throws -> User {
        fetchUserCallCount += 1
        lastFetchedUserId = id

        switch fetchUserResult {
        case .success(let user):
            return user
        case .failure(let error):
            throw error
        case .none:
            throw TestError.noMockResult
        }
    }

    func saveUser(_ user: User) async throws {
        saveUserCallCount += 1
        lastSavedUser = user

        switch saveUserResult {
        case .success:
            return
        case .failure(let error):
            throw error
        }
    }

    // Reset for reuse
    func reset() {
        fetchUserCallCount = 0
        lastFetchedUserId = nil
        saveUserCallCount = 0
        lastSavedUser = nil
        fetchUserResult = nil
        saveUserResult = .success(())
    }
}

enum TestError: Error {
    case noMockResult
}
swift
// 协议定义
protocol NetworkManagerProtocol {
    func fetchUser(id: String) async throws -> User
    func saveUser(_ user: User) async throws
}

// Mock实现
class MockNetworkManager: NetworkManagerProtocol {
    // 调用跟踪
    var fetchUserCallCount = 0
    var lastFetchedUserId: String?
    var saveUserCallCount = 0
    var lastSavedUser: User?

    // 可配置结果
    var fetchUserResult: Result<User, Error>?
    var saveUserResult: Result<Void, Error> = .success(())

    func fetchUser(id: String) async throws -> User {
        fetchUserCallCount += 1
        lastFetchedUserId = id

        switch fetchUserResult {
        case .success(let user):
            return user
        case .failure(let error):
            throw error
        case .none:
            throw TestError.noMockResult
        }
    }

    func saveUser(_ user: User) async throws {
        saveUserCallCount += 1
        lastSavedUser = user

        switch saveUserResult {
        case .success:
            return
        case .failure(let error):
            throw error
        }
    }

    // 重置以便复用
    func reset() {
        fetchUserCallCount = 0
        lastFetchedUserId = nil
        saveUserCallCount = 0
        lastSavedUser = nil
        fetchUserResult = nil
        saveUserResult = .success(())
    }
}

enum TestError: Error {
    case noMockResult
}

Spy Pattern

Spy模式

swift
class NetworkManagerSpy: NetworkManagerProtocol {
    private(set) var messages: [Message] = []

    enum Message: Equatable {
        case fetchUser(id: String)
        case saveUser(User)
    }

    var stubbedFetchUserResult: Result<User, Error> = .failure(TestError.noMockResult)

    func fetchUser(id: String) async throws -> User {
        messages.append(.fetchUser(id: id))
        return try stubbedFetchUserResult.get()
    }

    func saveUser(_ user: User) async throws {
        messages.append(.saveUser(user))
    }
}
swift
class NetworkManagerSpy: NetworkManagerProtocol {
    private(set) var messages: [Message] = []

    enum Message: Equatable {
        case fetchUser(id: String)
        case saveUser(User)
    }

    var stubbedFetchUserResult: Result<User, Error> = .failure(TestError.noMockResult)

    func fetchUser(id: String) async throws -> User {
        messages.append(.fetchUser(id: id))
        return try stubbedFetchUserResult.get()
    }

    func saveUser(_ user: User) async throws {
        messages.append(.saveUser(user))
    }
}

Async Testing Patterns

异步测试模式

Testing async/await Code

测试async/await代码

swift
func test_fetchUser_withValidId_returnsUser() async throws {
    // Arrange
    let expectedUser = User(id: "123", name: "John Doe")
    mockNetworkManager.fetchUserResult = .success(expectedUser)

    // Act
    let result = try await sut.fetchUser(id: "123")

    // Assert
    XCTAssertEqual(result, expectedUser)
}

func test_fetchUser_withNetworkError_throwsError() async {
    // Arrange
    mockNetworkManager.fetchUserResult = .failure(NetworkError.connectionFailed)

    // Act & Assert
    await XCTAssertThrowsError(try await sut.fetchUser(id: "123")) { error in
        XCTAssertEqual(error as? NetworkError, .connectionFailed)
    }
}
swift
func test_fetchUser_withValidId_returnsUser() async throws {
    // 准备
    let expectedUser = User(id: "123", name: "John Doe")
    mockNetworkManager.fetchUserResult = .success(expectedUser)

    // 执行
    let result = try await sut.fetchUser(id: "123")

    // 断言
    XCTAssertEqual(result, expectedUser)
}

func test_fetchUser_withNetworkError_throwsError() async {
    // 准备
    mockNetworkManager.fetchUserResult = .failure(NetworkError.connectionFailed)

    // 执行 & 断言
    await XCTAssertThrowsError(try await sut.fetchUser(id: "123")) { error in
        XCTAssertEqual(error as? NetworkError, .connectionFailed)
    }
}

Testing with Expectations

使用Expectations测试

swift
func test_notificationObserver_receivesNotification() {
    // Arrange
    let expectation = XCTestExpectation(description: "Notification received")
    let notificationName = Notification.Name("TestNotification")

    let observer = NotificationCenter.default.addObserver(
        forName: notificationName,
        object: nil,
        queue: nil
    ) { _ in
        expectation.fulfill()
    }

    // Act
    NotificationCenter.default.post(name: notificationName, object: nil)

    // Assert
    wait(for: [expectation], timeout: 1.0)

    // Cleanup
    NotificationCenter.default.removeObserver(observer)
}

func test_delegateCallback_isCalledOnSuccess() {
    // Arrange
    let expectation = XCTestExpectation(description: "Delegate called")
    let mockDelegate = MockDelegate()
    mockDelegate.onSuccessCalled = { expectation.fulfill() }
    sut.delegate = mockDelegate

    // Act
    sut.performOperation()

    // Assert
    wait(for: [expectation], timeout: 2.0)
    XCTAssertTrue(mockDelegate.successCallCount == 1)
}
swift
func test_notificationObserver_receivesNotification() {
    // 准备
    let expectation = XCTestExpectation(description: "Notification received")
    let notificationName = Notification.Name("TestNotification")

    let observer = NotificationCenter.default.addObserver(
        forName: notificationName,
        object: nil,
        queue: nil
    ) { _ in
        expectation.fulfill()
    }

    // 执行
    NotificationCenter.default.post(name: notificationName, object: nil)

    // 断言
    wait(for: [expectation], timeout: 1.0)

    // 清理
    NotificationCenter.default.removeObserver(observer)
}

func test_delegateCallback_isCalledOnSuccess() {
    // 准备
    let expectation = XCTestExpectation(description: "Delegate called")
    let mockDelegate = MockDelegate()
    mockDelegate.onSuccessCalled = { expectation.fulfill() }
    sut.delegate = mockDelegate

    // 执行
    sut.performOperation()

    // 断言
    wait(for: [expectation], timeout: 2.0)
    XCTAssertTrue(mockDelegate.successCallCount == 1)
}

Testing Combine Publishers

测试Combine发布者

swift
import Combine

func test_userPublisher_emitsUser() {
    // Arrange
    var receivedUser: User?
    var receivedError: Error?
    let expectation = XCTestExpectation(description: "Publisher emits")

    let cancellable = sut.userPublisher
        .sink(
            receiveCompletion: { completion in
                if case .failure(let error) = completion {
                    receivedError = error
                }
                expectation.fulfill()
            },
            receiveValue: { user in
                receivedUser = user
            }
        )

    // Act
    sut.loadUser(id: "123")

    // Assert
    wait(for: [expectation], timeout: 2.0)
    XCTAssertNotNil(receivedUser)
    XCTAssertNil(receivedError)
    cancellable.cancel()
}
swift
import Combine

func test_userPublisher_emitsUser() {
    // 准备
    var receivedUser: User?
    var receivedError: Error?
    let expectation = XCTestExpectation(description: "Publisher emits")

    let cancellable = sut.userPublisher
        .sink(
            receiveCompletion: { completion in
                if case .failure(let error) = completion {
                    receivedError = error
                }
                expectation.fulfill()
            },
            receiveValue: { user in
                receivedUser = user
            }
        )

    // 执行
    sut.loadUser(id: "123")

    // 断言
    wait(for: [expectation], timeout: 2.0)
    XCTAssertNotNil(receivedUser)
    XCTAssertNil(receivedError)
    cancellable.cancel()
}

View Controller Testing

视图控制器测试

swift
class LoginViewControllerTests: XCTestCase {
    var sut: LoginViewController!
    var mockAuthService: MockAuthService!

    override func setUpWithError() throws {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        sut = storyboard.instantiateViewController(
            withIdentifier: "LoginViewController"
        ) as? LoginViewController

        mockAuthService = MockAuthService()
        sut.authService = mockAuthService

        // Load view hierarchy
        sut.loadViewIfNeeded()
    }

    override func tearDownWithError() throws {
        sut = nil
        mockAuthService = nil
    }

    func test_outlets_areConnected() {
        XCTAssertNotNil(sut.emailTextField)
        XCTAssertNotNil(sut.passwordTextField)
        XCTAssertNotNil(sut.loginButton)
        XCTAssertNotNil(sut.errorLabel)
    }

    func test_loginButton_tap_callsAuthService() {
        // Arrange
        sut.emailTextField.text = "test@example.com"
        sut.passwordTextField.text = "password123"

        // Act
        sut.loginButton.sendActions(for: .touchUpInside)

        // Assert
        XCTAssertEqual(mockAuthService.loginCallCount, 1)
        XCTAssertEqual(mockAuthService.lastLoginEmail, "test@example.com")
        XCTAssertEqual(mockAuthService.lastLoginPassword, "password123")
    }

    func test_loginButton_withEmptyEmail_showsError() {
        // Arrange
        sut.emailTextField.text = ""
        sut.passwordTextField.text = "password"

        // Act
        sut.loginButton.sendActions(for: .touchUpInside)

        // Assert
        XCTAssertEqual(mockAuthService.loginCallCount, 0)
        XCTAssertFalse(sut.errorLabel.isHidden)
        XCTAssertEqual(sut.errorLabel.text, "Email is required")
    }

    func test_successfulLogin_navigatesToHome() {
        // Arrange
        mockAuthService.loginResult = .success(User(id: "1", name: "Test"))
        let mockNavigator = MockNavigator()
        sut.navigator = mockNavigator

        sut.emailTextField.text = "test@example.com"
        sut.passwordTextField.text = "password"

        // Act
        sut.loginButton.sendActions(for: .touchUpInside)

        // Assert
        XCTAssertTrue(mockNavigator.didNavigateToHome)
    }
}
swift
class LoginViewControllerTests: XCTestCase {
    var sut: LoginViewController!
    var mockAuthService: MockAuthService!

    override func setUpWithError() throws {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        sut = storyboard.instantiateViewController(
            withIdentifier: "LoginViewController"
        ) as? LoginViewController

        mockAuthService = MockAuthService()
        sut.authService = mockAuthService

        // 加载视图层级
        sut.loadViewIfNeeded()
    }

    override func tearDownWithError() throws {
        sut = nil
        mockAuthService = nil
    }

    func test_outlets_areConnected() {
        XCTAssertNotNil(sut.emailTextField)
        XCTAssertNotNil(sut.passwordTextField)
        XCTAssertNotNil(sut.loginButton)
        XCTAssertNotNil(sut.errorLabel)
    }

    func test_loginButton_tap_callsAuthService() {
        // 准备
        sut.emailTextField.text = "test@example.com"
        sut.passwordTextField.text = "password123"

        // 执行
        sut.loginButton.sendActions(for: .touchUpInside)

        // 断言
        XCTAssertEqual(mockAuthService.loginCallCount, 1)
        XCTAssertEqual(mockAuthService.lastLoginEmail, "test@example.com")
        XCTAssertEqual(mockAuthService.lastLoginPassword, "password123")
    }

    func test_loginButton_withEmptyEmail_showsError() {
        // 准备
        sut.emailTextField.text = ""
        sut.passwordTextField.text = "password"

        // 执行
        sut.loginButton.sendActions(for: .touchUpInside)

        // 断言
        XCTAssertEqual(mockAuthService.loginCallCount, 0)
        XCTAssertFalse(sut.errorLabel.isHidden)
        XCTAssertEqual(sut.errorLabel.text, "Email is required")
    }

    func test_successfulLogin_navigatesToHome() {
        // 准备
        mockAuthService.loginResult = .success(User(id: "1", name: "Test"))
        let mockNavigator = MockNavigator()
        sut.navigator = mockNavigator

        sut.emailTextField.text = "test@example.com"
        sut.passwordTextField.text = "password"

        // 执行
        sut.loginButton.sendActions(for: .touchUpInside)

        // 断言
        XCTAssertTrue(mockNavigator.didNavigateToHome)
    }
}

Performance Testing

性能测试

swift
func test_dataProcessing_performance() {
    let largeDataSet = generateLargeDataSet(count: 10000)

    measure {
        _ = sut.processData(largeDataSet)
    }
}

func test_dataProcessing_performanceWithOptions() {
    let options = XCTMeasureOptions()
    options.iterationCount = 10

    measure(options: options) {
        _ = sut.processData(generateLargeDataSet(count: 5000))
    }
}

func test_memoryUsage_withLargeDataSet() {
    let options = XCTMeasureOptions()
    options.iterationCount = 5

    measure(metrics: [XCTMemoryMetric()], options: options) {
        autoreleasepool {
            let data = sut.loadLargeDataSet()
            sut.processData(data)
        }
    }
}

func test_cpuUsage_duringOperation() {
    measure(metrics: [XCTCPUMetric()]) {
        sut.performCPUIntensiveOperation()
    }
}
swift
func test_dataProcessing_performance() {
    let largeDataSet = generateLargeDataSet(count: 10000)

    measure {
        _ = sut.processData(largeDataSet)
    }
}

func test_dataProcessing_performanceWithOptions() {
    let options = XCTMeasureOptions()
    options.iterationCount = 10

    measure(options: options) {
        _ = sut.processData(generateLargeDataSet(count: 5000))
    }
}

func test_memoryUsage_withLargeDataSet() {
    let options = XCTMeasureOptions()
    options.iterationCount = 5

    measure(metrics: [XCTMemoryMetric()], options: options) {
        autoreleasepool {
            let data = sut.loadLargeDataSet()
            sut.processData(data)
        }
    }
}

func test_cpuUsage_duringOperation() {
    measure(metrics: [XCTCPUMetric()]) {
        sut.performCPUIntensiveOperation()
    }
}

Parameterized Testing

参数化测试

swift
func test_emailValidation_withVariousInputs() {
    let testCases: [(email: String, isValid: Bool)] = [
        ("valid@example.com", true),
        ("user.name@domain.co.uk", true),
        ("invalid.email", false),
        ("", false),
        ("@example.com", false),
        ("test@", false),
        ("test@.com", false),
        ("test@domain", false)
    ]

    for testCase in testCases {
        let result = sut.isValidEmail(testCase.email)
        XCTAssertEqual(
            result,
            testCase.isValid,
            "Failed for email: '\(testCase.email)' - expected \(testCase.isValid), got \(result)"
        )
    }
}

// Using XCTestCase subclass for cleaner parameterized tests
class EmailValidationTests: XCTestCase {
    struct TestCase {
        let input: String
        let expected: Bool
        let file: StaticString
        let line: UInt

        init(_ input: String, _ expected: Bool,
             file: StaticString = #file, line: UInt = #line) {
            self.input = input
            self.expected = expected
            self.file = file
            self.line = line
        }
    }

    func test_isValidEmail() {
        let testCases = [
            TestCase("test@example.com", true),
            TestCase("invalid", false),
            TestCase("", false)
        ]

        for testCase in testCases {
            let result = EmailValidator.isValid(testCase.input)
            XCTAssertEqual(result, testCase.expected,
                          file: testCase.file, line: testCase.line)
        }
    }
}
swift
func test_emailValidation_withVariousInputs() {
    let testCases: [(email: String, isValid: Bool)] = [
        ("valid@example.com", true),
        ("user.name@domain.co.uk", true),
        ("invalid.email", false),
        ("", false),
        ("@example.com", false),
        ("test@", false),
        ("test@.com", false),
        ("test@domain", false)
    ]

    for testCase in testCases {
        let result = sut.isValidEmail(testCase.email)
        XCTAssertEqual(
            result,
            testCase.isValid,
            "Failed for email: '\(testCase.email)' - expected \(testCase.isValid), got \(result)"
        )
    }
}

// 使用XCTestCase子类实现更清晰的参数化测试
class EmailValidationTests: XCTestCase {
    struct TestCase {
        let input: String
        let expected: Bool
        let file: StaticString
        let line: UInt

        init(_ input: String, _ expected: Bool,
             file: StaticString = #file, line: UInt = #line) {
            self.input = input
            self.expected = expected
            self.file = file
            self.line = line
        }
    }

    func test_isValidEmail() {
        let testCases = [
            TestCase("test@example.com", true),
            TestCase("invalid", false),
            TestCase("", false)
        ]

        for testCase in testCases {
            let result = EmailValidator.isValid(testCase.input)
            XCTAssertEqual(result, testCase.expected,
                          file: testCase.file, line: testCase.line)
        }
    }
}

UI Testing with XCUITest

使用XCUITest进行UI测试

swift
class LoginUITests: XCTestCase {
    var app: XCUIApplication!

    override func setUpWithError() throws {
        continueAfterFailure = false
        app = XCUIApplication()
        app.launchArguments = ["--uitesting"]
        app.launch()
    }

    func test_loginFlow_withValidCredentials_showsHomeScreen() {
        // Navigate to login
        let loginButton = app.buttons["LoginButton"]
        XCTAssertTrue(loginButton.waitForExistence(timeout: 5))

        // Enter credentials
        let emailField = app.textFields["EmailTextField"]
        emailField.tap()
        emailField.typeText("test@example.com")

        let passwordField = app.secureTextFields["PasswordTextField"]
        passwordField.tap()
        passwordField.typeText("password123")

        // Tap login
        loginButton.tap()

        // Verify home screen
        let homeTitle = app.staticTexts["Welcome"]
        XCTAssertTrue(homeTitle.waitForExistence(timeout: 10))
    }

    func test_loginFlow_withInvalidCredentials_showsError() {
        let emailField = app.textFields["EmailTextField"]
        emailField.tap()
        emailField.typeText("wrong@example.com")

        let passwordField = app.secureTextFields["PasswordTextField"]
        passwordField.tap()
        passwordField.typeText("wrongpassword")

        app.buttons["LoginButton"].tap()

        let errorLabel = app.staticTexts["ErrorLabel"]
        XCTAssertTrue(errorLabel.waitForExistence(timeout: 5))
        XCTAssertEqual(errorLabel.label, "Invalid credentials")
    }
}
swift
class LoginUITests: XCTestCase {
    var app: XCUIApplication!

    override func setUpWithError() throws {
        continueAfterFailure = false
        app = XCUIApplication()
        app.launchArguments = ["--uitesting"]
        app.launch()
    }

    func test_loginFlow_withValidCredentials_showsHomeScreen() {
        // 导航到登录页
        let loginButton = app.buttons["LoginButton"]
        XCTAssertTrue(loginButton.waitForExistence(timeout: 5))

        // 输入凭证
        let emailField = app.textFields["EmailTextField"]
        emailField.tap()
        emailField.typeText("test@example.com")

        let passwordField = app.secureTextFields["PasswordTextField"]
        passwordField.tap()
        passwordField.typeText("password123")

        // 点击登录
        loginButton.tap()

        // 验证首页
        let homeTitle = app.staticTexts["Welcome"]
        XCTAssertTrue(homeTitle.waitForExistence(timeout: 10))
    }

    func test_loginFlow_withInvalidCredentials_showsError() {
        let emailField = app.textFields["EmailTextField"]
        emailField.tap()
        emailField.typeText("wrong@example.com")

        let passwordField = app.secureTextFields["PasswordTextField"]
        passwordField.tap()
        passwordField.typeText("wrongpassword")

        app.buttons["LoginButton"].tap()

        let errorLabel = app.staticTexts["ErrorLabel"]
        XCTAssertTrue(errorLabel.waitForExistence(timeout: 5))
        XCTAssertEqual(errorLabel.label, "Invalid credentials")
    }
}

Test Configuration

测试配置

Test Scheme Setup

测试Scheme设置

yaml
test_scheme_configuration:
  unit_tests:
    targets: ["YourAppTests"]
    coverage: true
    parallel: true

  ui_tests:
    targets: ["YourAppUITests"]
    coverage: false
    parallel: false
    launch_arguments: ["--uitesting", "--reset-state"]

  integration_tests:
    targets: ["YourAppIntegrationTests"]
    coverage: true
    parallel: false
yaml
test_scheme_configuration:
  unit_tests:
    targets: ["YourAppTests"]
    coverage: true
    parallel: true

  ui_tests:
    targets: ["YourAppUITests"]
    coverage: false
    parallel: false
    launch_arguments: ["--uitesting", "--reset-state"]

  integration_tests:
    targets: ["YourAppIntegrationTests"]
    coverage: true
    parallel: false

Test Plan Configuration

测试计划配置

json
{
  "configurations" : [
    {
      "name" : "Unit Tests",
      "options" : {
        "targetForVariableExpansion" : { "target" : { "name" : "YourApp" } }
      }
    }
  ],
  "defaultOptions" : {
    "codeCoverage" : true,
    "testTimeoutsEnabled" : true,
    "defaultTestExecutionTimeAllowance" : 60
  },
  "testTargets" : [
    { "target" : { "name" : "YourAppTests" } }
  ],
  "version" : 1
}
json
{
  "configurations" : [
    {
      "name" : "Unit Tests",
      "options" : {
        "targetForVariableExpansion" : { "target" : { "name" : "YourApp" } }
      }
    }
  ],
  "defaultOptions" : {
    "codeCoverage" : true,
    "testTimeoutsEnabled" : true,
    "defaultTestExecutionTimeAllowance" : 60
  },
  "testTargets" : [
    { "target" : { "name" : "YourAppTests" } }
  ],
  "version" : 1
}

Лучшие практики

最佳实践

  1. AAA Pattern — Arrange, Act, Assert для каждого теста
  2. One assertion per test — один логический assert на тест
  3. Descriptive names
    test_methodName_condition_expectedResult
  4. Test isolation — каждый тест независим от других
  5. Mock external dependencies — сеть, БД, системные сервисы
  6. Fast tests — unit tests должны выполняться за миллисекунды
  1. AAA模式 — 每个测试都遵循Arrange、Act、Assert流程
  2. 单一断言原则 — 每个测试只包含一个逻辑断言
  3. 描述性命名 — 采用
    test_方法名_条件_预期结果
    格式
  4. 测试独立性 — 每个测试独立于其他测试运行
  5. 模拟外部依赖 — 对网络、数据库、系统服务等外部依赖进行Mock
  6. 快速测试 — 单元测试应在毫秒级完成