单元测试(二)

2021-03-20  本文已影响0人  浅墨入画

一. OC&Swift测试工程配置

创建包含单元测试的OC工程TestApp,在TestAppTests目录下创建Swift单元测试文件TestAppSwift.swift,Xcode会提示创建桥接文件TestAppTests-Bridging-Header.h,我们选择创建,创建完目录如下
注意: 此时的桥接文件只在TestAppTests下生效

image.png

接下来我们在TestApp目录下创建Swift文件SwiftApp.swift,Xcode也会提示创建桥接文件TestApp-Bridging-Header.h,我们选择创建,目录如下

image.png

1.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目录下

image.png

此时选中Intermediates.noindex目录往下找,会发现estApp-Swift.h在DerivedSources目录下

image.png

找到路径之后开始配置,其中Debug-iphonesimulator目录即为CONFIGURATION_TEMP_DIR,配置内容为CONFIGURATION_TEMP_DIR/TestApp.build/DerivedSources,
如下图

image.png

测试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文件如下

image.png

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那样来使用需要怎么办?

image.png
// 编辑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.png

Cmd + U 测试成功,说明把TestApp-Swift.h写入mm.hmap文件,Header Search Paths 引入mm.hmap的方式,也能找到TestApp-Swift.h

上一篇下一篇

猜你喜欢

热点阅读