51Testing软件测试论坛

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

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 1041|回复: 0
打印 上一主题 下一主题

[转贴] 开发人员谈测试:如何写出简洁又规范的单元测试

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

    连续签到: 3 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2022-1-17 13:06:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
     我们之前谈到过要让开发人员认识到测试的重要性并了解测试,本文的主要重点是针对日常开发阶段工程师可以做的事情,也就是单元测试而展开。
      编写功能、业务代码的时候一般会遵循kiss原则 ,所以类、方法、函数往往不会太大,分层设计越好、职责越单一、耦合度越低的代码越适合做单元测试,单元测试也倒逼开发过程中代码分层、解耦。
      可能某个功能的实现代码有30行,测试代码有50行。单元测试的代码如何编写才更合理、整洁、规范呢?

      编码分模块展开
      先贴一段代码:
    1. -  (void)testInsertDataInOneSpecifiedTable
    2. {
    3.     XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库插入功能"];
    4.     // given
    5.     [dbInstance removeAllLogsInTableType:HCTLogTableTypeMeta];
    6.     NSMutableArray *insertModels = [NSMutableArray array];
    7.     for (NSInteger index = 1; index <= 10000; index++) {
    8.         HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
    9.         model.log_id = index;
    10.         // ...
    11.         [insertModels addObject:model];
    12.     }
    13.     // when
    14.     [dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
    15.      // then
    16.     [dbInstance recordsCountInTableType:HCTLogTableTypeMeta completion:^(NSInteger count) {
    17.         XCTAssert(count == insertModels.count, @"「数据增加」功能:异常");
    18.         [exception fulfill];
    19.     }];
    20.     [self waitForExpectationsWithCommonTimeout];
    21. }
    复制代码
    可以看到这个方法的名称为 testInsertDataInOneSpecifiedTable,这段代码做的事情通过函数名可以看出来:测试插入数据到某个特定的表。
      这个测试用例分为3部分:
      测试环境所需的先决条件准备;
      调用所要测试的某个方法、函数;
      验证输出和行为是否符合预期。
      其实,每个测试用例的编写也要按照该种方式去组织代码。步骤分为3个阶段:Given->When->Then。
      所以单元测试的代码规范也就出来了。此外单元测试代码规范统一后,每个人的测试代码都按照这个标准展开,那其他人的阅读起来就更加容易、方便。
      按照这3个步骤去阅读、理解测试代码,就可以清晰明了的知道在做什么。

      一个测试用例只测试一个分支
      我们写的代码有很多语句组成,有各种逻辑判断、分支(if...else、swicth)等等,因此一个程序从一个单一入口进去,过程可能产生n个不同的分支,但是程序的出口总是一个。
      所以由于这样的特性,我们的测试也需要针对这样的现状走完尽可能多的分支。相应的指标叫做「分支覆盖率」。
      假如某个方法内部有 if...else...,我们在测试的时候尽量将每种情况写成一个单独的测试用例,单独的输入、输出,判断是否符合预期。这样每个case都单一的测试某个分支,可读性也很高。
      比如对下面的函数做单元测试,测试用例设计如下:
    1. - (void)shouldIEatSomething
    2. {
    3.    BOOL shouldEat = [self getAteWeight] < self.dailyFoodSupport;
    4.    if (shouldEat) {
    5.      [self eatSomemuchFood];
    6.    } else {
    7.      [self doSomeExercise];
    8.    }
    9. }
    复制代码
    1. - (void)testShouldIEatSomethingWhenHungry
    2. {
    3.    // ....
    4. }

    5. - (void)testShouldIEatSomethingWhenFull
    6. {
    7.   // ...
    8. }
    复制代码
      明确标识被测试类
      这条主要站在团队合作和代码可读性角度出发来说明。
      写过单元测试的人都知道,可能某个函数本来就10行代码,可是为了测试它,测试代码写了30行。
      一个方法这样写问题不大,多看看就看明白是在测试哪个类的哪个方法。
      可是当这个类本身就很大,测试代码很大的情况下,不管是作者自身还是多年后负责维护的其他同事,看这个代码阅读成本会很大,需要先看测试文件名“代码类名+Test”才知道是测试的是哪个类,看测试方法名“test+ 方法名”才知道是测试的是哪个方法。
      这样的代码可读性很差,所以应该为当前的测试对象特殊标记,这样测试代码可读性越强、阅读成本越低。
      比如定义局部变量 _sut 用来标记当前被测试类(sut——System under  Test,软件测试领域有个词叫做被测系统,用来表示正在被测试的系统)。
    1. #import <XCTest/XCTest.h>
    2. #import "HCTLogPayloadModel.h"

    3. [url=home.php?mod=space&uid=10061]@interface[/url] HCTLogPayloadModelTest : HCTTestCase
    4. {
    5.     HCTLogPayloadModel *_sut;
    6. }

    7. @end

    8. @implementation HCTLogPayloadModelTest

    9. - (void)setUp
    10. {
    11.     [super setUp];
    12.     HCTLogPayloadModel *model = [[HCTLogPayloadModel alloc] init];
    13.     model.log_id = 1;
    14.     // ...
    15.     _sut = model;
    16. }

    17. - (void)tearDown
    18. {
    19.     _sut = nil;
    20.     [super tearDown];
    21. }

    22. - (void)testGetDictionary
    23. {
    24.     NSDictionary *payloadDictionary = [_sut getDictionary];
    25.     XCTAssert([(NSString *)payloadDictionary[@"report_id"] isEqualToString:@"001"] &&
    26.               [payloadDictionary[@"size"] integerValue] == 102 &&
    27.               [(NSString *)payloadDictionary[@"meta"] containsString:@"meiying"],
    28.               @"HCTLogPayloadModel 的 「getDictionary」功能异常");
    29. }

    30. @end
    复制代码
    使用分类来暴露私有方法、私有变量
      某些场景下写的测试方法内部可能需要调用被测对象的私有方法,也可能需要访问被测对象的某个私有属性。
      但是测试类里面是访问不到被测类的私有属性和私有方法的,借助于Category可以实现这样的需求。
      为测试类添加一个分类,后缀名为UnitTest,如下所示。
      HermesClient类有私有属性@property (nonatomic, strong) NSString *name;,私有方法 - (void)hello。
      为了在测试用例中访问私有属性和私有方法,写了如下分类:
    1. // HermesClientTest.m

    2. @interface HermesClient (UnitTest)

    3. - (NSString *)name;

    4. - (void)hello;

    5. @end
    6.   
    7. @implementation HermesClientTest

    8. - (void)testPrivatePropertyAndMethod
    9. {
    10.     NSLog(@"%@",[HermesClient sharedInstance].name);
    11.     [[HermesClient sharedInstance] hello];
    12. }
    13. @end
    复制代码



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

    使用道具 举报

    本版积分规则

    关闭

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

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

    GMT+8, 2024-5-2 17:55 , Processed in 0.062648 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

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