Project Icon

swift-custom-dump

Swift数据结构调试和测试工具集

swift-custom-dump是一个用于调试、比较和测试Swift应用程序数据结构的工具库。它优化了标准dump函数的输出,提高了结构可读性,并引入了diff功能用于值比较。库中的expectNoDifference和expectDifference断言函数有助于编写清晰的单元测试。开发者还可通过自定义协议控制特定类型的dump输出。

自定义转储

CI

用于调试、比较和测试应用程序数据结构的工具集合。

动机

Swift 提供了一个很棒的工具 dump,可以将任何值的内容转储为字符串。它将值的所有字段和子字段打印成树状描述:

struct User {
  var favoriteNumbers: [Int]
  var id: Int
  var name: String
}

let user = User(
  favoriteNumbers: [42, 1729],
  id: 2,
  name: "Blob"
)

dump(user)
▿ User
  ▿ favoriteNumbers: 2 elements
    - 42
    - 1729
  - id: 2
  - name: "Blob"

这非常有用,可以用来构建可视化应用程序运行时值数据的调试工具,但有时它的输出并不理想。

例如,转储字典会导致冗长的输出,可能难以阅读(另外请注意键是无序的):

dump([1: "one", 2: "two", 3: "three"])
▿ 3 key/value pairs
  ▿ (2 elements)
    - key: 2
    - value: "two"
  ▿ (2 elements)
    - key: 3
    - value: "three"
  ▿ (2 elements)
    - key: 1
    - value: "one"

同样,枚举的输出也非常冗长:

dump(Result<Int, Error>.success(42))
▿ Swift.Result<Swift.Int, Swift.Error>.success
  - success: 42

在处理深层嵌套结构时,它变得更难阅读:

dump([1: Result<User, Error>.success(user)])
▿ 1 key/value pair
  ▿ (2 elements)
    - key: 1
    ▿ value: Swift.Result<User, Swift.Error>.success
      ▿ success: User
        ▿ favoriteNumbers: 2 elements
          - 42
          - 1729
        - id: 2
        - name: "Blob"

有时 dump 根本不打印有用的信息,比如从 Objective-C 导入的枚举:

import UserNotifications

dump(UNNotificationSetting.disabled)
- __C.UNNotificationSetting

因此,尽管 dump 函数很方便,但它通常是一个过于粗糙的工具。这就是 customDump 函数的动机。

customDump

customDump 函数模仿 dump 的行为,但提供了更精细的嵌套结构输出,优化了可读性。例如,结构体的转储格式更接近 Swift 中的结构体语法,数组的转储包含每个元素的索引:

import CustomDump

customDump(user)
User(
  favoriteNumbers: [
    [0]: 42,
    [1]: 1729
  ],
  id: 2,
  name: "Blob"
)

字典以更紧凑的格式转储,模仿 Swift 的语法,并自动排序键:

customDump([1: "one", 2: "two", 3: "three"])
[
  1: "one",
  2: "two",
  3: "three"
]

同样,枚举也以更紧凑、可读的格式转储:

customDump(Result<Int, Error>.success(42))
Result.success(42)

深层嵌套结构有一个简化的树状结构:

customDump([1: Result<User, Error>.success(user)])
[
  1: Result.success(
    User(
      favoriteNumbers: [
        [0]: 42,
        [1]: 1729
      ],
      id: 2,
      name: "Blob"
    )
  )
]

diff

使用 customDump 函数的输出,我们可以构建一个非常轻量级的方法来文本比较 Swift 中的任意两个值:

var other = user
other.favoriteNumbers[1] = 91

print(diff(user, other)!)
  User(
    favoriteNumbers: [
      [0]: 42,
-     [1]: 1729
+     [1]: 91
    ],
    id: 2,
    name: "Blob"
  )

此外,当结构的部分没有变化时,会进行额外的工作以最小化差异的大小,例如大集合中的单个元素发生变化:

let users = (1...5).map {
  User(
    favoriteNumbers: [$0],
    id: $0,
    name: "Blob \($0)"
  )
}

var other = users
other.append(
  .init(
    favoriteNumbers: [42, 1729],
    id: 100,
    name: "Blob Sr."
  )
)

print(diff(users, other)!)
  [
    … (4 unchanged),
+   [4]: User(
+     favoriteNumbers: [
+       [0]: 42,
+       [1]: 1729
+     ],
+     id: 100,
+     name: "Blob Sr."
+   )
  ]

对于一个真实的用例,我们修改了 Apple 的 Landmarks 教程应用程序,以打印收藏地标时的前后状态:

  [
    [0]: Landmark(
      id: 1001,
      name: "Turtle Rock",
      park: "Joshua Tree National Park",
      state: "California",
      description: "This very large formation lies south of the large Real Hidden Valley parking lot and immediately adjacent to (south of) the picnic areas.",
-     isFavorite: true,
+     isFavorite: false,
      isFeatured: true,
      category: Category.rivers,
      imageName: "turtlerock",
      coordinates: Coordinates(…)
    ),
    … (11 unchanged)
  ]

expectNoDifference

XCTest 的 XCTAssertEqual 和 Swift Testing 的 #expect(_ == _) 都允许你断言两个值相等,如果不相等,测试套件将失败并显示消息:

var other = user
other.name += "!"
XCTAssertEqual(user, other)
#expect(user == other)
XCTAssertEqual失败:"User(favoriteNumbers: [42, 1729], id: 2, name: "Blob")"不等于"User(favoriteNumbers: [42, 1729], id: 2, name: "Blob!")"
期望失败:(user → User(favoriteNumbers: [42, 1729], id: 2, name: "Blob")) == (other → User(favoriteNumbers: [42, 1729], id: 2, name: "Blob!"))

不幸的是,这些失败信息很难直观地解析和理解。需要花几秒钟在信息中寻找才能发现唯一的区别是名称末尾的感叹号。如果类型更复杂,包含嵌套结构和大型集合,问题会变得更糟。

该库还附带了一个expectNoDifference函数来缓解这些问题。它的工作方式类似于XCTAssertEqual#expect(_ == _),只是失败信息使用格式良好的差异来准确显示两个值之间的不同:

expectNoDifference(user, other)
expectNoDifference失败:…

  User(
    favoriteNumbers: [...],
    id: 2,
-   name: "Blob"
+   name: "Blob!"
  )

(第一个:-,第二个:+)

expectDifference

该函数提供了expectNoDifference的反向功能:它通过在给定操作之前和之后评估给定表达式,然后比较结果,来断言一个值有一组变化。

例如,给定一个非常简单的计数器结构,我们可以针对其递增功能编写测试:

struct Counter {
  var count = 0
  var isOdd = false
  mutating func increment() {
    self.count += 1
    self.isOdd.toggle()
  }
}

var counter = Counter()
expectDifference(counter) {
  counter.increment()
} changes: {
  $0.count = 1
  $0.isOdd = true
}

如果changes没有详尽描述所有changed字段,断言将失败。

通过省略操作,你可以在changes闭包中只描述你想断言的字段,从而对一个值进行"非详尽"断言:

counter.increment()
expectDifference(counter) {
  $0.count = 1
  // 不需要进一步描述`isOdd`如何变化
}

自定义

Custom Dump提供了几种重要的方式来自定义数据类型的转储方式:CustomDumpStringConvertibleCustomDumpReflectableCustomDumpRepresentable

CustomDumpStringConvertible

CustomDumpStringConvertible协议提供了一种简单的方法,将类型转换为原始字符串以进行转储。它最适合具有简单、非嵌套内部表示的类型,其输出通常适合单行显示,例如日期、UUID、URL等:

extension URL: CustomDumpStringConvertible {
  public var customDumpDescription: String {
    "URL(\(self.absoluteString))"
  }
}

customDump(URL(string: "https://www.pointfree.co/")!)
URL(https://www.pointfree.co/)

Custom Dump还在内部使用此协议为从Objective-C导入的枚举提供更有用的输出:

import UserNotifications

print("dump:")
dump(UNNotificationSetting.disabled)
print("customDump:")
customDump(UNNotificationSetting.disabled)
dump:
- __C.UNNotificationSetting
customDump:
UNNotificationSettings.disabled

遇到打印不美观的Objective-C枚举?请参阅本README的贡献部分以帮助提交修复。

CustomDumpReflectable

CustomDumpReflectable协议提供了一种更全面的方法来将类型转储为更结构化的输出。它允许你构造一个自定义镜像,描述应该转储的结构。你可以省略、添加和替换字段,甚至更改结构转储的"显示样式"。

例如,假设你有一个表示状态的结构,它在内存中保存了一个不应该写入日志的安全令牌。你可以通过提供一个省略此字段的镜像来从customDump中省略令牌:

struct LoginState: CustomDumpReflectable {
  var username: String
  var token: String

  var customDumpMirror: Mirror {
    .init(
      self,
      children: [
        "username": self.username,
        // 从日志中省略令牌
      ],
      displayStyle: .struct
    )
  }
}

customDump(
  LoginState(
    username: "blob",
    token: "secret"
  )
)
LoginState(username: "blob")

就这样,转储中不会写入令牌数据。

CustomDumpRepresentable

CustomDumpRepresentable协议允许你返回_任何_值用于转储。这对于扁平化包装器类型的转储表示很有用。例如,类型安全标识符可能希望直接转储其原始值:

struct ID: RawRepresentable {
  var rawValue: String
}

extension ID: CustomDumpRepresentable {
  var customDumpValue: Any {
    self.rawValue
  }
}

customDump(ID(rawValue: "deadbeef")
"deadbeef"

贡献

Apple生态系统中有许多类型无法转储为格式良好的字符串。特别是,从Objective-C导入的所有枚举都会转储为不太有用的字符串:

import UserNotifications

dump(UNNotificationSetting.disabled)
- __C.UNNotificationSetting

因此,我们已经让Apple的许多类型符合CustomDumpStringConvertible协议,以便它们能打印出更合理的描述。如果你遇到不打印有用信息的类型,我们很乐意接受PR让这些类型符合CustomDumpStringConvertible

安装

你可以通过将Custom Dump添加为包依赖项来将其添加到Xcode项目中。

https://github.com/pointfreeco/swift-custom-dump

如果你想在SwiftPM项目中使用Custom Dump,只需将其添加到Package.swift中的dependencies子句即可:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.0.0")
]

文档

Custom Dump API的最新文档可在此处获得。

其他库

许可证

该库根据MIT许可证发布。有关详细信息,请参阅LICENSE

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号