前言

不久前,我正在工作中开发一项新服务,该服务由 Swift Package 组成,该 Package 公开了一个类似于Decodable协议,供我们应用程序的其余部分使用。事实上,该协议是从Decodable本身继承下来的,看起来像这样:

Fetchable.swit

protocol Fetchable: Decodable, Equatable {}

新的 package 将采用符合Fetchable的类型来尝试从远程或缓存的JSON数据块中解码它们。

由于这项服务对应用程序的正确运行至关重要,作为这项工作的一部分,我们希望确保始终存在故障安全( fail-safe)。因此,我们让该应用程序附带了一个备用的JSON文件,如果远程和缓存的数据解码失败,将使用该文件,来保证程序的正常运行。

无论如何,我们需要符合Fetchable的新类型从备用数据中正确解码。然而,有一个问题,有时很难发现备用JSON文件或模型本身是否有任何错误,因为解码错误会在运行时发生,并且只有在访问某些屏幕/功能时才会发生。

为了让我们对我们要发送的代码更有信心,我们添加了一些单元测试,试图根据我们附带的备用JSON解码符合Fetchable协议的每个模型。这些将使我们在CI上有一个早期指示,表明备用数据或模型中存在错误,如果所有测试都通过,我们将确定,一旦我们发布新服务,它始终具有故障安全功能

我们手动编写了这些测试,但我们很快就意识到这个解决方案是不可扩展的,因为随着越来越多的符合Fetchable协议的类型被添加,我们引入了大量的代码复制,并可能有人最终忘记为特定功能编写这些测试。

我们考虑过自动化该过程,但由于我们的代码库的性质,我们遇到了一些问题,代码库高度模块化,混合了Xcode项目和Swift Package。一些架构决策还意味着我们必须收集大量符号信息,才能获得生成测试的正确类型。

是什么让我再次关注到它?

在我忘记了这件事一段时间后,Xcode 14的公告允许在Xcode项目中使用 Swift Package 插件,以及一些架构更改使提取类型信息变得容易得多,这让我有动力再次开始研究这个问题。

请注意,Xcode项目的构建工具插件尚未按照发布说明在Xcode 14 Beta 2中提供,但将在Xcode 14的未来版本中提供。

在过去的几周里,我一直在研究如何使用软件包插件生成单元测试,在这篇文章中,我将解释我在向哪个方向尝试以及它涉及了什么。

实施细节

我开始了一项任务,即创建一个构建工具插件,与 Xcode 14 引入的命令插件不同,该插件可以任意运行并依赖用户输入,作为Swift软件包构建过程的一部分运行。

我知道我需要创建一个可执行文件,因为 Build Tool 插件依赖这些来执行操作。这个脚本将完全用 Swift 编写,因为这是我最熟悉的语言,并承担以下职责:

  1. 扫描目标目录并提取所有.swift文件。目标将被递归扫描,以确保不会错过子目录。
  2. 使用sourcekit,或者更具体地说,SourceKitten,扫描这些.swift文件并收集类型信息。这将允许提取符合Fetchable协议的所有类型,以便可以针对它们编写测试。
  3. 获得这些类型后,生成一个带有XCTestCase.swift文件,其中包含每种类型的单元测试。

让我们写一些代码吧

与所有 Swift Package 一样,最简单的入门方法是在命令行上运行swift package init

这创建了两个目标,一个是包含Fetchable协议定义和符合该定义的类型的实现代码,另一个是应用插件为此类类型生成单元测试的测试目标。

Package.swit

// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.import PackageDescriptionlet package = Package(name: "CodeGenSample",platforms: [.macOS(.v10_11)],products: [.library(name: "CodeGenSample",targets: ["CodeGenSample"]),],dependencies: [],targets: [.target(name: "CodeGenSample",dependencies: []),.testTarget(name: "CodeGenSampleTests",dependencies: ["CodeGenSample"])]
)

编写可执行文件

如前所述,所有构建工具插件都需要可执行文件来执行所有必要的操作。

为了帮助开发此命令行,将使用几个依赖项。第一个是SourceKitten——特别是其SourceKitten框架库,这是一个Swift包装器,用于帮助使用Swift代码编写sourcekit请求,第二个是快速参数解析器,这是苹果提供的软件包,可以轻松创建命令行工具,并以更快、更安全的方式解析在执行过程中传递的命令行参数。

在创建executableTarget并赋予它两个依赖项后,Package.swift就是这个样子:

Package.swift

// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.import PackageDescriptionlet package = Package(name: "CodeGenSample",platforms: [.macOS(.v10_11)],products: [.library(name: "CodeGenSample",targets: ["CodeGenSample"]),],dependencies: [.package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"),.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")],targets: [.target(name: "CodeGenSample",dependencies: []),.testTarget(name: "CodeGenSampleTests",dependencies: ["CodeGenSample"]),.executableTarget(name: "PluginExecutable",dependencies: [.product(name: "SourceKittenFramework", package: "SourceKitten"),.product(name: "ArgumentParser", package: "swift-argument-parser")])]
)

可执行目标需要一个入口点,因此,在PluginExecutable目标的源目录下,必须创建一个名为PluginExecutable.swift的文件,其中所有可执行逻辑都需要创建。

请注意,这个文件可以随心所欲地命名,我倾向于以与我在Package.swift中创建的目标相同的方式命名它。

如下所示的脚本导入必要的依赖项,并创建可执行文件的入口点(必须用@main装饰),并声明在执行时传递的4个输入。

所有逻辑和方法调用都存在于run函数中,该函数是调用可执行文件时运行的方法。这是ArgumentParser语法的一部分,如果您想了解更多信息,Andy Ibañez有一篇关于该主题的精彩文章,可能非常有帮助。

PluginExecutable.swift

import SourceKittenFramework
import ArgumentParser
import Foundation@main
struct PluginExecutable: ParsableCommand {@Argument(help: "The protocol name to match")var protocolName: String@Argument(help: "The module's name")var moduleName: String@Option(help: "Directory containing the swift files")var input: String@Option(help: "The path where the generated files will be created")var output: Stringfunc run() throws {// 1let files = try deepSearch(URL(fileURLWithPath: input, isDirectory: true))// 2setenv("IN_PROCESS_SOURCEKIT", "YES", 1)let structures = try files.map { try Structure(file: File(path: $0.path)!) }// 3var matchedTypes = [String]()structures.forEach { walkTree(dictionary: $0.dictionary, acc: &matchedTypes) }// 4try createOutputFile(withContent: matchedTypes)}// ...
}

现在让我们专注于上面的run方法,以了解当插件运行可执行文件时会发生什么:

  1. 首先,扫描目标目录以找到其中的所有.swift文件。这是递归完成的,这样子目录就不会错过。此目录的路径作为参数传递给可执行文件。
  2. 对于上次调用中找到的每个文件,通过SourceKitten发出Structure请求,以查找文件中Swift代码的类型信息。请注意,环境变量(IN_PROCESS_SOURCEKIT)也被设置为true。这需要确保选择源套件的进程中版本,以便它能够遵守插件的沙盒规则。

Xcode附带两个版本的sourcekit可执行文件,一个版本解析进程中的文件,另一个使用XPC向解析进程外文件的守护进程发送请求。后者是mac上的默认版本,为了能够将sourcekit用作插件进程的一部分,必须选择进程中版本。这最近在SourceKitten上作为环境变量实现,是运行引擎盖下使用sourcekit的其他可执行文件的关键,例如SwiftLint

  1. 浏览上次调用的所有响应,并扫描类型信息以提取符合Fetchable协议的任何类型。

  2. 在传递给可执行文件的output参数指定的位置创建一个输出文件,其中包含每种类型的单元测试。

请注意,上面没有重点介绍每个调用的具体细节,但如果你对实现感兴趣,包含所有代码的repo现在已经在Github上公开了!

创建该插件

与可执行文件一样,必须向Package.swift添加.plugin目标,并且必须创建包含插件实现的.swift文件(Plugins/SourceKitPlugin/SourceKitPlugin.swift)。

Package.swift

// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.import PackageDescriptionlet package = Package(name: "CodeGenSample",platforms: [.macOS(.v10_11)],products: [.library(name: "CodeGenSample",targets: ["CodeGenSample"]),],dependencies: [.package(url: "https://github.com/jpsim/SourceKitten.git", exact: "0.32.0"),.package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0")],targets: [.target(name: "CodeGenSample",dependencies: []),.testTarget(name: "CodeGenSampleTests",dependencies: [“CodeGenSample"],
plugins: [“SourceKitPlugin”],),.executableTarget(name: "PluginExecutable",dependencies: [.product(name: "SourceKittenFramework", package: "SourceKitten"),.product(name: "ArgumentParser", package: "swift-argument-parser")]),.plugin(name: "SourceKitPlugin",capability: .buildTool(),dependencies: [.target(name: "PluginExecutable")])]
)

以下代码显示了插件的初始实现,其struct符合BuildToolPlugin的协议。这需要实现一个返回具有单个构建命令的数组的createBuildCommands方法。

此插件使用buildCommand而不是preBuildCommand,因为它需要作为构建过程的一部分运行,而不是在它之前运行,因此它有机会构建和使用它所依赖的可执行文件。在这种情况下,支持使用buildCommand的另一点是,它只会在输入文件更改时运行,而不是每次构建目标时运行。

此命令必须为要运行的可执行文件提供名称和路径,这可以在插件的上下文中找到:

SourceKitPlugin.swift

import PackagePlugin@main
struct SourceKitPlugin: BuildToolPlugin {func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {return [.buildCommand(displayName: "Protocol Extraction!",executable: try context.tool(named: "PluginExecutable").path,arguments: ["FindThis",												

使用 Swift Package 插件生成代码相关推荐

  1. mybaties插件生成代码

    指定插件运行什么xml,关于如何用idea创建一个maven项目,可以看我以前写的博客 <?xml version="1.0" encoding="UTF-8&qu ...

  2. 使用 MyBatis 的 Maven 插件生成代码

    我们无需手动编写 实体类.DAO.XML 配置文件,只需要使用 MyBatis 提供的一个 Maven 插件就可以自动生成所需的各种文件便能够满足基本的业务需求,如果业务比较复杂只需要修改相关文件即可 ...

  3. spring boot中利用mybatis-generator插件生成代码

    使用Idea在spring boot中集成mybatis-generator,自动生成mapper.xml  model  dao 文件 一.配置 pom.xml 在pom.xml的<plugi ...

  4. 【MyBatis框架】mybatis逆向工程自动生成代码

    逆向工程 1.什么是逆向工程 mybaits需要程序员自己编写sql语句,mybatis官方提供逆向工程 可以针对单表自动生成mybatis执行所需要的代码(mapper.java,mapper.xm ...

  5. idea package自动生成_懒人必备,IntelliJ IDEA中代码一键生成

    之前有不少小伙伴问松哥微人事项目(https://github.com/lenve/vhr)使用的 MyBatis 逆向工程在哪里?其实旧版微人事当时没有使用逆向工程,是我自己手动敲出来的,当然手动敲 ...

  6. SpringBoot入门篇--整合mybatis+generator自动生成代码+druid连接池+PageHelper分页插件

    我们这一一篇博客讲的是如何整合Springboot和Mybatis框架,然后使用generator自动生成mapper,pojo等文件.然后再使用阿里巴巴提供的开源连接池druid,这个连接池的好处我 ...

  7. mybatis-generator-maven-plugin插件自动生成代码的配置方法

    1. 第一步,在pom文件中引入如下插件 <plugin><groupId>org.mybatis.generator</groupId><artifactI ...

  8. idea package自动生成_Idea 自动生成Junit单元测试插件JunitGenerator

    JunitGenerator Idea中提供了可以自动生成Junit单元测试的插件,JunitGenerator.本篇文章将介绍如何在idea中安装.配置及使用JunitGenerator,以方便大家 ...

  9. 用Maven插件生成Mybatis代码/数据库

    现在代码管理基本上是采用Maven管理,Maven的好处此处不多说,大家用百度搜索会有很多介绍,本文介绍一下用Maven工具如何生成Mybatis的代码及映射的文件. 一.配置Maven pom.xm ...

最新文章

  1. String,StringBuffer
  2. Forerunner:首个面向“多未来”的推测执行技术
  3. Nginx 介绍配置
  4. 计算机组成asr实验,计算机组成与结构实验讲义.doc
  5. mysql中如何去除重复数据_MySQL中如何删除重复数据只保留一条
  6. 制造业物料清单BOM、智能文档阅读、科学文献影响因子、Celebrated Italian mathematician ZepartzatT Gozinto 与 高津托图...
  7. codeforces George and Job
  8. Redis基础笔记 (二)
  9. Go 判断元素是否在切片中
  10. 深度学习:基本概要:监督,无监督,半监督,弱监督,多示例,迁移学习
  11. 【天光学术】社会语言学论文:委婉语合作原则违反的具体体现与影响(节选)
  12. 【Freeswitch从入门到精通】二、初识Freeswitch
  13. 让女人无法抗拒的30句表白【实用】
  14. 101. Domino 10 就要来了
  15. 共享新风机未来家居生活必备品新鲜空气齐分享
  16. Intel(R)Dual Band Wireless-AC 3165网卡驱动程序出现问题,WiFi,热点和以太网无法连接
  17. 系统迁移必知会(多年总结)
  18. 通过jsp向mysql批量导入数据_对大数据的批量导入MySQL数据库
  19. Unity 相机参数详解:多相机混合、小地图实现
  20. JQuery、Ajax基础语法

热门文章

  1. 2016年计算机本科考试试题,(2016年电大)电大计算机考试本科试题.doc
  2. 建筑力学与结构【7】
  3. html5画虚线,html5 实现画虚线
  4. LR性能测试框架学习总结
  5. AD/DA相关基础知识
  6. 2022安徽安庆市桐城市龙眠街道网格员招考仿真试题及答案
  7. html代码字号div style=,div字体大小设置 css设置div中字体大小尺寸样式
  8. Java的基础语法(基础语法大全)
  9. 走楼梯(无需担忧N的数值太大)
  10. Win10系统导入证书私钥