索多玛120 mtime的120天百度云资源谁有

2066次阅读|
745次阅读|
3942次阅读|
8192次阅读|
6985次阅读|
7439次阅读|
5435次阅读|
6775次阅读|
2937次阅读|
9067次阅读|
XCode下的iOS单元测试
<img src="/cms/uploads/allimg/4-0-L.jpg"
height="120" alt="XCode下的iOS单元测试" />
94685次阅读|
点击量7963点击量6184点击量3752点击量3572点击量3483点击量2970点击量2637点击量2633点击量2242
吃水不忘挖井人,感谢勤劳的编辑团队
&2016 Chukong Technologies,Inc.
京公网安备89966,690 一月 独立访问用户
语言 & 开发
架构 & 设计
文化 & 方法
您目前处于:
iOS开发中的单元测试(一)
iOS开发中的单元测试(一)
日. 估计阅读时间:
欲知区块链、VR、TensorFlow等潮流技术和框架,请锁定
相关厂商内容
OCUnit是XCode 4.x集成的单元测试框架,OCUnit中的测试分为两类,一类称为Logic Tests,另一类称为Application Tests。Logic Tests更倾向于所谓的白盒测试,用于测试工程中较细节的逻辑;Application Tests更倾向于黑盒测试,或接口测试,用于测试直接与用户交互的接口。
& 添加单元测试
OCUnit是XCode集成的,所以其与工程的结合理应是最好的,添加到工程中的成本也理应最低。使用XCode创建新工程的流程中就有一个&Include Unit Tests&的选项(如图1),新的工程就会自动生成一个Logic Tests。
向已存在的工程中添加OCUnit Logic Tests也不复杂,只需要添加一个类型为:&Cocoa Touch Unit Testing Bundle&的Target即可(如图2)。
图2,向已存在的工程中添加OCUnit测试
向已有工程中添加一个测试Target时,XCode会自动生成一个Scheme,运行单元测试用例和Build原工程需要切换不同的Scheme。如果认为切换Scheme非常麻烦,也可以在添加Target之前,在&Manage Scheme&菜单中取消&Autocreate schemes&(如图3)。
图3,添加Target不创建Scheme
Application Tests要基于Logic Tests做一些修改。一般来说一个工程既需要Logic Tests也需要Application Tests,所以建议按照上述方法添加一个单独的Target,然后执行以下操作(如图4):
1. 在Build Settings中搜索&bundle loader&,设置为:$(BUILT_PRODUCTS_DIR)/APP_NAME.app/APP_NAME(APP_NAME是应用名)
2. 再搜索&test host&,设置为:$(BUNDLE_LOADER)
3. 在Build Phases-Target Dependencies中添加依赖,选择主程序Target
图4,添加一个Application Tests
& 创建测试用例
OCUnit的测试用例最常用的方法有三个
1. - (void)setUp:每个test方法执行前调用
2. - (void)tearDown:每个test方法执行后调用
3. - (void)testXXX:命名为XXX的测试方法
添加Target之时XCode已经自动创建了一个测试用例类:UnitTestDemoTests,其中UnitTestDemo是工程的名字,该类中已经包含了setUp,tearDown和testExample三个方法。
通过command+n,选择&Objective-C test case class&创建一个新的测试用例类(如图5)。通过XCode创建的测试用例类是一个继承自SenTestCase(OCUnit由开发,因此基类命名为SenTestCase)的空类,需要模仿UnitTestDemoTests编写测试方法。
图5,创建一个测试用例类
开发者可以自己实现无返回值,且命名规则为testXXX的实例方法,并使用框架提供的大量断言方法。
Logic Tests与Application Tests的区别主要在setUp方法,Logic Tests只需在setUp方法中初始化一些测试数据,而Application Tests需要在setUp方法中获取主应用的AppDelegate,供test方法调用。
值得注意的是,OCUnit的test bundle是侵入主应用的,因此在使用过程中要十分注意,不要让单元测试的资源覆盖主应用资源,造成诡异的Bug。
& 运行测试
由于OCUnit是集成在XCode中的框架,因此在XCode中运行也比较方便。切换到单元测试的scheme(如果与工程共用scheme则无需切换),Product-&Test(或直接使用快捷键command+u),框架会自动查找所有工程中SenTestCase的子类,运行其中全部命名类似testXXX的无返回值方法。
& 测试反馈
OCUnit的失败方法会通过Console和XCode Issues两个位置反馈,通过XCode Issues可以直接定位到出现错误的单元测试代码行。Issue的提示信息就是在单元测试断言方法中定义的description。
是一款Objective-C的测试框架,除了支持iOS工程还支持OSX的工程,但OSX不在本文的讨论范围。GHUnit不同于OCUnit,它提供了GUI界面来操作测试用例,而且也不区分Logic Tests和Application Tests。
& 添加单元测试
与集成进XCode的OCUnit相比,GHUnit的添加过程略显复杂。首先在上下载,当前的For iOS的最新版本是0.5.6,解压后是一个GHUnitIOS.framework的文件夹。
打开已经存在的工程,添加一个EmptyApplication Target,并在新Target中添加刚刚下载的GHUnitIOS.framework(如图6、7)。
图6,在新Target中添加GHUnitIOS.framework
在Build Phases中添加非官方框架并不会把框架文件拷贝到工程目录,而是只做一个链接,所以建议在添加之前先把框架拷贝到工程目录下。
图7,选择GHUnitIOS.framework
接下来用相同的方法添加框架依赖的其他库:&QuartzCore.framework&。
在Build Settings中搜索&linker flags&,设置Other Linker Flags - Debug - 添加一个支持全架构和全版本SDK的标示&-ObjC -all_load&(如图8)。
图8,设置linker flags
删除Tests Target中的AppDelegate(.h和.m一起删除)。修改main函数,支持GHUnitIOS,导入GHUnitIOSAppDelegate代替原来的AppDelegate,修改UIApplicationMain的参数(如图9)。
图9,修改main函数
至此已经完成了GHUnit的添加,选择新建Target同时创建的scheme,直接Build and Run即可在设备或Simulator中启动一个新的App(如图10),即该单元测试的App。
图10,单元测试App
& 创建测试用例
创建GHUnit测试用例与创建OCUnit测试用例相似。
新建一个Objective-C Class文件,继承自GHTestCase,在XCode生成的.h文件中不会导入GHUnit.h文件,需要开发者自行导入&#import &GHUnitIOS/GHUnit.h&&。
GHUnit框架提供断言方法比OCUnit更加丰富,开发用例也就可以做的更加细致,更有利查找/定位错误。
测试方法的命名规则与OCUnit一样,是以test开头的无返回值方法:- (void)testXXX。而常用的方法除了上述提到的setUp和tearDown,GHUnit还提供了setUpClass和tearDownClass两个方法,在该用例运行前和结束后调用。另外,刚刚提到GHUnit不区分Logic Tests和Application Tests,所以在setUp和tearDown方法中也就不存在设置的区分。
& 运行测试
运行GHUnit需要分两步,首先编译并安装单元测试App到设备或Simulator里(如图11),创建了两个用例,每个用例中分别有一个方法。
图11,两个用例的GHUnit App
在App中可以通过点击右上角的Run按钮运行全部用例,框架会查找所有以testXXX命名的无返回值方法,并执行。或点击TableView中的某个Cell运行单独的测试方法。
& 测试反馈
断言失败测试未通过的方法在App中会标记为红色,并给出每一个方法的运行时间。在Console中会打印出详细的出错信息,包括:异常类型,出错文件,位置,以及断言方法中指定的出错原因。更重要的是,出错时的程序堆栈内容(如图12)。
图12,未通过测试的方法,Console中的内容
GHUnit通过Console中的内容给开发者提供帮助,可以快速定位程序出错的位置,这一点比OCUnit做的要好。
GHUnit在安装上确实显得有些麻烦,无法跟集成在XCode里的OCUnit相比。 但从开发者的角度讲,我更喜欢GHUnit带来的体验,GUI的操作界面可以脱离IDE单独运行,支持运行单一测试方法和运行全部用例的,打印出错堆栈可以更快定位到问题所在。
本文简单介绍了两款框架的安装与入门,可以初步了解其各自特点,在接下来的文章中将会更加详细的介绍如何使用框架进行单元测试,以及框架中的一些高级功能。此外,后续还将向大家介绍另外的与这两款框架区别更加明显的单元测试框架。
作者简介:
(微博:),联合创始人,杭州iOS开发者沙龙发起人,资深iOS开发者。
感谢对本文的审校。
给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博()或者腾讯微博()关注我们,并与我们的编辑和其他读者朋友交流。
Author Contacted
告诉我们您的想法
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p
当有人回复此评论时请E-mail通知我
赞助商链接
InfoQ每周精要
通过个性化定制的新闻邮件、RSS Feeds和InfoQ业界邮件通知,保持您对感兴趣的社区内容的时刻关注。
架构 & 设计
文化 & 方法
<及所有内容,版权所有 &#169;
C4Media Inc.
服务器由 提供, 我们最信赖的ISP伙伴。
北京创新网媒广告有限公司
京ICP备号-7
注意:如果要修改您的邮箱,我们将会发送确认邮件到您原来的邮箱。
使用现有的公司名称
修改公司名称为:
公司性质:
使用现有的公司性质
修改公司性质为:
使用现有的公司规模
修改公司规模为:
使用现在的国家
使用现在的省份
Subscribe to our newsletter?
Subscribe to our industry email notices?
我们发现您在使用ad blocker。
我们理解您使用ad blocker的初衷,但为了保证InfoQ能够继续以免费方式为您服务,我们需要您的支持。InfoQ绝不会在未经您许可的情况下将您的数据提供给第三方。我们仅将其用于向读者发送相关广告内容。请您将InfoQ添加至白名单,感谢您的理解与支持。iOS开发实战:如何在ReactiveCocoa中编写单元测试?
发表于 15:20|
作者刘耀柱
摘要:在开发iOS应用时使用ReactiveCocoa的开发者可谓多数,但很多时候使用它之后,如何编写单元测试来验证程序是否正确呢?本文作者通过一个例子来讲述自己是如何在RAC中使用Kiwi来编写单元测试。
业务逻辑测试
由于这里需要验证用户名和密码,复用性高,我不将处理逻辑放在viewModel中,而是定义一个DataValidation来处理。这里的用户名是邮箱格式,而密码要求长度大于等于6即可,方法如下:
@interface DataValidation : NSObject
+ (BOOL)isValidEmail:(NSString *)
+ (BOOL)isValidPassword:(NSString *)
@end测试用例设计如下:
图:数据验证 Test Case
然后使用kiwi编写测试如下:
SPEC_BEGIN(DataValidationSpec)
describe(@"DataValidation", ^{
context(@"when email is ", ^{
it(@"should return YES", ^{
BOOL result = [DataValidation isValidEmail:@""];
[[theValue(result) should] beYes];
context(@"when email ", ^{
it(@"should return YES", ^{
BOOL result = [DataValidation isValidEmail:@""];
[[theValue(result) should] beNo];
......省略两个测试用例
});ViewModel层测试
前面已经完成了数据绑定和数据校验逻辑,接下来思考使用哪个类处理用户名和密码是否有效才能点击和点击按钮后,如何调用网络层在来匹配用户名和密码,RAC提供一个RACCommand类。LoginViewModel定义一个属性loginCommand,并在实现文件中使用Lazy
Initialization初始化:
- (RACCommand *)loginCommand
if (!_loginCommand) {
_loginCommand = [[RACCommand alloc] initWithEnabled:[self isValidUsernameAndPasswordSignal] signalBlock:^RACSignal *(id input) {
return [LoginClient loginWithUsername:self.username password:self.password];
return _loginC
}上面有一个重要方法isValidUsernameAndPasswordSignal来监听和验证用户名和密码:
- (RACSignal *)isValidUsernameAndPasswordSignal
return [RACSignal combineLatest:@[RACObserve(self, username), RACObserve(self, password)] reduce:^(NSString *username, NSString *password) {
return @([DataValidation isValidEmail:username] && [DataValidation isValidPassword:password]);
}由于上面的方法isValidUsernameAndPasswordSignal已经监听LoginViewModel的username和password,当username和password其中一个改变时,DataValidation类都会调用isValidEmail和isValidPassword来数据验证,并将结果包裹成RACSignal对象返回。
测试用例设计如下:
图:View Model Test Case
然后使用kiwi编写测试如下:
describe(@"LoginViewModel", ^{
__block LoginViewModel* viewModel =
beforeEach(^{
viewModel = [LoginViewModel new];
afterEach(^{
viewModel =
context(@"when username is
and password is freedom", ^{
__block BOOL result = NO;
it(@"should return signal that value is YES", ^{
viewModel.username = @"";
viewModel.password = @"freedom";
[[viewModel isValidUsernameAndPasswordSignal] subscribeNext:^(id x) {
result = [x boolValue];
[[theValue(result) should] beYes];
......省略两个测试用例
});以上测试用例很简单,设置viewModel的username和password,然后调用isValidUsernameAndPasswordSignal返回RACSignal对象,使用subscribeNext获取它的值,最后验证。
网络层测试
最后处理点击登录按钮访问服务器来验证用户名和密码。我定义一个LoginClient类来处理:
@interface LoginClient : NSObject
+ (RACSignal *)loginWithUsername:(NSString *)username password:(NSString *)
@end只要输入username和password两个参数,就能返回是否验证成功的结果被包裹在RACSignal对象中。
由于这里我是使用模拟服务,所以只设计一个成功的测试用例:
图:Network Test Case
然后使用kiwi编写测试如下:
describe(@"LoginClient", ^{
context(@"when username is
and password is samlau", ^{
__block BOOL success = NO;
__block NSError *error =
it(@"should login successfully", ^{
RACTuple *tuple = [[LoginClient loginWithUsername:@"" password:@"samlau"] asynchronousFirstOrDefault:nil success:&success error:&error];
NSDictionary *result = tuple.
[[theValue(success) should] beYes];
[[error should] beNil];
[[result[@"result"] should] equal:@"success"];
});里面使用RAC的一个重要方法asynchronousFirstOrDefault来测试异步网络访问的。详情可参考文章。
抓取网络数据并显示情景
如图所示,输入正确的用户名和密码后,跳转到一个食物列表页面,它从服务端抓取图片、价格和已售份数后以列表的方式显示。
网络层测试
首先考虑如何设计和实现API,然后再考虑如何测试。因为它需要从服务端抓取数据,需要设计一个访问食物列表数据的类FoodListClient,设计如下:@interface FoodListClient : NSObject
+ (RACSignal *)fetchFoodL
FoodListClient实现如下:
@implementation FoodListClient
+ (RACSignal *)fetchFoodList
return [[[AFHTTPSessionManager manager] rac_GET:[URLHelper URLWithResourcePath:@"/v1/foodlist"] parameters:nil] replayLazily];
fetchFoodList方法主要从服务端抓取数据后,返回一个JSON格式的数组。因此想测试这个API,只需要使用RAC的asynchronousFirstOrDefault方法返回RACTuple对象,获取第一个值,测试返回数组不为空即可。使用kiwi编写测试如下:
describe(@"FoodListClient", ^{
context(@"when fetch food list ", ^{
__block BOOL successful = NO;
__block NSError *error =
it(@"should receive data", ^{
RACSignal *result = [FoodListClient fetchFoodList];
RACTuple *tuple = [result asynchronousFirstOrDefault:nil success:&successful error:&error];
NSArray *foodList = tuple.
[[theValue(successful) should] beYes];
[[error should] beNil];
[[foodList shouldNot] beEmpty];
});Model层测试
抓取完数据后,它的数据格式一般都是JSON格式,需要转化为Model方便访问和修改,通常我都使用来实现。我定义一个FoodModel类:
@interface FoodModel : MTLModel &MTLJSONSerializing&
@brief 食物图片URL
@property (copy, nonatomic) NSString *foodImageURL;
@brief 食物价格
@property (copy, nonatomic) NSString *foodP
@brief 销量
@property (copy, nonatomic) NSString *saleN
@end那么如何测试它是否转化成功呢?首先基于上一个网络层测试获取返回JSON格式的食物列表数据,然后调用MTLJSONAdapter类的modelsOfClass:
fromJSONArray: error:方法来转化成FoodModel的数组。接下来断言数组不能为空和数组的第一个元素是FoodModel类。
使用kiwi编写测试如下:
describe(@"FoodModel", ^{
context(@"when JSON data convert to FoodModel", ^{
__block BOOL successful = NO;
__block NSError *error =
it(@"should return FoodModel array", ^{
// get data from network
RACSignal *result = [FoodListClient fetchFoodList];
RACTuple *tuple = [result asynchronousFirstOrDefault:nil success:&successful error:&error];
NSArray *foodList = tuple.
// assert that foodList can&#039;t be empty
[[theValue(successful) should] beYes];
[[error should] beNil];
[[foodList shouldNot] beEmpty];
// assert that return FoolModel array
NSArray *foodModelList = [MTLJSONAdapter modelsOfClass:[FoodModel class] fromJSONArray:foodList error:nil];
[[foodModelList shouldNot] beEmpty];
[[foodModelList[0] should] beKindOfClass:[FoodModel class]];
});ViewModel抓取数据
完成抓取网络数据和转化JSON数据为Model后,我使用FoodViewModel来抓取网络数据和完成数据映射,设计与实现如下:
@interface FoodViewModel : RVMViewModel
@brief FoodModel列表
@property (strong, nonatomic, readonly) NSArray *foodModelL
@end@implementation FoodViewModel
- (instancetype)init
self = [super init];
if (!self) {
RAC(self, foodModelList) = [[FoodListClient fetchFoodList] map:^id(RACTuple * tuple) {
return [MTLJSONAdapter modelsOfClass:[FoodModel class] fromJSONArray:tuple.first error:nil];
@endController加载数据
最后FoodListViewController负责构建view hierarchy和加载数据:
#pragma mark - Lifecycle
- (void)viewDidLoad
[super viewDidLoad];
// setup title name and background color
self.title = @"食物列表";
self.view.backgroundColor = [UIColor whiteColor];
// build view hierarchy
[self buildViewHierarchy];
// when finish fetching data and reload table view
[RACObserve(self.foodViewModel, foodModelList) subscribeNext:^(NSArray* items) {
self.foodListDataSource.items =
[self.tableView reloadData];
编写单元测试是程序员的一项基本技能,如果能够设计好的测试用例并编写测试验证结果,不仅保证代码的质量,而且有利于以后重构加一层保护层。一旦修改了代码之后,如果运行单元测试,并没有通过的话,说明你在重构过程中引入新的Bug。如果通过了单元测试,说明并没有引入新的Bug。
ReactiveCocoa
作者简介:刘耀柱(),iOS Developer兼业余Designer,参与开发技术前线iOS项目翻译,个人博客:,GitHub:。
将于10月15-16日在北京新云南皇冠假日酒店召开。大会特设五大技术专场:平台与技术iOS、平台与技术Android、产品与设计、游戏开发、企业移动化、虚拟现实专场。此外,大会更是首次举办国内极具权威影响力的IoT技术峰会,特设硬件开发技术与嵌入式开发两大专场。大会将聚集国内最具实力的产品技术团队,与开发者一道进行最前沿的探讨与交流。&
第一时间掌握最新移动开发相关信息和技术,请关注mobilehub公众微信号(ID: mobilehub)。
推荐阅读相关主题:
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章

我要回帖

更多关于 索多玛120 mtime 的文章

 

随机推荐