1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "MyLib",
platforms: [.macOS(.v15), .iOS(.v18)],
products: [
.library(name: "MyLib", targets: ["MyLib"]),
],
targets: [
.target(name: "MyLib"),
.testTarget(name: "MyLibTests", dependencies: ["MyLib"]),
]
)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "MyTool",
platforms: [.macOS(.v15)],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"),
],
targets: [
.executableTarget(
name: "MyTool",
dependencies: [
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
),
.testTarget(name: "MyToolTests", dependencies: ["MyTool"]),
]
)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "App",
targets: [
.target(
name: "Core",
swiftSettings: [
.swiftLanguageMode(.v6),
.enableUpcomingFeature("ExistentialAny"),
.define("DEBUG", .when(configuration: .debug)),
]
),
.target(name: "UI", dependencies: ["Core"]),
.target(name: "Features", dependencies: ["Core"]),
.executableTarget(name: "App", dependencies: ["UI", "Features"]),
]
)
|
⚠️ target 之间必须显式声明依赖。UI 里想 import Core,就得在 dependencies 里写明;写漏了编译器会告诉你 “no such module”。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // swift-tools-version: 6.2
import PackageDescription
let package = Package(
name: "Cross",
dependencies: [
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.0"),
],
targets: [
.target(
name: "Cross",
dependencies: [
.product(
name: "Logging",
package: "swift-log",
condition: .when(platforms: [.linux])
),
]
),
]
)
|
条件依赖让同一份清单在 Apple 平台与 Linux 上用不同实现,是跨平台库的常见手法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| import Testing
@testable import MyLib
@Test func additionWorks() {
#expect(1 + 1 == 2)
}
@Test("解析合法输入")
func parseValid() throws {
let value = try #require(Int("42"))
#expect(value == 42)
}
@Test(arguments: [1, 2, 3])
func positive(n: Int) {
#expect(n > 0)
}
@Suite("分组示例")
struct GroupTests {
@Test func insideGroup() { #expect(true) }
}
|
| 写法 | 作用 |
|---|
@Test | 标记一个测试函数(可以是自由函数,不必继承任何类) |
@Test("说明") | 给测试加显示名 |
@Test(arguments:) | 参数化,一次跑多组数据 🔥 |
@Suite | 给测试分组 |
#expect(...) | 断言,失败时输出表达式两边的值 |
try #require(...) | 断言并解包,失败立刻中止这个测试 |
.tags(...) | 打标签,按标签筛选运行 |
跑起来大致长这样(真的输出还会多几行版本号与耗时):
1
2
3
4
5
| ◇ Test run started.
◇ Suite "分组示例" started.
◇ Test additionWorks() started.
✔ Test additionWorks() passed after 0.001 seconds.
✔ Test run with 4 tests in 1 suite passed after 0.001 seconds.
|
🚧 两个测试框架都随工具链提供:装了完整 Xcode 才有 TestingMacros 插件与 XCTest 框架,只装 Command Line Tools 时 import Testing 会找不到模块。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import XCTest
@testable import MyLib
final class MyLibTests: XCTestCase {
func testAddition() {
XCTAssertEqual(1 + 1, 2)
}
func testThrowing() throws {
let value = try XCTUnwrap(Int("42"))
XCTAssertEqual(value, 42)
}
func testAsync() async throws {
let result = await fetchSomething()
XCTAssertNotNil(result)
}
}
|
| 断言 | 作用 |
|---|
XCTAssertEqual / XCTAssertNotEqual | 相等比较 |
XCTAssertTrue / XCTAssertFalse | 真假 |
XCTAssertNil / XCTAssertNotNil | 可选值 |
XCTAssertThrowsError | 期望抛错 |
XCTUnwrap | 解包并断言非空 |
XCTSkip | 主动跳过 |
🚧 UI 测试与性能测试目前仍是 XCTest 的地盘;纯逻辑测试建议迁到 Swift Testing。