单元测试(一)

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

前言:

单元测试 Unit Test
单元测试:是检查每个代码单元(例如类或函数)是否能产生预期
的结果。 单元测试是独立运行的,不依赖于其他模块或组件。
UI测试
UI测试:属于端到端测试,是从应用程序启动到结束的测试过程。
UI测试:完全按照用户与应用程序交互的方式来复制与应用程序的交 互。
UI测试:比单元测试慢得多,运行起来也更消耗资源。
测试应涵盖:

测试原则 FIRST:

测试驱动开发

  1. Arrange:构建数据,描述代码场景;
  2. Act:编写代码;
  3. Assert:检查代码是否达到预期效果

一. 单元测试 Unit Test

首先创建带有单元测试的工程LoginApp,创建完成之后,在文件AppDelegate.m的didFinishLaunchingWithOptions方法中打断点,Cmd + U执行单元测试,会发现断点执行到didFinishLaunchingWithOptions方法
得出结论: 单元测试需要启动App,需要执行didFinishLaunchingWithOptions方法
此时会有一个问题,didFinishLaunchingWithOptions方法中需要添加启动页 定位 等相关业务功能,此时执行单元测试didFinishLaunchingWithOptions方法中的业务相关代码也会执行,这就导致单元测试会非常耗时。
接下来探索避免单元测试的耗时问题?
新建FakeAppDelegate类

// FakeAppDelegate.h文件内容
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface FakeAppDelegate : UIResponder <UIApplicationDelegate>

@end
NS_ASSUME_NONNULL_END
// FakeAppDelegate.m文件内容
#import "FakeAppDelegate.h"

@implementation FakeAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    return YES;
}
@end
// 修改main.m内容如下,把需要加载的appdelegate修改为FakeAppDelegate
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, @"FakeAppDelegate");
}

Cmd + U执行单元测试,发现会执行FakeAppDelegate中的方法
此时只要判断当前运行的是单元测试工程就加载FakeAppDelegate,如果运行的是LoginApp工程,就加载AppDelegate,这样就能避免单元测试的耗时问题

// Cmd + U运行单元测试的时候,单元测试工程LoginAppTests中创建的类都会进行编译,可以用下面方法判断当前运行的是单元测试还是LoginApp工程
#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        // 方法 字符串-》class
        id cls = NSClassFromString(@"XCTest");
        appDelegateClassName = cls ?  @"FakeAppDelgate" : NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

二. 链接XCTest动态库

Cmd + U 执行单元测试,下面打印信息

// 单元测试LoginAPPTests.m文件中testExample方法,底层是Test Case
// 每次执行testExample方法需要0.086秒
Test Case '-[LoginAppUnitTests testExample]' passed (0.086 seconds).

通过单元测试可以获取一些信息,比如编写一个Test Case,能够获取当前测试用例的执行时间,那么Targes下LoginAPPTests 是怎么测试到我们工程中的功能呢?

image.png image.png

发现Frameworks下面多了几个动态库,是不是ipa包里面有了这几个动态库,就可以在项目中运行我们的测试工程?
接下来就给我们的工程引入XCTest动态库

// 在Xcode中寻找XCTest动态库
$ lldb -P
/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python3
$ open /Applications/Xcode.app/Contents

// XCTest动态库目录
Contents -> Developer -> Platforms -> iPhoneSimulator.platform(这里选择模拟器) -> Developer -> Library -> Frameworks -> XCTest.framework
找到了XCTest,就开始把XCTest引入我们的工程
创建Debug.Config.xcconfig文件,并进行相应配置

// 引入XCTest动态库
// 方式一
// 1. header
HEADER_SEARCH_PATHS = $(inherited) "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks/XCTest.framework/Headers"
// 2.链接动态库
// 传统方式
OTHER_LDFLAGS = $(inherited) -F"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks" -framework "XCTest"
// 3.配置rpath
LD_RUNPATH_SEARCH_PATHS = $(inherited) "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks"
// 方式二
// 1. header
HEADER_SEARCH_PATHS = $(inherited) "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks/XCTest.framework/Headers"
// 2.链接动态库
// 配置路径
SLASH = /
OTHER_LDFLAGS = $(inherited) ${SLASH}/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks/XCTest.framework/XCTest
// 3.配置rpath
LD_RUNPATH_SEARCH_PATHS = $(inherited) "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Frameworks"

三. 执行XCTestCase

XCTest动态库引入之后,执行单元测试,ViewController.m文件内容修改如下

#import "ViewController.h"
// module
#import <XCTest/XCTest.h>

@interface LoginAppUITests : XCTestCase

@end

@implementation LoginAppUITests

- (void)setUp {
    self.continueAfterFailure = NO;
}

- (void)testExample {
    NSLog(@"------1223412");
}
@end
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 管理者 XCTestSuite
    XCTestSuite *suite = [XCTestSuite defaultTestSuite];
    // 初始化 testCase
    LoginAppUITests *testCase = [LoginAppUITests new];
    // testCase -》 suite
    [suite addTest:testCase];
    for (XCTest *test in suite.tests) {
        [test runTest];
    }
}
@end
// 运行工程,点击屏幕,正常打印
2021-03-14 22:22:20.839039+0800 LoginApp[41985:4221406] ------1223412

上面方式只能执行一次,第一次点击屏幕成功打印,第二次点击屏幕会崩溃,原因是[XCTestSuite defaultTestSuite] 这种方式只能执行一次。
接下来我们来学习第二种方式:这样就能避免只执行一次的问题,这样就可以边运行边测试

// 修改点击方法如下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // 管理者 XCTestSuite
    XCTestSuite *suite = [XCTestSuite testSuiteForTestCaseClass:LoginAppUITests.class];
    // 初始化 testCase
    LoginAppUITests *testCase = [LoginAppUITests new];
    // testCase -》 suite
    [suite addTest:testCase];
    for (XCTest *test in suite.tests) {
        [test runTest];
    }
}
// 如果运行时间过长,可以边运行边测试来进行相应优化
2021-03-14 22:35:47.512057+0800 LoginApp[42107:4233651] ------1223412
Test Case '-[LoginAppUITests testExample]' passed (0.005 seconds).
/*!  官方文档
 * @class XCTestSuite
 * A concrete subclass of XCTest, XCTestSuite is a collection of test cases. Suites
 * are usually managed by the IDE, but XCTestSuite also provides API for dynamic test
 * and suite management:
 * @textblock

    XCTestSuite *suite = [XCTestSuite testSuiteWithName:@"My tests"];
    [suite addTest:[MathTest testCaseWithSelector:@selector(testAdd)]];
    [suite addTest:[MathTest testCaseWithSelector:@selector(testDivideByZero)]]; */
// 第三种方式:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    // @"MyTest"是固定写法
    XCTestSuite *suite = [XCTestSuite testSuiteWithName:@"MyTest"];
    // 指定单元测试类中某一个方法
    [suite addTest:[LoginAppUITests testCaseWithSelector:@selector(testExample)]];
    for (XCTest *test in suite.tests) {
        [test runTest];
    }
}
上一篇下一篇

猜你喜欢

热点阅读