51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2697|回复: 2
打印 上一主题 下一主题

[转贴] 为什么我说写好测试很重要(二)

[复制链接]
  • TA的每日心情
    无聊
    3 天前
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2021-8-9 10:09:40 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
     四、 单元测试下开发模式、技术框架选择
      单元测试是按照测试范围来划分的。TDD、BDD 是按照开发模式来划分的。因此就有各种排列组合,这里我们只关心单元测试下的 TDD、BDD 方案。
      在单元测试阶段,TDD 和 BDD 都可以适用。

      1. TDD
      TDD 强调不断的测试推动代码的开发,这样简化了代码,保证了代码质量。
      思想是在拿到一个新的功能时,首先思考该功能如何测试,各种测试用例、各种边界 case;然后完成测试代码的开发;最后编写相应的代码以满足、通过这些测试用例。
      TDD 开发过程类似下图:

    先编写该功能的测试用例,实现测试代码。这时候去跑测试,是不通过的,也就是到了红色的状态。
      然后编写真正的功能实现代码。这时候去跑测试,测试通过,也就是到了绿色的状态。
      在测试用例的保证下,可以重构、优化代码。

      抛出一个问题:TDD 看上去很好,应该用它吗?
      这个问题不用着急回答,回答了也不会有对错之分。开发中经常是这样一个流程,新的需求出来后,先经过技术评审会议,确定宏观层面的技术方案、确定各个端的技术实现、使用的技术等,整理出开发文档、会议文档。工期评估后开始编码。事情这么简单吗?前期即使想的再充分、再细致,可能还是存在特殊 case 漏掉的情况,导致技术方案或者是技术实现的改变。如果采用 TDD,那么之前新功能给到后,就要考虑测试用例的设计、编写了测试代码,在测试用例的保证下再去实现功能。如果遇到了技术方案的变更,之前的测试用例要改变、测试代码实现要改变。可能新增的某个 case 导致大部分的测试代码和实现代码都要改变。

      如何开展 TDD
      新建一个工程,确保 “Include Unit Tests” 选项是选中的状态。

    创建后的工程目录如下:

    删除 Xcode 创建的测试模版文件 TDDDemoTests.m。
      假如我们需要设计一个人类,它具有吃饭的功能,且当他吃完后会说一句“好饱啊”。
      那么按照 TDD 我们先设计测试用例。假设有个 Person 类,有个对象方法叫做吃饭,吃完饭后会返回一个“好饱啊”的字符串。那测试用例就是:

    实现测试用例代码。创建继承自 Unit Test Case class 的测试类,命名为 工程前缀+测试类名+Test,也就是 TDDPersonTest.m。

    因为要测试 Person 类,所以在主工程中创建 Person 类。
      因为要测试人类在吃饭后说一句“好饱啊”。所以设想那个类目前只有一个吃饭的方法。于是在 TDDPersonTest.m 中创建一个测试函数 -(void)testReturnStatusStringWhenPersonAte;函数内容如下:
    1. - (void)testReturnStatusStringWhenPersonAte
    2. {
    3.     // Given
    4.     Person *somebody = [[Person alloc] init];
    5.    
    6.     // When
    7.     NSString *statusMessage = [somebody performSelector:@selector(eat)];
    8.    
    9.     // Then
    10.     XCTAssert([statusMessage isEqualToString:@"好饱啊"], @"Person 「吃饭后返回“好饱啊”」功能异常");
    11. }
    复制代码
    Xcode 下按快捷键 Command + U,跑测试代码发现是失败的。因为我们的 Person 类根本没实现相应的方法。
      从 TDD 开发过程可以看到,我们现在是红色的 “Fail” 状态。所以需要去 Person 类中实现功能代码。Person 类如下:
    1. #import "Person.h"

    2. @implementation Person

    3. - (NSString *)eat
    4. {
    5.     [NSThread sleepForTimeInterval:1];
    6.     return @"好饱啊";;
    7. }

    8. @end
    复制代码
     再次运行,跑一下测试用例(Command + U 快捷键)。发现测试通过,也就是TDD 开发过程中的绿色 “Success” 状态。
      例子比较简单,假如情况需要,可以在 -(void)setUp 方法里面做一些测试的前置准备工作,在 -(void)tearDown 方法里做资源释放的操作。
      假如 eat 方法实现的不够漂亮。现在在测试用例的保证下,大胆重构,最后确保所有的 Unit Test case 通过即可。

      2. BDD
      相比 TDD,BDD 关注的是行为方式的设计,拿上述“人吃饭”举例说明。
      和 TDD 相比第1~4步骤相同。
      BDD 则需要先实现功能代码。创建 Person 类,实现 -(void)eat;方法。代码和上面的相同。
      BDD 需要引入好用的框架 Kiwi,使用 Pod 的方式引入。
      因为要测试人类在吃饭后说一句“好饱啊”。所以设想那个类目前只有一个吃饭的方法。于是在 TDDPersonTest.m 中创建一个测试函数 -(void)testReturnStatusStringWhenPersonAte;函数内容如下:
    1. #import "kiwi.h"
    2. #import "Person.h"

    3. SPEC_BEGIN(BDDPersonTest)

    4. describe(@"Person", ^{
    5.     context(@"when someone ate", ^{
    6.         it(@"should get a string",^{
    7.          Person *someone = [[Person alloc] init];
    8.             NSString *statusMessage = [someone eat];
    9.             [[statusMessage shouldNot] beNil];
    10.             [[statusMessage should] equal:@"好饱啊"];
    11.         });
    12.     });
    13. });

    14. SPEC_EN
    复制代码
    3. XCTest
      开发步骤
      Xcode 自带的测试系统是 XCTest,使用简单。开发步骤如下:
      在 Tests 目录下为被测的类创建一个继承自 XCTestCase 的测试类。
      删除新建的测试代码模版里面的无用方法 - (void)testPerformanceExample、- (void)testExample。
      跟普通类一样,可以继承,可以写私有属性、私有方法。所以可以在新建的类里面,根据需求写一些私有属性等。
      在 - (void)setUp 方法里面写一些初始化、启动设置相关的代码。比如测试数据库功能的时候,写一些数据库连接池相关代码。
      为被测类里面的每个方法写测试方法。被测类里面可能是 n 个方法,测试类里面可能是 m 个方法(m >= n),根据我们在第三部分:单元测试编码规范里讲过的 一个测试用例只测试一个分支,方法内部有 if、switch 语句时,需要为每个分支写测试用例。
      为测试类每个方法写的测试方法有一定的规范。命名必须是 test+被测方法名。函数无参数、无返回值。比如 - (void)testSharedInstance。
      测试方法里面的代码按照 Given->When->Then 的顺序展开。测试环境所需的先决条件准备;调用所要测试的某个方法、函数;使用断言验证输出和行为是否符合预期。
      在 - (void)tearDown 方法里面写一些释放掉资源或者关闭的代码。比如测试数据库功能的时候,写一些数据库连接池关闭的代码。


      断言相关宏:
    1. /*!
    2. * [url=home.php?mod=space&uid=10594]@Function[/url] XCTFail(...)
    3. * Generates a failure unconditionally.
    4. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    5. */
    6. #define XCTFail(...) \
    7.     _XCTPrimitiveFail(self, __VA_ARGS__)

    8. /*!
    9. * @define XCTAssertNil(expression, ...)
    10. * Generates a failure when ((\a expression) != nil).
    11. * @param expression An expression of id type.
    12. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    13. */
    14. #define XCTAssertNil(expression, ...) \
    15.     _XCTPrimitiveAssertNil(self, expression, @#expression, __VA_ARGS__)

    16. /*!
    17. * @define XCTAssertNotNil(expression, ...)
    18. * Generates a failure when ((\a expression) == nil).
    19. * @param expression An expression of id type.
    20. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    21. */
    22. #define XCTAssertNotNil(expression, ...) \
    23.     _XCTPrimitiveAssertNotNil(self, expression, @#expression, __VA_ARGS__)

    24. /*!
    25. * @define XCTAssert(expression, ...)
    26. * Generates a failure when ((\a expression) == false).
    27. * @param expression An expression of boolean type.
    28. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    29. */
    30. #define XCTAssert(expression, ...) \
    31.     _XCTPrimitiveAssertTrue(self, expression, @#expression, __VA_ARGS__)

    32. /*!
    33. * @define XCTAssertTrue(expression, ...)
    34. * Generates a failure when ((\a expression) == false).
    35. * @param expression An expression of boolean type.
    36. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    37. */
    38. #define XCTAssertTrue(expression, ...) \
    39.     _XCTPrimitiveAssertTrue(self, expression, @#expression, __VA_ARGS__)

    40. /*!
    41. * @define XCTAssertFalse(expression, ...)
    42. * Generates a failure when ((\a expression) != false).
    43. * @param expression An expression of boolean type.
    44. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    45. */
    46. #define XCTAssertFalse(expression, ...) \
    47.     _XCTPrimitiveAssertFalse(self, expression, @#expression, __VA_ARGS__)

    48. /*!
    49. * @define XCTAssertEqualObjects(expression1, expression2, ...)
    50. * Generates a failure when ((\a expression1) not equal to (\a expression2)).
    51. * @param expression1 An expression of id type.
    52. * @param expression2 An expression of id type.
    53. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    54. */
    55. #define XCTAssertEqualObjects(expression1, expression2, ...) \
    56.     _XCTPrimitiveAssertEqualObjects(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

    57. /*!
    58. * @define XCTAssertNotEqualObjects(expression1, expression2, ...)
    59. * Generates a failure when ((\a expression1) equal to (\a expression2)).
    60. * @param expression1 An expression of id type.
    61. * @param expression2 An expression of id type.
    62. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    63. */
    64. #define XCTAssertNotEqualObjects(expression1, expression2, ...) \
    65.     _XCTPrimitiveAssertNotEqualObjects(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

    66. /*!
    67. * @define XCTAssertEqual(expression1, expression2, ...)
    68. * Generates a failure when ((\a expression1) != (\a expression2)).
    69. * @param expression1 An expression of C scalar type.
    70. * @param expression2 An expression of C scalar type.
    71. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    72. */
    73. #define XCTAssertEqual(expression1, expression2, ...) \
    74.     _XCTPrimitiveAssertEqual(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

    75. /*!
    76. * @define XCTAssertNotEqual(expression1, expression2, ...)
    77. * Generates a failure when ((\a expression1) == (\a expression2)).
    78. * @param expression1 An expression of C scalar type.
    79. * @param expression2 An expression of C scalar type.
    80. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    81. */
    82. #define XCTAssertNotEqual(expression1, expression2, ...) \
    83.     _XCTPrimitiveAssertNotEqual(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

    84. /*!
    85. * @define XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, ...)
    86. * Generates a failure when (difference between (\a expression1) and (\a expression2) is > (\a accuracy))).
    87. * @param expression1 An expression of C scalar type.
    88. * @param expression2 An expression of C scalar type.
    89. * @param accuracy An expression of C scalar type describing the maximum difference between \a expression1 and \a expression2 for these values to be considered equal.
    90. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    91. */
    92. #define XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, ...) \
    93.     _XCTPrimitiveAssertEqualWithAccuracy(self, expression1, @#expression1, expression2, @#expression2, accuracy, @#accuracy, __VA_ARGS__)

    94. /*!
    95. * @define XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, ...)
    96. * Generates a failure when (difference between (\a expression1) and (\a expression2) is <= (\a accuracy)).
    97. * @param expression1 An expression of C scalar type.
    98. * @param expression2 An expression of C scalar type.
    99. * @param accuracy An expression of C scalar type describing the maximum difference between \a expression1 and \a expression2 for these values to be considered equal.
    100. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    101. */
    102. #define XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, ...) \
    103.     _XCTPrimitiveAssertNotEqualWithAccuracy(self, expression1, @#expression1, expression2, @#expression2, accuracy, @#accuracy, __VA_ARGS__)

    104. /*!
    105. * @define XCTAssertGreaterThan(expression1, expression2, ...)
    106. * Generates a failure when ((\a expression1) <= (\a expression2)).
    107. * @param expression1 An expression of C scalar type.
    108. * @param expression2 An expression of C scalar type.
    109. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    110. */
    111. #define XCTAssertGreaterThan(expression1, expression2, ...) \
    112.     _XCTPrimitiveAssertGreaterThan(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

    113. /*!
    114. * @define XCTAssertGreaterThanOrEqual(expression1, expression2, ...)
    115. * Generates a failure when ((\a expression1) < (\a expression2)).
    116. * @param expression1 An expression of C scalar type.
    117. * @param expression2 An expression of C scalar type.
    118. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    119. */
    120. #define XCTAssertGreaterThanOrEqual(expression1, expression2, ...) \
    121.     _XCTPrimitiveAssertGreaterThanOrEqual(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

    122. /*!
    123. * @define XCTAssertLessThan(expression1, expression2, ...)
    124. * Generates a failure when ((\a expression1) >= (\a expression2)).
    125. * @param expression1 An expression of C scalar type.
    126. * @param expression2 An expression of C scalar type.
    127. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    128. */
    129. #define XCTAssertLessThan(expression1, expression2, ...) \
    130.     _XCTPrimitiveAssertLessThan(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

    131. /*!
    132. * @define XCTAssertLessThanOrEqual(expression1, expression2, ...)
    133. * Generates a failure when ((\a expression1) > (\a expression2)).
    134. * @param expression1 An expression of C scalar type.
    135. * @param expression2 An expression of C scalar type.
    136. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    137. */
    138. #define XCTAssertLessThanOrEqual(expression1, expression2, ...) \
    139.     _XCTPrimitiveAssertLessThanOrEqual(self, expression1, @#expression1, expression2, @#expression2, __VA_ARGS__)

    140. /*!
    141. * @define XCTAssertThrows(expression, ...)
    142. * Generates a failure when ((\a expression) does not throw).
    143. * @param expression An expression.
    144. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    145. */
    146. #define XCTAssertThrows(expression, ...) \
    147.     _XCTPrimitiveAssertThrows(self, expression, @#expression, __VA_ARGS__)

    148. /*!
    149. * @define XCTAssertThrowsSpecific(expression, exception_class, ...)
    150. * Generates a failure when ((\a expression) does not throw \a exception_class).
    151. * @param expression An expression.
    152. * @param exception_class The class of the exception. Must be NSException, or a subclass of NSException.
    153. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    154. */
    155. #define XCTAssertThrowsSpecific(expression, exception_class, ...) \
    156.     _XCTPrimitiveAssertThrowsSpecific(self, expression, @#expression, exception_class, __VA_ARGS__)

    157. /*!
    158. * @define XCTAssertThrowsSpecificNamed(expression, exception_class, exception_name, ...)
    159. * Generates a failure when ((\a expression) does not throw \a exception_class with \a exception_name).
    160. * @param expression An expression.
    161. * @param exception_class The class of the exception. Must be NSException, or a subclass of NSException.
    162. * @param exception_name The name of the exception.
    163. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    164. */
    165. #define XCTAssertThrowsSpecificNamed(expression, exception_class, exception_name, ...) \
    166.     _XCTPrimitiveAssertThrowsSpecificNamed(self, expression, @#expression, exception_class, exception_name, __VA_ARGS__)

    167. /*!
    168. * @define XCTAssertNoThrow(expression, ...)
    169. * Generates a failure when ((\a expression) throws).
    170. * @param expression An expression.
    171. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    172. */
    173. #define XCTAssertNoThrow(expression, ...) \
    174.     _XCTPrimitiveAssertNoThrow(self, expression, @#expression, __VA_ARGS__)

    175. /*!
    176. * @define XCTAssertNoThrowSpecific(expression, exception_class, ...)
    177. * Generates a failure when ((\a expression) throws \a exception_class).
    178. * @param expression An expression.
    179. * @param exception_class The class of the exception. Must be NSException, or a subclass of NSException.
    180. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    181. */
    182. #define XCTAssertNoThrowSpecific(expression, exception_class, ...) \
    183.     _XCTPrimitiveAssertNoThrowSpecific(self, expression, @#expression, exception_class, __VA_ARGS__)

    184. /*!
    185. * @define XCTAssertNoThrowSpecificNamed(expression, exception_class, exception_name, ...)
    186. * Generates a failure when ((\a expression) throws \a exception_class with \a exception_name).
    187. * @param expression An expression.
    188. * @param exception_class The class of the exception. Must be NSException, or a subclass of NSException.
    189. * @param exception_name The name of the exception.
    190. * @param ... An optional supplementary description of the failure. A literal NSString, optionally with string format specifiers. This parameter can be completely omitted.
    191. */
    192. #define XCTAssertNoThrowSpecificNamed(expression, exception_class, exception_name, ...) \
    193.     _XCTPrimitiveAssertNoThrowSpecificNamed(self, expression, @#expression, exception_class, exception_name, __VA_ARGS__)
    复制代码
    经验小结
      XCTestCase 类和其他类一样,你可以定义基类,这里面封装一些常用的方法。
    1. // HCTTestCase.h
    2. #import <XCTest/XCTest.h>

    3. NS_ASSUME_NONNULL_BEGIN

    4. [url=home.php?mod=space&uid=10061]@interface[/url] HCTTestCase : XCTestCase

    5. @property (nonatomic, assign) NSTimeInterval networkTimeout;


    6. /**
    7. 用一个默认时间设置异步测试 XCTestExpectation 的超时处理
    8. */
    9. - (void)waitForExpectationsWithCommonTimeout;

    10. /**
    11. 用一个默认时间设置异步测试的

    12. @param handler 超时的处理逻辑
    13. */
    14. - (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler __nullable)handler;


    15. /**
    16. 生成 Crash 类型的 meta 数据

    17. [url=home.php?mod=space&uid=26358]@return[/url] meta 类型的字典
    18. */
    19. - (NSDictionary *)generateCrashMetaDataFromReport;

    20. @end

    21. NS_ASSUME_NONNULL_END

    22. // HCTTestCase.m
    23. #import "HCTTestCase.h"
    24. #import ...

    25. @implementation HCTTestCase

    26. #pragma mark - life cycle

    27. - (void)setUp
    28. {
    29.     [super setUp];
    30.     self.networkTimeout = 20.0;
    31.     // 1. 设置平台信息
    32.     [self setupAppProfile];
    33.     // 2. 设置 Mget 配置
    34.     [[TITrinityInitManager sharedInstance] setup];
    35.     // ....
    36.     // 3. 设置 HermesClient
    37.     [[HermesClient sharedInstance] setup];
    38. }

    39. - (void)tearDown
    40. {
    41.     [super tearDown];
    42. }


    43. #pragma mark - public Method

    44. - (void)waitForExpectationsWithCommonTimeout
    45. {
    46.     [self waitForExpectationsWithCommonTimeoutUsingHandler:nil];
    47. }

    48. - (void)waitForExpectationsWithCommonTimeoutUsingHandler:(XCWaitCompletionHandler __nullable)handler
    49. {
    50.     [self waitForExpectationsWithTimeout:self.networkTimeout handler:handler];
    51. }


    52. - (NSDictionary *)generateCrashMetaDataFromReport
    53. {
    54.     NSMutableDictionary *metaDictionary = [NSMutableDictionary dictionary];
    55.     NSDate *crashTime = [NSDate date];
    56.     metaDictionary[@"MONITOR_TYPE"] = @"appCrash";
    57.     // ...
    58.     metaDictionary[@"USER_CRASH_DATE"] = @([crashTime timeIntervalSince1970] * 1000);
    59.     return [metaDictionary copy];
    60. }


    61. #pragma mark - private method

    62. - (void)setupAppProfile
    63. {
    64.     [[CMAppProfile sharedInstance] setMPlatform:@"70"];
    65.     // ...
    66. }

    67. @end
    复制代码
    上述说的基本是开发规范相关。测试方法内部如果调用了其他类的方法,则在测试方法内部必须 Mock 一个外部对象,限制好返回值等。
      在 XCTest 内难以使用 mock 或 stub,这些是测试中非常常见且重要的功能。

      例子
      这里举个例子,是测试一个数据库操作类 HCTDatabase,代码只放某个方法的测试代码。
    1. - (void)testRemoveLatestRecordsByCount
    2. {
    3.     XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库删除最新数据功能"];
    4.     // 1. 先清空数据表
    5.     [dbInstance removeAllLogsInTableType:HCTLogTableTypeMeta];
    6.     // 2. 再插入一批数据
    7.     NSMutableArray *insertModels = [NSMutableArray array];
    8.     NSMutableArray *reportIDS = [NSMutableArray array];
    9.    
    10.     for (NSInteger index = 1; index <= 100; index++) {
    11.         HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
    12.         model.log_id = index;
    13.         // ...
    14.         if (index > 90 && index <= 100) {
    15.             [reportIDS addObject:model.report_id];
    16.         }
    17.         [insertModels addObject:model];
    18.     }
    19.     [dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
    20.    
    21.     // 3. 将早期的数据删除掉(id > 90 && id <= 100)
    22.     [dbInstance removeLatestRecordsByCount:10 inTableType:HCTLogTableTypeMeta];
    23.    
    24.     // 4. 拿到当前的前10条数据和之前存起来的前10条 id 做比较。再判断当前表中的总记录条数是否等于 90
    25.     [dbInstance getLatestRecoreds:10 inTableType:HCTLogTableTypeMeta completion:^(NSArray<HCTLogModel *> * _Nonnull records) {
    26.         NSArray<HCTLogModel *> *latestRTentRecords = records;
    27.         
    28.         [dbInstance getOldestRecoreds:100 inTableType:HCTLogTableTypeMeta completion:^(NSArray<HCTLogModel *> * _Nonnull records) {
    29.             NSArray<HCTLogModel *> *currentRecords = records;
    30.             
    31.             __block BOOL isEarlyData = NO;
    32.             [latestRTentRecords enumerateObjectsUsingBlock:^(HCTLogModel * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    33.                 if ([reportIDS containsObject:obj.report_id]) {
    34.                     isEarlyData = YES;
    35.                 }
    36.             }];
    37.             
    38.             XCTAssert(!isEarlyData && currentRecords.count == 90, @"***Database「删除最新n条数据」功能:异常");
    39.             [exception fulfill];
    40.         }];
    41.         
    42.     }];
    43.     [self waitForExpectationsWithCommonTimeout];
    44. }
    复制代码
    3. 测试框架
      1)Kiwi
      BDD 框架里的 Kiwi 可圈可点。使用 CocoaPods 引入 pod 'Kiwi'。看下面的例子。
      被测类(Planck 项目是一个基于 WebView 的 SDK,根据业务场景,发现针对 WebView 的大部分功能定制都是基于 WebView 的生命周期内发生的,所以参考 NodeJS 的中间件思想,设计了基于生命周期的 WebView 中间件)。
    1. #import <Foundation/Foundation.h>

    2. @interface TPKTrustListHelper : NSObject

    3. +(void)fetchRemoteTrustList;

    4. +(BOOL)isHostInTrustlist:(NSString *)scheme;

    5. +(NSArray *)trustList;

    6. @end
    复制代码
    测试类:
    1. SPEC_BEGIN(TPKTrustListHelperTest)
    2. describe(@"Middleware Wrapper", ^{
    3.    
    4.     context(@"when get trustlist", ^{
    5.         it(@"should get a array of string",^{
    6.             NSArray *array = [TPKTrustListHelper trustList];
    7.             [[array shouldNot] beNil];
    8.             NSString *first = [array firstObject];
    9.             [[first shouldNot] beNil];
    10.             [[NSStringFromClass([first class]) should] equal:@"__NSCFString"];
    11.         });
    12.     });
    13.    
    14.     context(@"when check a string wether contained in trustlist ", ^{
    15.         it(@"first string should contained in trustlist",^{
    16.             NSArray *array = [TPKTrustListHelper trustList];
    17.             NSString *first = [array firstObject];
    18.             [[theValue([TPKTrustListHelper isHostInTrustlist:first]) should] equal:@(YES)];
    19.         });
    20.     });
    21. });
    22. SPEC_END
    复制代码
    例子包含 Kiwi 的最基础元素。SPEC_BEGIN 和 SPEC_END 表示测试类;describe 描述需要被测试的类;context 表示一个测试场景,也就是 Given->When->Then 里的 Given;it 表示要测试的内容,也就是也就是 Given->When->Then 里的 When 和 Then。1个 describe 下可以包含多个 context,1个 context 下可以包含多个 it。
      Kiwi 的使用分为:Specs、 Expectations 、 Mocks and Stubs 、Asynchronous Testing 四部分。
      it 里面的代码块是真正的测试代码,使用链式调用的方式,简单上手。
      测试领域中 Mock 和 Stub 非常重要。Mock 模拟对象可以降低对象之间的依赖,模拟出一个纯净的测试环境(类似初中物理课上“控制变量法”的思想)。Kiwi 也支持的非常好,可以模拟对象、模拟空对象、模拟遵循协议的对象等等,点击  Mocks and Stubs 查看。Stub 存根可以控制某个方法的返回值,这对于方法内调用别的对象的方法返回值很有帮助。减少对于外部的依赖,单一测试当前行为是否符合预期。
      针对异步测试,XCTest 则需要创建一个 XCTestExpectation 对象,在异步实现里面调用该对象的 fulfill 方法,最后设置最大等待时间和完成的回调 - (void)waitForExpectationsWithTimeoutNSTimeInterval)timeout handlernullable XCWaitCompletionHandler)handler; 如下例子:
    1. XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库插入功能"];
    2.     [dbInstance removeAllLogsInTableType:HCTLogTableTypeMeta];
    3.     NSMutableArray *insertModels = [NSMutableArray array];
    4.     for (NSInteger index = 1; index <= 10000; index++) {
    5.         HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
    6.         model.log_id = index;
    7.      // 。。。
    8.         [insertModels addObject:model];
    9.     }
    10.     [dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
    11.     [dbInstance recordsCountInTableType:HCTLogTableTypeMeta completion:^(NSInteger count) {
    12.         XCTAssert(count == insertModels.count, @"**Database「数据增加」功能:异常");
    13.         [exception fulfill];
    14.     }];
    15.     [self waitForExpectationsWithCommonTimeout];
    复制代码
    2)expecta、Specta
      expecta 和 Specta 都出自 orta 之手,他也是 Cocoapods 的开发者之一。太牛逼了,工程化、质量保证领域的大佬。
      Specta 是一个轻量级的 BDD 测试框架,采用 DSL 模式,让测试更接近于自然语言,因此更易读。
      特点:
      易于集成到项目中。在 Xcode 中勾选 Include Unit Tests ,和 XCTest 搭配使用。
      语法很规范,对比 Kiwi 和 Specta 的文档,发现很多东西都是相同的,也就是很规范,所以学习成本低、后期迁移到其他框架很平滑。
      Expecta 是一个匹配(断言)框架,相比 Xcode 的断言 XCAssert,Excepta 提供更加丰富的断言。
      特点:
      Eepecta 没有数据类型限制,比如 1,并不关心是 NSInteger 还是 CGFloat链式编程,写起来很舒服。
      反向匹配,很灵活。断言匹配用 except(...).to.equal(...),断言不匹配则使用 .notTo 或者 .toNot
      延时匹配,可以在链式表达式后加入 .will、.willNot、.after(interval) 等。

      4.小结
      Xcode 自带的 XCTestCase 比较适合 TDD,不影响源代码,系统独立且不影响 App 包大小。适合简单场景下的测试。且每个函数在最左侧又个测试按钮,点击后可以单独测试某个函数。
      Kiwi 是一个强大的 BDD 框架,适合稍微复杂写的项目,写法舒服、功能强大,模拟对象、存根语法、异步测试等满足几乎所有的测试场景。不能和 XCTest 继承。
      Specta 也是一个 BDD 框架,基于 XCTest 开发,可以和 XCTest 模版集合使用。相比 Kiwi,Specta 轻量一些。开发中一般搭配 Excepta 使用。如果需要使用 Mock 和 Stud 可以搭配 OCMock。
      Excepta 是一个匹配框架,比 XCTest 的断言则更加全面一些。
      没办法说哪个最好、最合理,根据项目需求选择合适的组合。











    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-24 18:16 , Processed in 0.068835 second(s), 25 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表