单元测试(二)
一. OC&Swift测试工程配置
创建包含单元测试的OC工程TestApp,在TestAppTests目录下创建Swift单元测试文件TestAppSwift.swift,Xcode会提示创建桥接文件TestAppTests-Bridging-Header.h,我们选择创建,创建完目录如下
注意: 此时的桥接文件只在TestAppTests下生效
接下来我们在TestApp目录下创建Swift文件SwiftApp.swift,Xcode也会提示创建桥接文件TestApp-Bridging-Header.h,我们选择创建,目录如下
image.png1.1 测试OC类中引用工程OC类
测试类中引入工程OC类头文件即可使用
//TestAppTests.m内容
#import <XCTest/XCTest.h>
#import "ViewController.h"
@interface TestAppTests : XCTestCase
@end
@implementation TestAppTests
- (void)setUp {
}
- (void)testExample {
ViewController *vc = [ViewController new];
}
@end
// Cmd + U 测试成功
1.2 测试Swift类中引用工程OC类
现在我们想在TestAppTests目录下TestAppSwift.swift文件中使用ViewController,需要修改文件内容如下
// 第一步 TestApp目录下桥接文件TestApp-Bridging-Header.h中导入ViewController
// TestApp-Bridging-Header.h内容
#import "ViewController.h"
// 第二步 TestAppTests目录下修改TestAppSwift.swift文件内容
import XCTest
import TestApp //导入module的方式引入
class TestAppSwift: XCTestCase {
func testExample() throws {
let vc = ViewController()
}
}
// Cmd + U 测试成功
1.3 测试Swift类中引用工程Swift类
我们在SwiftApp.swift文件中创建两个模型
// SwiftApp.swift内容
import Foundation
@objc open class SwiftModel: NSObject {
var num: Int?
}
class SwiftModel2: NSObject {
}
这个时候我们在TestAppSwift.swift文件引用SwiftModel与SwiftModel2,会发现SwiftModel2找不到,这时就需要用到@testable
//TestApp目录下创建的属性为internal的类,需要使用@testable才能在TestAppTests中引用
import XCTest
@testable import TestApp
class TestAppSwift: XCTestCase {
func testExample() throws {
let vc = ViewController()
let model = SwiftModel()
let model2 = SwiftModel2()
}
}
// Cmd + U 测试成功
1.4 工程OC类中引用工程Swift类
工程OC类中引入TestApp-Swift.h即可使用
//ViewController.m内容
#import "ViewController.h"
#import "TestApp-Swift.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
SwiftModel *model = [SwiftModel new];
SwiftModel2 *model2 = [SwiftModel2 new];
// Do any additional setup after loading the view.
}
@end
// Cmd + B 编译成功
1.5 测试OC类中引用工程Swift类
TestApp-Swift.h文件是在TestApp生成的,所以需要在TestAppTests中配置TestApp-Swift.h路径,现在需要找到TestApp-Swift.h路径,选中Products目录下TestApp.app,右键show in finder,可以看出TestApp在Products->Debug-iphonesimulator目录下
此时选中Intermediates.noindex目录往下找,会发现estApp-Swift.h在DerivedSources目录下
image.png找到路径之后开始配置,其中Debug-iphonesimulator目录即为CONFIGURATION_TEMP_DIR/TestApp.build/DerivedSources,
如下图
测试OC类中引入TestApp-Swift.h即可使用
// TestAppTests.m内容
#import <XCTest/XCTest.h>
#import "ViewController.h"
#import "TestApp-Swift.h"
@interface TestAppTests : XCTestCase
@end
@implementation TestAppTests
- (void)setUp {
}
- (void)testExample {
ViewController *vc = [ViewController new];
SwiftModel *model = [SwiftModel new];
SwiftModel2 *model2 = [SwiftModel2 new];
}
@end
// Cmd + U 测试成功
注意:非递归non-recursive与递归recursive
image.png二. HeaderMap原理&读取
2.1 手动查看.hmap结构内容
打开我们的DumpHeaderMap工程,把上面例子中的.hmap文件复制到工程根目录下,Edit Scheme 中配置.hmap文件如下
main函数中打上断点,Cmd + R运行工程
image.png// 参数一 当前可执行文件的路径
// 参数二 配置的.hmap文件路径
(lldb) parray 2 argv
(const char **) $0 = 0x00007ffeefbff390 {
(const char *) [0] = 0x00007ffeefbff598 "/Users/wangning/Library/Developer/Xcode/DerivedData/DumpHeaderMap-gtvyguhkmfjhahbpigfnkdwbquht/Build/Products/Debug/DumpHeaderMap"
(const char *) [1] = 0x00007ffeefbff61a "/Users/wangning/Documents/资料/3:16/第十六节课、单元测试与UI测试/上课代码/02-HeaderMap原理&读取/DumpHeaderMap/LGTestApp-project-headers.hmap"
}
接下来在这里打上断点,执行断点
image.png// 继续执行打印如下
// String table entry count: 5,说明有5个头文件
Header map: /Users/wangning/Documents/资料/3:16/第十六节课、单元测试与UI测试/上课代码/02-HeaderMap原理&读取/DumpHeaderMap/LGTestApp-project-headers.hmap
Hash bucket count: 8
String table entry count: 5
Max value length: 0 bytes
// 跳过断点打印如下
// 主题
// Key LGTestApp-Bridging-Header.h
// 头文件的前半部分
// /Users/ws/Desktop/VIP课程/第十六节课、单元测试与UI测试/上课代码/01-OC&Swift测试工程配置/LGTestApp/LGTestApp/
//头文件的后半部分
// Suffix LGTestApp-Bridging-Header.h
// hmap 对应 按照规则存储一堆的头文件
- Bucket 1: Key ViewController.h -> Prefix /Users/ws/Desktop/VIP课程/第十六节课、单元测试与UI测试/上课代码/01-OC&Swift测试工程配置/LGTestApp/LGTestApp/, Suffix ViewController.h
- Bucket 2: Key AppDelegate.h -> Prefix /Users/ws/Desktop/VIP课程/第十六节课、单元测试与UI测试/上课代码/01-OC&Swift测试工程配置/LGTestApp/LGTestApp/, Suffix AppDelegate.h
- Bucket 3: Key SceneDelegate.h -> Prefix /Users/ws/Desktop/VIP课程/第十六节课、单元测试与UI测试/上课代码/01-OC&Swift测试工程配置/LGTestApp/LGTestApp/, Suffix SceneDelegate.h
- Bucket 6: Key LGTestAppTests-Bridging-Header.h -> Prefix /Users/ws/Desktop/VIP课程/第十六节课、单元测试与UI测试/上课代码/01-OC&Swift测试工程配置/LGTestApp/LGTestAppTests/, Suffix LGTestAppTests-Bridging-Header.h
- Bucket 7: Key LGTestApp-Bridging-Header.h -> Prefix /Users/ws/Desktop/VIP课程/第十六节课、单元测试与UI测试/上课代码/01-OC&Swift测试工程配置/LGTestApp/LGTestApp/, Suffix LGTestApp-Bridging-Header.h
2.2 终端配置DumpHeaderMap,查看.hmap结构内容
接下来我们想让可执行文件DumpHeaderMap像终端lldb那样来使用需要怎么办?
- 首先我们找到可执行文件DumpHeaderMap,并且在用户 -> wn 目录下创建如下文件,放入DumpHeaderMap
- 修改配置文件如下
// 编辑bashrc文件
$ vim ~/.bashrc
image.png
// 让配置生效
$ vim ~/.bashrc
此时终端使用DumpHeaderMap命令即可生效
image.png我们发现有的文件是相对路径,有的是绝对路径,原因是Build Phases -> Headers下面,头文件为Public/Private属性都为相对路径,只有头文件为Project属性才为绝对路径。
三. 生成HeaderMap文件
3.1 测试手动生成hmap文件
使用上面TestApp工程,找到TestApp-Swift.h文件,复制到TestApp工程根目录下
打开WriteHeaderMap工程,修改write方法如下
void write() {
struct HMapHeader header;
// 8 :指定几个Bucket
// 750: 指定buffer大小
typedef MapFile<8, 750> FileTy;
FileTy File;
File.init();
FileMaker<FileTy> Maker(File);
// 1.添加第一个Bucket
// 添加key
auto a = Maker.addString("TestApp-Swift.h");
// 前半部分
auto b = Maker.addString("/Users/wn/Desktop/TestApp/");
// 后半部分
auto c = Maker.addString("TestApp-Swift.h");
Maker.addBucket(getHash(@"TestApp-Swift.h"), a, b, c);
auto ViewControllera = Maker.addString("ViewController.h");
auto ViewControllerb = Maker.addString("/Users/wn/Desktop/TestApp/TestApp");
auto ViewControllerc = Maker.addString("ViewController.h");
Maker.addBucket(getHash(@"ViewController.h"), ViewControllera, ViewControllerb, ViewControllerc);
auto Bridginga = Maker.addString("TestApp-Bridging-Header.h");
auto Bridgingb = Maker.addString("/Users/wn/Desktop/TestApp/TestApp");
auto Bridgingc = Maker.addString("TestApp-Bridging-Header.h");
Maker.addBucket(getHash(@"TestApp-Bridging-Header.h"), Bridginga, Bridgingb, Bridgingc);
auto ScrollViewa = Maker.addString("TestAppTests-Bridging-Header.h");
auto ScrollViewb = Maker.addString("/Users/wn/Desktop/TestApp/LGTestAppTests/");
auto ScrollViewc = Maker.addString("TestAppTests-Bridging-Header.h");
Maker.addBucket(getHash(@"TestAppTests-Bridging-Header.h"), ScrollViewa, ScrollViewb, ScrollViewc);
NSData *data = File.getBuffer();
[data getBytes:&header length:sizeof(struct HMapHeader)];
// bucket写入路径
[data writeToFile:@"/Users/wn/Desktop/TestApp/TestApp/mm.hmap" atomically:YES];
bool is_swapped = false;
uint32_t (*swap)(uint32_t) = nop_swap;
if (header.Magic == HMAP_HeaderMagicNumber
&& header.Version == HMAP_HeaderVersion) {
is_swapped = false;
} else if (header.Magic == HMAP_SwappedMagic
&& header.Version == HMAP_SwappedVersion) {
is_swapped = true;
swap = actually_swap;
} else {
warn(/*EX_DATAERR,*/ "header lacks HMAP magic");
return;
}
const uint32_t bucket_count = swap(header.NumBuckets);
printf(
"\tHash bucket count: %" PRIu32 "\n"
"\tString table entry count: %" PRIu32 "\n"
"\tMax value length: %" PRIu32 " bytes\n",
bucket_count,
swap(header.NumEntries),
swap(header.MaxValueLength));
const char *raw = (const char *)data.bytes;
const struct HMapBucket *const buckets = (const struct HMapBucket *const)(raw
+ sizeof(struct HMapHeader));
const char *const string_table = (raw
+ sizeof(struct HMapHeader)
+ bucket_count*sizeof(struct HMapBucket));
for (uint32_t i = 0; i < bucket_count; ++i) {
const struct HMapBucket *const bucket = &(buckets[i]);
if (swap(bucket->Key) == HMAP_EmptyBucketKey) { continue; }
// LLVM is careful to sanity-check bucket-count and strlen,
// but we're just going to assume they're all NUL-terminated
// and not maliciously crafted input files.
//
// This is naive, but this is also not exactly "production" code.
const char *key = string_table + swap(bucket->Key);
const char *prefix = string_table + swap(bucket->Prefix);
const char *suffix = string_table + swap(bucket->Suffix);
NSLog(@"\t- Bucket %" PRIu32 ": "
"Key %s -> Prefix %s, Suffix %s\n",
i,
key, prefix, suffix);
}
}
// 打印可以看出,bucket写入mm.hmap文件
Hash bucket count: 2
String table entry count: 1
Max value length: 0 bytes
2021-03-19 23:09:50.529531+0800 WriteHeaderMap[63414:6244069] - Bucket 0: Key LGTestApp-Swift.h -> Prefix /Users/wangning/Desktop/TestApp/, Suffix TestApp-Swift.h
TestAppTests配置mm.hmap文件路径如下图
image.pngCmd + U 测试成功,说明把TestApp-Swift.h写入mm.hmap文件,Header Search Paths 引入mm.hmap的方式,也能找到TestApp-Swift.h