iOS 自动生成参数单元测试实践方案

目标:

根据Objective-C项目中的公开接口,自动生成单元测试用例并测试边界值,以排查边界异常情况。

想要达到目标大概分两个部分:
一个是原生部分的测试用例怎么生成,一个是脚本怎么写

自动生成测试用例实现思路
未命名文件.png
我们先看下原生怎么实现:
原生OC测试模板代码生成测试代码 实现思路:

正常写单元测试是通过继承XCTestCase类,写test开头的方法,这些方法被系统识别为单测方法。
我们需要的是自动生成多个test方法,我们可以在测试类里通过runtime添加test开头的方法实现,然后在系统XCTestCase类的被测方法数组里添加刚刚生成test开头的方法指针,就可以添加自己生成的测试方法。
具体来说:
1.我们需要用runtime在测试类添加test方法实现(class_addMethod),代码位置:buildSelectorWithEachCaseParams
2.把生成的test方法的指针 (SEL包装成对象_QuickSelectorWrapper)放到数组
3.重写系统XCTestCase的testInvocations方法,返回包含刚才生成的test方法数组(把_QuickSelectorWrapper 转成 NSInvocation)

上面只是添加一个test方法所需要做的事,生成多个test方法,我们需要一个gen函数。
系统在调用testInvocations方法时,去扫描自身(实际是子类去写)以‘genMethod’开头的函数并执行它,并把执行结果放到方法数组里。
子类的genMethod根据被测的参数多次创建方法: 调用父类的buildMethods方法构造真正的方法,
构造方法时调用 genEachTestCaseNameWithPrefix 方法按照格式生成函数名

现在我们看下原生阶段下写了什么来生成测试代码:

我们可以继承基类ParametrizedTestCase(XCTestCase的子类,封装了上述的构造测试方法,扫描gen开头方法等等)并写类似下面代码示例的方法(genMethod开头)
OC生成测试方法的代码:

#import "ParametrizedTestCase.h"
#import "UIImage+Format.h"
#import "ParametrizedTestCase+CustomParam.h"
@interface ParamTestUIImage_Format : ParametrizedTestCase

@end

@implementation ParamTestUIImage_Format

+ (NSArray<_QuickSelectorWrapper *> *)genMethod0 {
    NSArray *params = @[
        [self genDataParams],
    ];
    
    
    if (![UIImage respondsToSelector:@selector(imageFromData:)]) {
        return @[];
    }
    
    return
    [self buildMethods:params methodName:@"imageFromData:" excuteBlock:^(ParametrizedTestCase *example, NSArray *eachCaseParams) {
        id eachParam1Origin = QUnboxFor(eachCaseParams[0]);
        id eachParam1 = eachParam1Origin;
        
        [UIImage imageFromData:eachParam1];
    }];
}

@end

此时我们可以做到原生针对一个被测方法写一个genMethod方法生成多个测试方法。

接下来我们看看ruby脚本怎么做:

根据不同的功能,拆分了不同的ruby脚本
ReadProject->TCGen->ModProj

ModProj.rb

功能:修改项目文件,自动生成目录结构
实现原理:借助了cocoapod开源的的 Xcodeproj工具(https://github.com/CocoaPods/Xcodeproj),修改了项目的project.pbxproj文件

ReadProject.rb

功能:找到项目文件中public的.h文件
实现原理:和Xcodeproj工具类似的原理,自己去分析项目的project.pbxproj文件,正则匹配找到了public的头文件

TCGen.rb

功能:
a.正则匹配.h文件,找出OC被测方法
b.按照原生模板,写入testCase.m文件

实现原理:
1.分析头文件,获取到了函数名,函数的是类方法还是实例方法,参数类型。
2.根据分析得出的类生成 “ParamTest被测类”
3.根据每个被测方法生成’genMethod‘方法
4.在每个’genMethod‘方法里 根据参数类型拼接出"gen参数"方法,放在生成的模板函数里。根据方法名,类名,是否为实例方法,拼接出OC的方法调用。
5.最终生成了.m文件,包含了OC生成单元测试方法的代码。
麻烦的是有部分类遵循Protocol,为此需要输入参数加上代码目录,寻找协议头文件再次加回代码。

其它的一些细节:
生成自定义测试方法的名称:

格式为 test|方法名|参数1_ 参数2
部分参数替换成缩写(例如Float.max)

自定义的类:

脚本无法根据自定义的类生成构造函数,需要使用方自己写构造方法以及传入时需要的拆解方法(放在分类)

注意:

在写自定义参数有nil时,使用QUnbox拆包

最终我们使用以下命令:
  1. 生成 边界单元测试 代码
    ruby ReadProject.rb [xcodeproj位置] [单元测试Target] [项目代码路径]
cd ./GenUtil
ruby ReadProject.rb ../CombineTest.xcodeproj CombineTestTests ../CombineTest/

2.使用命令行可以直接开始测试(可选)

xcodebuild test -scheme CommonKitUnitTests -destination 'platform=iOS Simulator,name=iPhone 11 Pro Max,OS=14.0' -workspace CommonKit.xcworkspace

3.同样可以使用开源工具XCTestHTMLReport分析xcresult,看到出错的单元测试(可选)

在本地运行单元测试的效果
test.png
shell分析单元测试结果
shell.png

你可能感兴趣的:(iOS 自动生成参数单元测试实践方案)