iOS ArticleItem *ai = [[ArticleItemself alloc init] init]; 实例化一个对象(只有三个字符串属性)

玩转模拟对象 - 博客 - 伯乐在线
& 玩转模拟对象
测试驱动开发(TDD)中,开发者经常使用模拟对象进行系统设计,模拟对象到底是什么呢?部分模拟对象和全部模拟对象又是什么呢?模拟对象真的让人又爱又恨吗?让我们以Objective-C测试框架OCMock来探个究竟。
模拟对象设计
模拟对象可以解决两种问题。第一种是(它们也是因此而提出的)用于设计测试驱动开发的测试类。想象一下,你已经完成了第一个测试,并知道了一些关于第一个类的API的信息。你的测试调用了新类的方法,你知道,应该从它们协作者之一种抓取一些信息。问题是,协作者尚不存在,而你又不想放弃这个已经设计出来的并开始测试的类。
此时,你可以创建一个模拟对象代表这个尚未“出生”的协作者。你可以设定你想要通过该“协作者”测试调用对象的期望值,而且,如果需要的话,还可以返回一个可以测试控制的值。你的测试可以验证你所期望调用的方法是否真的被调用了,如果没有,则测试失败。
在这种情况下,模拟对象就像一台VCR,只是没有上世纪八十年代的矮胖的造型和易受损的磁带。测试期间,模拟对象会记录你发送给它的每一条消息。然后,可以通过重放与消息列表做比较来看是不是你所需要的。就像用VCR,如果你想要看的是小精灵2(Gremlins 2),但是记录的却是上半年的新闻和欢乐酒店(Cheers),这就让人较为失望。
关键的部分是,你实际上并不需要建立真正的协作对象。事实上,你完全不需要关心它是怎么实施的。唯一需要关注的是它需要返回的消息,这样就可以验证他们是否被发送了。实际上,模拟对象可以让你觉得说,“我知道,在某些时候,我会考虑这一点,但我不希望因此而分心。” 对于测试驱动开发者,这就像一个待办事项清单一样清晰。
让我们来看一个例子。假设书呆子Ranch发现了市场上对博物馆库存管理App的需求。通常博物馆收藏了大量的文物,他们需要了解所有的库存,并能按主题,国家,年代等在画廊组织展览。关于库存的需求类似如下:
“作为策展人,我想知道所有需要展出的文物,这样我就可以给我的游客们讲故事了”。
我会写一个可以提供一个所有文物的清单的库存类用来测试。当然,磁盘上还有其他类也存储了所有的文物,但是我不关心他们是如何工作的,我只要创建一个库存接口的模拟对象。我的测试类如下:
@implementation BNRMuseumInventoryTests
- (void)testArtefactsAreRetrievedFromTheStore
//Assemble
id store = [OCMockObject mockForProtocol:@protocol(BNRInventoryStore)];
BNRMuseumInventory *inventory = [[BNRMuseumInventory alloc] initWithStore:store];
NSArray *expectedArtefacts = @[@&An artefact&];
[[[store expect] andReturn:expectedArtefacts] fetchAllArtefacts];
NSArray *allArtefacts = [inventory allArtefacts];
XCTAssertEqualObjects(allArtefacts, expectedArtefacts);
[store verify];
为了让这个类编译通过,我需要创建BNRMuseumInventory类和它的initWithStore:和allArtefacts方法。
@interface BNRMuseumInventory : NSObject
- (id)initWithStore:(id &BNRInventoryStore&)
- (NSArray *)allA
@implementation BNRMuseumInventory
- (id)initWithStore:(id &BNRInventoryStore&)store
- (NSArray *)allArtefacts
我还要定义BNRInventoryStore协议及其-fetchAllArtefacts方法,但我现在还不需要实现它们。为什么要我将它定义为一个协议,而不是另一个类?是为了提高灵活性:我知道我想发送给BNRInventoryStore的消息,但我并不需要关心它是如何处理这些消息的。使用协议能让我灵活的处理实现存储的方法:只要它能响应我所关心的消息,它可以是任何类型的类。
@protocol BNRInventoryStore &NSObject&
- (NSArray *)fetchAllA
现在有足够的信息让编译器来编译和运行测试,但它还是不能通过。
Test Case '-[BNRMuseumInventoryTests testArtefactsAreRetrievedFromTheStore]' started.
/Users/leeg/BNRMuseumInventory/BNRMuseumInventory Tests/BNRMuseumInventoryTests.m:91: error: -[BNRMuseumInventoryTests testArtefactsAreRetrievedFromTheStore] : ((allArtefacts) equal to (expectedArtefacts)) failed: (&(null)&) is not equal to (&(
&An artefact&
&unknown&:0: error: -[BNRMuseumInventoryTests testArtefactsAreRetrievedFromTheStore] : OCMockObject[BNRInventoryStore]: expected method was not invoked: fetchAllArtefacts
// snip more output
在测试中断言检测到期待的文物集合并未返回,fetchAllArtefacts方法没有被调用,模拟对象验证失败。只有修复这两个问题,我们才可以通过测试。
@implementation BNRMuseumInventory
id &BNRInventoryStore& _
- (id)initWithStore:(id &BNRInventoryStore&)store
self = [super init];
- (NSArray *)allArtefacts
return [_store fetchAllArtefacts];
模拟一体化
第二种使用模拟对象的方法是使用外部代码,如苹果的框架或第三方库,进行一体化。模拟对象可以简化使用框架所带来的复杂性,因为测试并不需要搭建一个成熟的环境,只需确保我们的应用程序能连接到该环境中的一小部分。这种使用模拟对象的模式叫做谦卑对象(Humble Object)。
继续VCR的比喻,我们并没有设计一个与框架交互的类,但我们要检查我们是否遵守了他们规定的规则。就像了你买了一台VHS录像机,但你不需要知道磁带的类型,你只能使用VHS录像带,因为这是厂家规定的。同样的,我们可以告诉我们的模拟对象,期望值是VHS磁带,所以如果我们给它一个录像带Betamax,测试将会失败。
回到我们的博物馆例子中,当应用程序启动时,首先应该看到的是博物馆所有文物的清单,这可以使用UIKit设置窗口的根视图控制器来实现。但是要设置整个窗口的测试环境,会非常慢且复杂,所以我们用一个模拟对象替换窗口。
- (void)testFirstScreenIsTheListOfAllArtefacts
BNRAppDelegate *appDelegate = [[BNRAppDelegate alloc] init];
id window = [OCMockObject mockForClass:[UIWindow class]];
appDelegate.window =
[[window expect] setRootViewController:[OCMArg checkWithBlock:^(id viewController) {
return [viewController isKindOfClass:[BNRAllArtefactsTableViewController class]];
[appDelegate application:nil didFinishLaunchingWithOptions:nil];
[window verify];
为了使这个测试通过,须实现应用程序的委托方法。
@implementation BNRAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options
self.window.rootViewController = [[BNRAllArtefactsTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
return YES;
该例子中,还有另外一个需求:当加载一个UIKit的应用程序时:包含初始视图控制器的窗口必须是主要且可见的。我们可以添加一个测试表达这一要求。请注意,由于这个测试和之前的测试使用的是相同的对象,该构造函数可以被分解成一个setup方法。
@implementation BNRAppDelegateTests
BNRAppDelegate *_appD
- (void)setUp
_appDelegate = [[BNRAppDelegate alloc] init];
_window = [OCMockObject mockForClass:[UIWindow class]];
appDelegate.window = _
- (void)testWindowIsMadeKeyAndVisible
[[_window expect] makeKeyAndVisible];
[_appDelegate application:nil didFinishLaunchingWithOptions:nil];
[_window verify];
- (void)testFirstScreenIsTheListOfArtefacts
[[_window expect] setRootViewController:[OCMArg checkWithBlock:^(id viewController) {
return [viewController isKindOfClass:[BNRAllArtefactsTableViewController class]];
[_appDelegate application:nil didFinishLaunchingWithOptions:nil];
[_window verify];
现在我们遇到了一个棘手的问题。新测试失败的原因有两个:预期的makeKeyAndVisible消息没有被发送,却正在发送一个意外的消息setRootViewController:.在 [BNRAppDelegate application:didFinishLaunchingWithOptions:]方法中添加 -makeKeyAndVisible消息 -表示两个测试都失败了,因为模拟窗口对象在每个测试都接收了一个未期待的方法。
完全模拟可以解决这个问题。完全模拟对象可记录它接收到的所有消息,就像一个普通的模拟消息对象,包括不期待的消息。这就像说,“我想记录星际旅行的那个情节:航海者,但如果在这之前有天气预报,我也不介意”,它忽略了额外的信息,并且不考虑导致测试失败的消息。
我们可以在setUp方法中把这个测试的模拟窗口改成一个完全模拟。
- (void)setUp
_appDelegate = [[BNRAppDelegate alloc] init];
_window = [OCMockObject niceMockForClass:[UIWindow class]];
appDelegate.window = _
现在,它可以改变应用程序的委托,这样两个测试都可以通过。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)options
self.window.rootViewController = [[BNRAllArtefactsTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
[self.window makeKeyAndVisible];
return YES;
有时候,你并不需要用模拟取代所有对象的行为。你只是想消除一些依赖或复杂的行为,并在你要测试的方法使用其结果。你可以创建一个子类,并重写复杂的方法,此时使用部分模拟会更容易。部分模拟作为真正的对象的代理,截取了部分消息,但是仍然可以使用那些没有被替换的消息的实现方法。
再回到我们的博物馆库存的App例子中,策展人需要将文物的原产地作为筛选条件。这意味着需要所有的文物清单,并对这些对象做一些测试,而我们所做的是使allArtefacts方法与库存对象进行沟通。但这并不是我们在本次测试需要关心的事情:我们要专注于筛选,且不重复我们在之前已经完成的测试工作。使用库存对象的部分模拟就可以让我们去掉桩对象的那部分。这个测试类也会影响文物数据模型的设计。
@implementation BNRMuseumInventoryTests
BNRMuseumInventory *_ //created in -setUp
- (void)testArtefactsCanBeFilteredByCountryOfOrigin
id romanPot = [OCMockObject mockForProtocol:@protocol(BNRArtefact)];
[[[romanPot stub] andReturn:@&Italy&] countryOfOrigin];
id greekPot = [OCMockObject mockForProtocol:@protocol(BNRArtefact)];
[[[greekPot stub] andReturn:@&Greece&] countryOfOrigin];
id partialInventory = [OCMockObject partialMockForObject:_inventory];
[[[partialInventory stub] andReturn:@[romanPot, greekPot]] allArtefacts];
NSArray *greekArtefacts = [partialInventory artefactsFromCountry:@&Greece&];
XCTAssertTrue([greekArtefacts containsObject:greekPot]);
XCTAssertFalse([greekArtefacts containsObject:romanPot]);
在上面的测试中,我用OCMock的-stub方法,而不是-expect方法。该方法告诉模拟对象处理该消息并返回指定的值(如果有),但不设置该测试稍后需验证的消息的期望值。我可以通过artefactsFromCountry的返回值来辨别代码是否有用,我并不需要关心如何实现(但如果你担心硬编码的一些作弊行为,譬如:通常都会返回集合中的最后一个对象,你可以简单地添加更多的测试)。
这个测试告诉我们一些关于BNRArtefact协议的事情。
- (NSString *)countryOfO
现在就可以创建artfactsFromCountry:方法。
- (NSArray *)artefactsFromCountry:(NSString *)country
NSArray *artefacts = [self allArtefacts];
NSIndexSet *locationsOfMatchingArtefacts = [artefacts indexesOfObjectsPassingTest:^(id &BNRArtefact& anArtefact, NSUInteger idx, BOOL *stop){
return [[anArtefact countryOfOrigin] isEqualToString:country];
return [artefacts objectsAtIndexes:locationsOfMatchingArtefacts];
当你构建应用程序的测试驱动时,模拟对象能帮助你集中注意力。他们让你专注于你现在正在做的测试,同时推迟对你未创建对象的测试。他们让你专注于你正在测试的对象的部分,忽略你已经测试过或尚未测试的东西。他们还让你专注于你自己的代码,用简单的类代替复杂的框架类。
如果你很由文中VCR想到了你家的那部录音机,那它估计已经到了进博物馆的年纪了,而我们刚刚写的文物库存管理应用程序,它会在那找到一个舒适的家。
关于作者:
可能感兴趣的话题
o 227 回复
关于伯乐在线博客
在这个信息爆炸的时代,人们已然被大量、快速并且简短的信息所包围。然而,我们相信:过多“快餐”式的阅读只会令人“虚胖”,缺乏实质的内涵。伯乐在线博客团队正试图以我们微薄的力量,把优秀的原创/译文分享给读者,做一个小而精的精选博客,为“快餐”添加一些“营养”元素。
新浪微博:
推荐微信号
(加好友请注明来意)
– 好的话题、有启发的回复、值得信赖的圈子
– 分享和发现有价值的内容与观点
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 翻译传播优秀的外文文章
– 国内外的精选文章
– UI,网页,交互和用户体验
– 专注iOS技术分享
– 专注Android技术分享
– JavaScript, HTML5, CSS
– 专注Java技术分享
– 专注Python技术分享
& 2015 伯乐在线
赞助云主机版权所有 京ICP备号-2
迷上了代码!当前访客身份:游客 [
当前位置:
& & & &&提供的数据持久化方式有:SQLiteCoreData属性列表、NSUserDefault对象归档。
& & & & 这里来简单介绍下中的对象归档:
& & & & 对象归档是将对象归档以文件的形式保存到磁盘中(也称为序列化,持久化)使用的时候读取该文件的保存路径读取文件的内容(也称为接档,反序列化)
& & & & 对象归档的文件是保密的磁盘上无法查看文件中的内容,而属性列表是明文的可以查看)
& & & & 对象归档有两种方式:1、对foundat中对象进行归档 & & 2、自定义对象归档
& & & & 1、简单对象归档
& & & &使用两个类:NSKeyedA&richivNSKeyedUnarchiver
& & & &NSStr*homeDirectori=NSHomeD&&//获取根目录
& & & &NSStringhomePath=[homeDirectoristringByA&ppendingPathComponent:@"自定义文件名,如test.archiver"];
& & & &NSA&rrai*arrai=@[@"abc",@"123",@12];
& & & &Boolflag=[NSKeyedA&richivarchiveRootObject:arraitoFile:homePath];
& & & ifflag{
& & & NSLog@"归档成功!";
& & & 读取归档文件的内容:
& & & NSA&rrai*arrai=[NSKeyedUnarchivunarchiveObjectWithFile:homePath];
& & & NSLog@"%@",
& & & & 这样就简单了实现了将NSA&rrai对象的归档和解档。
& & & &但是这种归档方式有个缺点,就是一个文件只能保存一个对象,如果有多个对象要保存的话那岂不是有n多个文件,这样不是很适合的所以有了下面这种归档方式。
& & & & 2、自定义内容归档
& & & &归档:
& & & &使用NSData实例作为归档的存储数据
& & & &添加归档的内容---使用键值对
& & & &完成归档
& & & &解归档:
& & & &从磁盘读取文件,生成NSData实例
& & & &根据NSData实例和初始化解归档实例
& & & &解归档,根据kei访问value
& & & &NSStr*homeDirectori=NSHomeD//获取根目录
& & & &NSStringhomePath=[homeDirectoristringByA&ppendingPathComponent:@"自定义文件名,如test.archiver"];
& & & &NSMutableData*data=[[NSMutableDataalloc]init];
& & & &NSKeyedA&rchiv*archiv=[[NSKeyedA&rchivalloc]initForWritingWithMutableData:data];
& & & &[archivencodeFloat:50forKey:@"age"];
& & & &[archivencodeObject:@"jack"forKey:@"name"];
& & & &[archivfinishEncoding];&//结束添加对象到data中
& & & &[archivrelease];
& & & &[datawriteToFile:homePath&atomically:YES];//将data写到文件中保存在磁盘上
& & & &NData*content=[NSDatadataWithConenteOfFile:homePath];
& & & &NSKeyedUnarchiv*unarchiv=[[NSKeyedUnarchivalloc]initForReadingWithData:content];
& & & &floatag=[unarchivdecodeFloatForKey:@"age"];
& & & &NSStr*name=[unarchivdecodeObjectForKey:@"name"];
& & & & 在iOS开发中,除了归档问题,往往不注意的是安全问题。别以为,就Android会爆漏洞,早在WireLurker出现的时候,iOS的安全神话就已经不复存在了。而目前在国内,iOS安全加密保护这块还是空白。但是,广大iOS开发者也不必担心,因为就在前不久,已经有平台推出了iOS应用加密服务,针对iOS的技术原理和破解原理,分别从本地数据、方法体/方法名、URL编码、程序结构、网络传输数据等几个方面对iOS应用进行全方位的保护,并可以根据iOS应用用户的需求提供定制解决方案,从而实现iOS防破解保护。iOS应用加密
共有7个评论
<span class="a_vote_num" id="a_vote_num_
iOS对象归档,额,用得到吧
<span class="a_vote_num" id="a_vote_num_
<span class="a_vote_num" id="a_vote_num_
在成都市非常重视
<span class="a_vote_num" id="a_vote_num_
啊实打实大大大
<span class="a_vote_num" id="a_vote_num_
iOS对象归档,最近正要研究这块
<span class="a_vote_num" id="a_vote_num_
<span class="a_vote_num" id="a_vote_num_
本地数据、方法体/方法名、URL编码、程序结构、网络传输数据等
更多开发者职位上
有什么技术问题吗?
类似的话题两天热门文章
最新推荐文章

我要回帖

更多关于 alloc和init 的文章

 

随机推荐