Token导航 LogoToken导航

Agent Skill · 开发

ios-unit-test(iOS unit 测试)

用于辅助测试设计、自动化测试、用例整理和回归验证。它适合让 Agent 编写单元测试、端到端测试、测试计划或根据失败日志定位问题。使用时需要确认项目测试框架、运行命令和夹具数据,避免为了通过测试而改坏真实逻辑;涉及浏览器或外部服务时,应区分本地模拟、测试环境和生产环境。

ios-unit-test 是开发类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在开发类的 21,746 个 Skill 里排第 10,811。

总安装 2,070周安装 88GitHub Stars 4下载 725核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:ios-unit-test(iOS unit 测试)
来源仓库:https://github.com/dengineproblem/agents-monorepo
仓库路径:skills/ios-unit-test
安装命令:
npx skills add https://github.com/dengineproblem/agents-monorepo --skill ios-unit-test
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/dengineproblem/agents-monorepo --skill ios-unit-test

这个 Skill 是做什么的

开发类 Skill 面向写代码、改代码和排查问题,一般会读写仓库文件,部分还会执行构建或测试命令。

该技能用于辅助测试设计、自动化测试和回归验证。ios-unit-test 属于开发类 Skill,可作为该场景下的辅助能力补充。适合编写单元测试、端到端测试或根据日志定位问题。使用时需确认项目测试框架、运行命令和夹具数据。应避免为了通过测试而破坏真实逻辑,区分模拟与生产环境。

涉及浏览器或外部服务时需明确本地测试与正式环境的差异。

来源仓库内路径:skills/ios-unit-test

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/dengineproblem/agents-monorepo --skill ios-uni…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

ios-unit-test 怎么安装?

在终端执行 npx skills add https://github.com/dengineproblem/agents-monorepo --skill ios-unit-test。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

ios-unit-test 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

ios-unit-test 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

ios-unit-test 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

ios-unit-test 用的人多吗?

公开数据是累计安装 2,070 次,近一周安装 88 次,下载 725 次,GitHub 4 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

ios-unit-test 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理开发类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

开发类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
skill-vetter

技能审查员 · OpenClaw

8,779,018986操作浏览器MIT-0通过
ontology

本体论 · OpenClaw

6,636,960576只读MIT-0通过
entra-app-registration

内部应用程序注册 · Codex、Claude

6,631,872764接触敏感数据未明确通过
entra-app-registration

内部应用程序注册 · Codex、Claude

3,757,680201接触敏感数据未明确通过
nano-banana-pro

纳米香蕉专业版 · OpenClaw

3,526,898364接触敏感数据MIT-0有提醒
ios-unit-test

iOS unit 测试 · 本页

2,0704需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

iOS Unit Testing Expert

Expert in iOS testing with XCTest framework and best practices.

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

XCTest Framework Fundamentals

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)
        }
    }
}

Mocking and Dependency Injection

Protocol-Based Mocking

// 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
}

Spy Pattern

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

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)
    }
}

Testing with Expectations

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)
}

Testing Combine Publishers

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()
}

View Controller Testing

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)
    }
}

Performance Testing

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

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)
        }
    }
}

UI Testing with XCUITest

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")
    }
}

Test Configuration

Test Scheme Setup

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

{
  "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 должны выполняться за миллисекунды

平台分布

Codex

40.94%
按下载量换算297

Claude

28.92%
按下载量换算210

Cursor

18.42%
按下载量换算134

Gemini CLI

9.26%
按下载量换算67

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills