网站首页 > 编程文章 正文
编写功能、业务代码的时候一般会遵循kiss原则 ,所以类、方法、函数往往不会太大,分层设计越好、职责越单一、耦合度越低的代码越适合做单元测试,单元测试也倒逼开发过程中代码分层、解耦。
可能某个功能的实现代码有30行,测试代码有50行。单元测试的代码如何编写才更合理、整洁、规范呢?
编码分模块展开
先贴一段代码:
- (void)testInsertDataInOneSpecifiedTable
{
XCTestExpectation *exception = [self expectationWithDescription:@"测试数据库插入功能"];
// given
[dbInstance removeAllLogsInTableType:HCTLogTableTypeMeta];
NSMutableArray *insertModels = [NSMutableArray array];
for (NSInteger index = 1; index <= 10000; index++) {
HCTLogMetaModel *model = [[HCTLogMetaModel alloc] init];
model.log_id = index;
// ...
[insertModels addObject:model];
}
// when
[dbInstance add:insertModels inTableType:HCTLogTableTypeMeta];
// then
[dbInstance recordsCountInTableType:HCTLogTableTypeMeta completion:^(NSInteger count) {
XCTAssert(count == insertModels.count, @"「数据增加」功能:异常");
[exception fulfill];
}];
[self waitForExpectationsWithCommonTimeout];
}
(左右滑动查看完整代码)
可以看到这个方法的名称为 testInsertDataInOneSpecifiedTable,这段代码做的事情通过函数名可以看出来:测试插入数据到某个特定的表。
这个测试用例分为3部分:
- 测试环境所需的先决条件准备;
- 调用所要测试的某个方法、函数;
- 验证输出和行为是否符合预期。
其实,每个测试用例的编写也要按照该种方式去组织代码。步骤分为3个阶段:Given->When->Then。
所以单元测试的代码规范也就出来了。此外单元测试代码规范统一后,每个人的测试代码都按照这个标准展开,那其他人的阅读起来就更加容易、方便。
按照这3个步骤去阅读、理解测试代码,就可以清晰明了的知道在做什么。
一个测试用例只测试一个分支
我们写的代码有很多语句组成,有各种逻辑判断、分支(if...else、swicth)等等,因此一个程序从一个单一入口进去,过程可能产生n个不同的分支,但是程序的出口总是一个。
所以由于这样的特性,我们的测试也需要针对这样的现状走完尽可能多的分支。相应的指标叫做「分支覆盖率」。
假如某个方法内部有 if...else...,我们在测试的时候尽量将每种情况写成一个单独的测试用例,单独的输入、输出,判断是否符合预期。这样每个case都单一的测试某个分支,可读性也很高。
比如对下面的函数做单元测试,测试用例设计如下:
- (void)shouldIEatSomething
{
BOOL shouldEat = [self getAteWeight] < self.dailyFoodSupport;
if (shouldEat) {
[self eatSomemuchFood];
} else {
[self doSomeExercise];
}
}
(左右滑动查看完整代码)
- (void)testShouldIEatSomethingWhenHungry
{
// ....
}
- (void)testShouldIEatSomethingWhenFull
{
// ...
}
(左右滑动查看完整代码)
明确标识被测试类
这条主要站在团队合作和代码可读性角度出发来说明。
写过单元测试的人都知道,可能某个函数本来就10行代码,可是为了测试它,测试代码写了30行。
一个方法这样写问题不大,多看看就看明白是在测试哪个类的哪个方法。
可是当这个类本身就很大,测试代码很大的情况下,不管是作者自身还是多年后负责维护的其他同事,看这个代码阅读成本会很大,需要先看测试文件名“代码类名+Test”才知道是测试的是哪个类,看测试方法名“test+ 方法名”才知道是测试的是哪个方法。
这样的代码可读性很差,所以应该为当前的测试对象特殊标记,这样测试代码可读性越强、阅读成本越低。
比如定义局部变量 _sut 用来标记当前被测试类(sut——System under Test,软件测试领域有个词叫做被测系统,用来表示正在被测试的系统)。
#import <XCTest/XCTest.h>
#import "HCTLogPayloadModel.h"
@interface HCTLogPayloadModelTest : HCTTestCase
{
HCTLogPayloadModel *_sut;
}
@end
@implementation HCTLogPayloadModelTest
- (void)setUp
{
[super setUp];
HCTLogPayloadModel *model = [[HCTLogPayloadModel alloc] init];
model.log_id = 1;
// ...
_sut = model;
}
- (void)tearDown
{
_sut = nil;
[super tearDown];
}
- (void)testGetDictionary
{
NSDictionary *payloadDictionary = [_sut getDictionary];
XCTAssert([(NSString *)payloadDictionary[@"report_id"] isEqualToString:@"001"] &&
[payloadDictionary[@"size"] integerValue] == 102 &&
[(NSString *)payloadDictionary[@"meta"] containsString:@"meiying"],
@"HCTLogPayloadModel 的 「getDictionary」功能异常");
}
@end
(左右滑动查看完整代码)
使用分类
来暴露私有方法、私有变量
某些场景下写的测试方法内部可能需要调用被测对象的私有方法,也可能需要访问被测对象的某个私有属性。
但是测试类里面是访问不到被测类的私有属性和私有方法的,借助于Category可以实现这样的需求。
为测试类添加一个分类,后缀名为UnitTest,如下所示。
HermesClient类有私有属性@property (nonatomic, strong) NSString *name;,私有方法 - (void)hello。
为了在测试用例中访问私有属性和私有方法,写了如下分类:
// HermesClientTest.m
@interface HermesClient (UnitTest)
- (NSString *)name;
- (void)hello;
@end
@implementation HermesClientTest
- (void)testPrivatePropertyAndMethod
{
NSLog(@"%@",[HermesClient sharedInstance].name);
[[HermesClient sharedInstance] hello];
}
@end
猜你喜欢
- 2025-06-12 从零开始搭建AI网站(6):如何使用响应式编程
- 2025-06-12 Windows下Ollama安装目录迁移到D盘
- 2025-06-12 RocketMQ的偏移量更新原理(rocketmq迁移)
- 2025-06-12 Seata源码—7.Seata TCC模式的事务处理
- 2025-06-12 Seata源码—4.全局事务拦截与开启事务处理二
- 2025-06-12 Seata源码—3.全局事务注解扫描器的初始化一
- 2025-06-12 Seata源码—6.Seata AT模式的数据源代理二
- 2025-06-12 Seata源码—6.Seata AT模式的数据源代理三
- 2025-06-12 写出更优雅的代码:搞懂 Python 协议与抽象基类的核心区别
- 2025-06-12 Seata源码—6.Seata AT模式的数据源代理一
你 发表评论:
欢迎- 06-13边缘计算网关如何实现系统高效运维?
- 06-13虚拟专用网络VPN连接配置
- 06-13WIN10/11下配置VPN(解决L2TP/IPsec无法连接的问题)
- 06-13公共网络上的私有安全通道——VPN
- 06-13远程办公如何访问公司内网办公系统和内部资源?
- 06-13VBRAS场景测试方法—如何高效验证网络设备的性能与稳定性
- 06-13Win10系统如何使用VPN远程办公
- 06-13iPhone轻松实现远程访问公司局域网电脑上的共享文件
- 最近发表
- 标签列表
-
- spire.doc (70)
- instanceclient (62)
- system.data.oracleclient (61)
- 按键小精灵源码提取 (66)
- pyqt5designer教程 (65)
- 联想刷bios工具 (66)
- c#源码 (64)
- graphics.h头文件 (62)
- mysqldump下载 (66)
- sqljdbc4.jar下载 (56)
- libmp3lame (60)
- maven3.3.9 (63)
- 二调符号库 (57)
- git.exe下载 (68)
- diskgenius_winpe (72)
- pythoncrc16 (57)
- solidworks宏文件下载 (59)
- qt帮助文档中文版 (73)
- satacontroller (66)
- hgcad (64)
- bootimg.exe (69)
- android-gif-drawable (62)
- axure9元件库免费下载 (57)
- libmysqlclient.so.18 (58)
- springbootdemo (64)
本文暂时没有评论,来添加一个吧(●'◡'●)