iOS - 单元测试

简单使用单元测试

对方法引用AFN框架的单元测试

不写单元测试的程序员是不合格的,为了让自己成为一名合格的程序员,学习如何写单元测试是很有必要的,这里以Xcode集成的测试框架XCTest为例。

XCTest基础用法

默认的测试类继承自XCTestCase,当然也可以自定义测试类,添加一些公共的辅助方法,所有的测试方法都必须以test开头,且不能有参数,不然不会识别为测试方法。

@interface__Tests:XCTestCase@end@implementation__Tests// 在每一个测试用例开始前调用,用来初始化相关数据- (void)setUp {    [supersetUp];// Put setup code here. This method is called before the invocation of each test method in the class.}// 在测试用例完成后调用,可以用来释放变量等结尾操作- (void)tearDown {// Put teardown code here. This method is called after the invocation of each test method in the class.[supertearDown];}// 测试方法- (void)testExample {// This is an example of a functional test case.// Use XCTAssert and related functions to verify your tests produce the correct results.}// 性能测试方法,通过测试block中方法执行的时间,比对设定的标准值和偏差觉得是否可以通过测试- (void)testPerformanceExample {// This is an example of a performance test case.[selfmeasureBlock:^{// Put the code you want to measure the time of here.}];}/// 我自定义的 针对 Person 类的测试方法- (void)testPerson{}@end

断言

XCTest的断言具体可查阅XCTestAssertions.h文件,这里还是做个简单的总结

//通用断言XCTAssert(expression,format...)//常用断言:XCTAssertTrue(expression,format...)XCTAssertFalse(expression,format...)XCTAssertEqual(expression1, expression2,format...)XCTAssertNotEqual(expression1, expression2,format...)XCTAssertEqualWithAccuracy(expression1, expression2, accuracy,format...)XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy,format...)XCTAssertNil(expression,format...)XCTAssertNotNil(expression,format...)

举个例子

- (void)testExample {//设置变量和设置预期值NSUIntegera =10;NSUIntegerb =15;NSUIntegerexpected =24;//执行方法得到实际值NSUIntegeractual = [selfadd:a b:b];//断言判定实际值和预期是否符合XCTAssertEqual(expected, actual,@"add方法错误!");}-(NSUInteger)add:(NSUInteger)a b:(NSUInteger)b{returna+b;}

从这也能看出一个测试用例比较规范的写法,1:定义变量和预期,2:执行方法得到实际值,3:断言

当然在有些特殊情况下直接使用这些断言,会让代码看起来很臃肿,使用时可以对其进行一定的封装一下:

#define ml_ssertTrue(expr)XCTAssertTrue((expr), @"设置指定的字符串,同下")#define ml_assertFalse(expr)XCTAssertFalse((expr), @"")#define ml_assertNil(a1)XCTAssertNil((a1), @"")#define ml_assertNotNil(a1)XCTAssertNotNil((a1), @"")#define ml_assertEqual(a1, a2)XCTAssertEqual((a1),(a2), @"")#define ml_assertEqualObjects(a1, a2)XCTAssertEqualObjects((a1),(a2), @"")#define ml_assertNotEqual(a1, a2)XCTAssertNotEqual((a1),(a2), @"")#define ml_assertNotEqualObjects(a1, a2)XCTAssertNotEqualObjects((a1),(a2), @"")#define ml_assertAccuracy(a1, a2, acc)XCTAssertEqualWithAccuracy((a1),(a2),(acc))

简单实用准备工作

795875-a207ce29f12fa7f2.png.jpeg

如果是之前创建的项目,里面没有勾选,可以自己创建:

795875-9e3ebd787a699757.png.jpeg

进入到这个类,

setUp是每个测试方法调用执行,

tearDown是每个测试方法调用执行。

testExample是测试方法,和我们新建的没有差别。

不过测试方法必须testXXX的格式,且不能有参数,不然不会识别为测试方法。测试方法的执行顺序是字典序排序。

按快捷键Command + U进行单元测试,这个快捷键是全部测试。

795875-362afa43ea505bb6.png.jpeg

一、简单使用单元测试

1、创建一个Person类,里面有一个test1类方法

2、顺便勾选右边的测试单元按钮,为了对这个.m文件的数据测试

795875-192f3a273594f9e7.png.jpeg

3、如果项目比较小,就可以直接在项目刚创建的tests方法里测试,如果项目比较大的话,就创建一个单独的tests,例如:PersonTets:

795875-89721ddcf3d48789.png.jpeg

4、创建一个测试方法:testPerson:

795875-cbeeb6ba7a19c6ef.png.jpeg

上面也说了:测试方法必须testXXX的格式,且不能有参数,不然不会识别为测试方法

如果前面不出现小菱形,可以Build一下

5、点击小菱形图标,出现绿色说明成功:

795875-fdefc42918aaccb4.png.jpeg

6、有时候需要验证值的话,就使用 XCTAssert(<#expression, ...#>),或者其他的断言方法测试结果;

二、对方法引用AFN框架的单元测试

1、测试带有网络请求的方法,例如:

795875-12be468144d6e31a.png.jpeg

2、 按照之前的那样 [Person test2]的话,点击小菱角会出现找到AFN框架:

795875-d4d646284ee3dd06.png.jpeg

3、需要做一些配置

3.1、复制Target(App) - Build Setting - Header Search Paths 的路径。

795875-27159a355bc9a14f.png.jpeg

3.2、粘贴到Target(UnitTests) - Build Setting - Header - Search Paths里。

3.3、复制Target(App) - Build Setting - User-Defined - PODS_ROOT整条。

3.4、到Target(UnitTests) - Build Setting - User-Defined新建一条PODS_ROOT。

795875-601bbddba0354ed1.png.jpeg

4、OK了!

三、期望

期望实际上是异步测试,当测试异步方法时,因为结果并不是立刻获得,所以我们可以设置一个期望,期望是有时间限定的的,fulfill表示满足期望。

例如

- (void)testAsynExample {// 1. 创建期望XCTestExpectation *exp = [selfexpectationWithDescription:@"这里可以是操作出错的原因描述。。。"];NSOperationQueue*queue = [[NSOperationQueuealloc]init];    [queue addOperationWithBlock:^{//模拟:这个异步操作需要2秒后才能获取结果,比如一个异步网络请求sleep(2);//模拟:获取的异步操作后,获取结果,判断异步方法的结果是否正确XCTAssertEqual(@"a",@"a");//2. 如果断言没问题,就调用fulfill宣布测试满足[exp fulfill];    }];//3. 设置延迟多少秒后,如果没有满足测试条件就报错[selfwaitForExpectationsWithTimeout:3handler:^(NSError* _Nullable error) {if(error) {NSLog(@"Timeout Error: %@", error);        }    }];}

这个测试肯定是通过的,因为设置延迟为3秒,而异步操作2秒就除了一个正确的结果,并宣布了条件满足 [exp fulfill],但是当我们把延迟改成1秒,这个测试用例就不会成功,错误原因是 expectationWithDescription:@"这里可以是操作出错的原因描述。。。

异步测试除了使用expectationWithDescription

以外,还可以使用expectationForPredicate和expectationForNotification

,

掌握expectationWithDescription

即可,后两者者仅仅了解

下面这个例子使用expectationForPredicate 测试方法,代码来自于AFNetworking,用于测试backgroundImageForState方法

- (void)testThatBackgroundImageChanges {    XCTAssertNil([self.buttonbackgroundImageForState:UIControlStateNormal]);NSPredicate*predicate = [NSPredicatepredicateWithBlock:^BOOL(UIButton* _Nonnull button,NSDictionary * _Nullable bindings) {return[button backgroundImageForState:UIControlStateNormal] !=nil;    }];    [selfexpectationForPredicate:predicate              evaluatedWithObject:self.buttonhandler:nil];    [selfwaitForExpectationsWithTimeout:20handler:nil];}

利用谓词计算,button是否正确的获得了backgroundImage,如果正确20秒内正确获得则通过测试,否则失败。

expectationForNotification 方法 ,该方法监听一个通知,如果在规定时间内正确收到通知则测试通过。

- (void)testAsynExample1 {    [selfexpectationForNotification:(@"监听通知的名称xxx") object:nilhandler:nil];    [[NSNotificationCenterdefaultCenter]postNotificationName:@"监听通知的名称xxx"object:nil];//设置延迟多少秒后,如果没有满足测试条件就报错[selfwaitForExpectationsWithTimeout:3handler:nil];}

这个例子也可以用expectationWithDescription实现,只是多些很多代码而已,但是这个可以帮助你更好的理解expectationForNotification 方法和 expectationWithDescription 的区别。同理,expectationForPredicate方法也可以使用expectationWithDescription实现。

functestAsynExample1(){letexpectation = expectationWithDescription("监听通知的名称xxx")letsub =NSNotificationCenter.defaultCenter().addObserverForName("监听通知的名称xxx", object:nil, queue:nil) { (not) ->Voidinexpectation.fulfill()    }NSNotificationCenter.defaultCenter().postNotificationName("监听通知的名称xxx", object:nil)    waitForExpectationsWithTimeout(1, handler:nil)NSNotificationCenter.defaultCenter().removeObserver(sub)}

四、期望实战:

-(void)testRequest{# 创建期望XCTestExpectation *expectation =[selfexpectationWithDescription:@"没有满足期望"];    AFHTTPSessionManager *sessionManager = [AFHTTPSessionManager manager];    sessionManager.responseSerializer= [AFHTTPResponseSerializer serializer];    [sessionManager GET:@"http://www.weather.com.cn/adat/sk/101110101.html"parameters:nilsuccess:^(NSURLSessionDataTask* _Nonnull task,id_Nullable responseObject) {NSLog(@"responseObject:%@", [NSJSONSerializationJSONObjectWithData:responseObject options:1error:nil]);# 不为nil 通过,XCTAssertNotNil(responseObject,@"返回出错");# 满意[expectation fulfill];    } failure:^(NSURLSessionDataTask* _Nullable task,NSError* _Nonnull error) {# 为nil 通过,XCTAssertNil(error,@"请求出错");    }];# 设置5秒的超时时间[selfwaitForExpectationsWithTimeout:3.0handler:^(NSError*error) {if(error) {NSLog(@"Timeout Error: %@", error);        }    }];}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容

  • 程序员最讨厌的就是反复的修复查找BUG,,,,,特别是万恶的IE6,不过没办法,改修复还是修复处理,谁让我们干的是...
    虚幻的锈色阅读 334评论 0 1
  • 前言 每次线上都会有些崩溃记录在友盟,但是这些崩溃日志并不好查找崩溃的原因,我就在想有没有好一点的方法. 错误的异...
    Link913阅读 742评论 2 3
  • bug分类: 文案展示问题 数据展示问题 操作逻辑错误 崩溃问题 20号
    George_Luofz阅读 169评论 0 1
  • 戳花镇情感,关注最有情感深度的公众号 我一直声称我喜欢大叔型的男人。气质成熟,有品味,有一定的经济基础,为人处事拿...
    花镇阅读 2,327评论 0 0
  • 他是你的全世界,而我希望你的世界里能有自己的立锥之地。一个人最坚实的依靠从来不是男人的肩膀,而是自己的怀抱。 -1...
    慢小小阅读 457评论 1 2