Jun 12

使用cocoapods的项目配置单测踩坑

Lrdcq , 2016/06/12 00:26 , 程序 , 閱讀(3278) , Via 本站原創
前几日为已经配置好cocoapods的项目配置单元测试,本来事前已经做好单测的准备工作,甚至单测的demo都写好了,一往项目中搭,却遇到了很多意外的问题,折腾了整整一天,特此记录。

1.一开始,什么也没做,就直接放入了准备好的单测程序,然后运行。当然,gg,编译不通过。仔细一看编译失败的原因,它告诉我是“Libraries not found”。原来如此,可以想想确实并没有通过pods把那么多第三方库引入嘛。好,那么参考这篇问答(http://stackoverflow.com/questions/14512792/libraries-not-found-when-using-cocoapods-with-ios-logic-tests,注意,这是本文最大的坑)的最佳答案,我把我们的podfile改成了其中推荐的形式即:
def shared_pods
    pod 'SSKeychain', '~> 0.1.4'
    ...
end

target 'project' do
    shared_pods
end

target 'projectTests' do
    shared_pods
end

然后运行pod install,这次总可以了吧。

2.然后,上面那么做之后的后果,就是在pod检查文件的shell这儿,就编译失败了,发生错误类似于:“diff: /../Podfile.lock: No such file or directory”。而且不止是单测编译失败,主项目也编译失败,发生了什么?上蹿下跳的检查了半天,最后发现,是pod的配置文件没加载进来导致的,原来是配置文件设置错了。配置文件的设置是在项目设置的PROJECT中info的最开始,configurations部分,进去一看便知道,每一次编译config下的target,项目target下的pod配置加载的Pods.debug,而测试target下压根就没有。对应找一下实际存在的文件,原来是有了Pods-project.debug和Pods-projectTests.debug。

仔细一分析就可以知道,原来pod只有一个目标的时候,生成的文件便是Pods为名的,但是如果有多个目标,则是以Pods-targetName为名的了。而在执行pod install的时候,对已经存在的项目文件,pod可能不敢去随意修改configurations部分的内容,就保持了原状,结果配置文件就匹配不上了。最后,解决的方法也很简单,要么手动把配置文件设置为正确的文件,要么把configurations部分清理干净再跑pod install,就会自动填上了。同理还有pod检查的shell部分,shell中写的地址也有可能出错,请用相同的方式处理。

3.这次程序终于编译成功了,也正常开始运行了,然后......崩溃啦。错误是从程序本体的rac代码中报出来了,错误信息类似于:
2013-10-28 15:58:29.212 Routine[3314:a0b] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Value from stream <RACSignal: 0xe476a00> name: +combineLatest: (
    "<RACSignal: 0xe475890> name: [RACAble(<TimelineNightViewController: 0xe474d70>, self.timeline.beginning)] -map:",
    "<RACSignal: 0xe475d80> name: [RACAble(<TimelineNightViewController: 0xe474d70>, self.timeline.ending)] -map:",
    "<RACSignal: 0xe475b60> name:     [[RACAble(<TimelineNightViewController: 0xe474d70>,     self.sunrise)] -map:] -distinctUntilChanged"
) is not a tuple: <RACTuple: 0xe4a2e00> (
    1,
    0,
    1
)'

知错就改,当然是必须的,应该我们满满断点到错误的代码处看看发生了什么。找到reactivecocoa源码中对应的这样的代码:
- (instancetype)reduceEach:(id (^)())reduceBlock {
  NSCParameterAssert(reduceBlock != nil);

  __weak RACStream *stream __attribute__((unused)) = self;
  return [[self map:^(RACTuple *t) {
    NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t);
    return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t];
  }] setNameWithFormat:@"[%@] -reduceEach:", self.name];
}

断点看看?发现一个令人惊讶的事儿,po [t isKindOfClass:RACTuple.class]确确实实返回的YES,NSCAssert却确确实实抛出了错误,什么鬼?继续谷歌谷歌,知道找到了这么一个几乎相同的问题:https://github.com/ReactiveCocoa/ReactiveCocoa/issues/901。嗯?我在项目中link了两份reactivecocoa?顺藤摸瓜,我们找到了在cocoapods下的对应问题:https://github.com/CocoaPods/CocoaPods/issues/1411

问题一目了然了,之前问题1中podfile那种写法并不正确,那样写会导致运行单测的时候环境中会有本体和单测link的两份第三方库,导致运行到有关于isKindOfClass方法的时候运行环境找不到确切的对比目标,而判断为NO,对应程序就崩溃了。同样不只是reactivecocoa,所有使用pod加载的第三方库的isKindOfClass调用处都会出现这个问题。

这个时候回到一开始那个stackoverflow问答里面。。。。原来下面回复里有人说这个问题啊,那么为什么那个回答还是最佳呢?不管这么多了,总之我们1.是需要建立单测target对应的pod配置,同时单测的配置里不应该有何主程序重复的第三方库引入。得了,最后podfiles还是写得干干净净:
target 'project' do
    pod 'SSKeychain', '~> 0.1.4'
    ...
end

target 'projectTests' do
    ...
end

其中似乎projectTests中一个库都不填写的话,并不会生效,还好我整好需要pod 'OCMockito', '~> 3.0',那就配置进去好了。把项目文件中pod一开始配置的东西清理干净,pod install一下,这次终于可以了。

总之,这次在pod下配置单测遇到这么多大坑,和还是对pod和ios加载与运行第三方库的机制不熟悉和误解有关,不过坑踩过了也是好事儿,下次做这种事儿就是分分钟了(笑)。
logo