精选文章

27. 测试实践

2018-09-30 · 测试

27. 测试实践

测试的目标是“业务逻辑可验证、回归成本可控”。核心是:可测试的结构 + 稳定的用例。

一、XCTest 基础

import XCTest
@testable import App

final class MathTests: XCTestCase {
    func testAdd() {
        XCTAssertEqual(1 + 1, 2)
    }
}

二、SUT 结构

final class LoginViewModelTests: XCTestCase {
    private var sut: LoginViewModel!
    private var service: MockLoginService!

    override func setUp() {
        super.setUp()
        service = MockLoginService()
        sut = LoginViewModel(service: service)
    }

    override func tearDown() {
        sut = nil
        service = nil
        super.tearDown()
    }

    func testLoginSuccess() {
        service.result = .success(true)
        sut.login(username: "jj", password: "123")
        XCTAssertTrue(service.called)
    }
}

三、Mock 与协议隔离

protocol LoginService {
    func login(username: String, password: String, completion: @escaping (Result<Bool, Error>) -> Void)
}

final class MockLoginService: LoginService {
    var result: Result<Bool, Error> = .success(true)
    var called = false

    func login(username: String, password: String, completion: @escaping (Result<Bool, Error>) -> Void) {
        called = true
        completion(result)
    }
}

四、异步测试

func testAsyncLogin() {
    let exp = expectation(description: "login")
    service.result = .success(true)

    sut.onLogin = { success in
        XCTAssertTrue(success)
        exp.fulfill()
    }

    sut.login(username: "jj", password: "123")
    waitForExpectations(timeout: 1)
}

五、网络层测试(URLProtocol)

final class MockURLProtocol: URLProtocol {
    static var handler: ((URLRequest) -> (HTTPURLResponse, Data))?

    override class func canInit(with request: URLRequest) -> Bool { true }
    override class func canonicalRequest(for request: URLRequest) -> URLRequest { request }

    override func startLoading() {
        guard let handler = MockURLProtocol.handler else { return }
        let (response, data) = handler(request)
        client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
        client?.urlProtocol(self, didLoad: data)
        client?.urlProtocolDidFinishLoading(self)
    }

    override func stopLoading() {}
}

六、UI 测试

1. 基础示例

final class LoginUITests: XCTestCase {
    func testLoginFlow() {
        let app = XCUIApplication()
        app.launch()

        app.textFields["username"].tap()
        app.textFields["username"].typeText("jj")
        app.secureTextFields["password"].tap()
        app.secureTextFields["password"].typeText("123")
        app.buttons["login"].tap()

        XCTAssertTrue(app.staticTexts["welcome"].exists)
    }
}

2. 可测性标识

usernameTextField.accessibilityIdentifier = "username"
passwordTextField.accessibilityIdentifier = "password"
loginButton.accessibilityIdentifier = "login"

七、测试策略

  • 业务逻辑优先写单元测试
  • UI 测试覆盖关键路径
  • Mock 外部依赖,避免真实网络

测试不是把覆盖率做高,而是保证核心流程不会回归。

JJ

作者简介

专注于内容创作、产品策略与设计实践。欢迎交流合作。

上一篇 下一篇