美文网首页
单元测试(一)

单元测试(一)

作者: 浅墨入画 | 来源:发表于2021-03-14 22:52 被阅读0次

    前言:

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

    • 核心功能:模型类和方法及其与控制器的交互 2. UI工作流程
    • 特殊的边界条件
    • Bug处理

    测试原则 FIRST:

    • Fast:测试模块应该是快速高效的;
    • Independent/Isolated:测试模块应该是独立相互不影响的;
    • Repeatable:测试实例应该是可以重复使用的,测试结果应该是相同的;
    • Self-validating:测试应完全自动化。输出结果要么是“成功”,要么是“失败”;
    • Timely:理想情况下,应该在编写要测试的生产代码之前编写测试(测试驱动开发)。

    测试驱动开发

    • TDD Test-Driven Development
    • BDD Behavior-Driven Development
      三个步骤:
    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 是怎么测试到我们工程中的功能呢?

    • Products文件下,选中LoginApp.app,右键show in finder
    image.png
    • 选中LoginAPPUITests-Runner,右键显示包内容
    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];
        }
    }
    

    相关文章

      网友评论

          本文标题:单元测试(一)

          本文链接:https://www.haomeiwen.com/subject/gukccltx.html