qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

iOS集成测试框架—KIF 滚动屏幕时隐藏toolbar

 iOS集成测试框架——KIF
  KIF的全称是“Keep It Functional”,是一款iOS集成测试框架,来自square。KIF使用了苹果非公开的API,很多iOS测试框架都使用了非公开API,出于测试目的还是安全的,KIF并不使用非公开的API生成代码,所以苹果不会拒绝你的应用。
  注意: KIF 2.0并不兼容KIF 1.0,并且使用了不同的测试执行机制。
  功能:
  1.KIF用Objective C写成,最大化集成代码的同时还可以最小化层级数目。
  2.配置简单。KIF可直接集成进你的Xcode项目中,无需运行额外的网络服务器或者安装任何额外的包。
  3.OS覆盖范围广泛。KIF的测试套件可以运行iOS 5.1以上系统,甚至更低Testing Framework的版本也能运行。
  4.向用户一样进行测试。KIF可以模仿用户的输入,点击事件等。
  5.可自动集成Xcode 5测试工具。
  IQDropDownTextField
  使用UIPickerView,支持下拉菜单的文本框。点击文本框出现相关的选择器。
  测试环境:Xcode 5.0,iOS 5.0以上
  KASlideShow--适用于iOS的幻灯篇展示(仅支持ARC)
  适用于iOS的幻灯篇展示(仅支持ARC)
  提供了淡入淡出和水平滑动两种方式来展示幻灯片,支持手动展示。
  测试环境:Xcode 5.0,iOS 4.3以上版本
  XHShockHUD
  重用性好、使用简单、可任意定制HUD样式(用的人去定制),默认有四种定制好的方式。
  测试环境:Xcode 5.0,iOS 4.3以上
  SKSlideViewController
  SKSlideViewController是一个简单易用的滑动导航菜单控件,可设置主要的和可选的视图控制器,易于设置和调整。
  测试环境:Xcode 5.0,iOS 4.3以上
 CCHMapClusterController
  使用MapKit且适用于iOS和OS X平台的高性能map聚集,仅需四行代码。
  测试环境:Xcode 5.0,iOS 6.0以上
  滚动屏幕时隐藏toolbar--ABFullScrollViewController
  自定义ViewController,滚动屏幕时隐藏toolbar,类似Facebook、Safari以及Twitter等。
  测试环境:Xcode 5.0,iOS 7.0以上版本

posted @ 2014-08-01 09:47 顺其自然EVO 阅读(275) | 评论 (0)编辑 收藏

iOS开发之GCD使用总结

  GCD是iOS的一种底层多线程机制,今天总结一下GCD的常用API和概念,希望对大家的学习起到帮助作用。
  GCD队列的概念
  在多线程开发当中,程序员只要将想做的事情定义好,并追加到DispatchQueue(派发队列)当中就好了。
  派发队列分为两种,一种是串行队列(SerialDispatchQueue),一种是并行队列(ConcurrentDispatchQueue)。
  一个任务就是一个block,比如,将任务添加到队列中的代码是:
  1 dispatch_async(queue, block);
  当给queue添加多个任务时,如果queue是串行队列,则它们按顺序一个个执行,同时处理的任务只有一个。
  当queue是并行队列时,不论第一个任务是否结束,都会立刻开始执行后面的任务,也就是可以同时执行多个任务。
  但是并行执行的任务数量取决于XNU内核,是不可控的。比如,如果同时执行10个任务,那么10个任务并不是开启10个线程,线程会根据任务执行情况复用,由系统控制。
  获取队列
  系统提供了两个队列,一个是MainDispatchQueue,一个是GlobalDispatchQueue。
  前者会将任务插入主线程的RunLoop当中去执行,所以显然是个串行队列,我们可以使用它来更新UI。
  后者则是一个全局的并行队列,有高、默认、低和后台4个优先级。
  它们的获取方式如下:
  1 dispatch_queue_t queue = dispatch_get_main_queue();
  2
  3 dispatch queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRORITY_DEFAULT, 0)
  执行异步任务
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2     dispatch_async(queue, ^{
  3         //...
  4     });
  这个代码片段直接在子线程里执行了一个任务块。使用GCD方式任务是立即开始执行的
  它不像操作队列那样可以手动启动,同样,缺点也是它的不可控性。
  令任务只执行一次
  1 + (id)shareInstance {
  2     static dispatch_once_t onceToken;
  3     dispatch_once(&onceToken, ^{
  4         _shareInstance = [[self alloc] init];
  5     });
  6 }
  这种只执行一次且线程安全的方式经常出现在单例构造器当中。
  任务组
  有时候,我们希望多个任务同时(在多个线程里)执行,再他们都完成之后,再执行其他的任务,
  于是可以建立一个分组,让多个任务形成一个组,下面的代码在组中多个任务都执行完毕之后再执行后续的任务:
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2     dispatch_group_t group = dispatch_group_create();
  3
  4     dispatch_group_async(group, queue, ^{ NSLog(@"1"); });
  5     dispatch_group_async(group, queue, ^{ NSLog(@"2"); });
  6     dispatch_group_async(group, queue, ^{ NSLog(@"3"); });
  7     dispatch_group_async(group, queue, ^{ NSLog(@"4"); });
  8     dispatch_group_async(group, queue, ^{ NSLog(@"5"); });
  9
  10     dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"done"); });延迟执行任务
  1     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  2         //...
  3     });
  这段代码将会在10秒后将任务插入RunLoop当中。
  dispatch_asycn和dispatch_sync
  先前已经有过一个使用dispatch_async执行异步任务的一个例子,下面来看一段代码:
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2
  3     dispatch_async(queue, ^{
  4         NSLog(@"1");
  5     });
  6
  7     NSLog(@"2");
  这段代码首先获取了全局队列,也就是说,dispatch_async当中的任务被丢到了另一个线程里去执行,async在这里的含义是,当当前线程给子线程分配了block当中的任务之后,当前线程会立即执行,并不会发生阻塞,也就是异步的。那么,输出结果不是12就是21,因为我们没法把控两个线程RunLoop里到底是怎么执行的。
  类似的,还有一个“同步”方法dispatch_sync,代码如下:
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2
  3     dispatch_sync(queue, ^{
  4         NSLog(@"1");
  5     });
  6
  7     NSLog(@"2");
  这就意味着,当主线程将任务分给子线程后,主线程会等待子线程执行完毕,再继续执行自身的内容,那么结果显然就是12了。
  需要注意的一点是,这里用的是全局队列,那如果把dispatch_sync的队列换成主线程队列会怎么样呢:
  1     dispatch_queue_t queue = dispatch_get_main_queue();
  2     dispatch_sync(queue, ^{
  3         NSLog(@"1");
  4     });
  这段代码会发生死锁,因为:
  1.主线程通过dispatch_sync把block交给主队列后,会等待block里的任务结束再往下走自身的任务,
  2.而队列是先进先出的,block里的任务也在等待主队列当中排在它之前的任务都执行完了再走自己。
  这种循环等待就形成了死锁。所以在主线程当中使用dispatch_sync将任务加到主队列是不可取的。
  创建队列
  我们可以使用系统提供的函数获取主串行队列和全局并行队列,当然也可以自己手动创建串行和并行队列,代码为:
  1     dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_SERIAL);
  2     dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_CONCURRENT);
  在MRC下,手动创建的队列是需要释放的
  1     dispatch_release(myConcurrentDispatchQueue);
  手动创建的队列和默认优先级全局队列优先级等同,如果需要修改队列的优先级,需要:
  1     dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.Steak.GCD", DISPATCH_QUEUE_CONCURRENT);
  2     dispatch_queue_t targetQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
  3     dispatch_set_target_queue(myConcurrentDispatchQueue, targetQueue);
  上面的代码修改队列的优先级为后台级别,即与默认的后台优先级的全局队列等同。
  串行、并行队列与读写安全性
  在向串行队列(SerialDispatchQueue)当中加入多个block任务后,一次只能同时执行一个block,如果生成了n个串行队列,并且向每个队列当中都添加了任务,那么系统就会启动n个线程来同时执行这些任务。
  对于串行队列,正确的使用时机,是在需要解决数据/文件竞争问题时使用它。比如,我们可以令多个任务同时访问一块数据,这样会出现冲突,也可以把每个操作都加入到一个串行队列当中,因为串行队列一次只能执行一个线程的任务,所以不会出现冲突。
  但是考虑到串行队列会因为上下文切换而拖慢系统性能,所以我们还是很期望采用并行队列的,来看下面的示例代码:
1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2     dispatch_async(queue, ^{
3         //数据读取
4     });
5     dispatch_async(queue, ^{
6         //数据读取2
7     });
8     dispatch_async(queue, ^{
9         //数据写入
10     });
11     dispatch_async(queue, ^{
12         //数据读取3
13     });
14     dispatch_async(queue, ^{
15         //数据读取4
16     });
  显然,这5个操作的执行顺序是我们无法预期的,我们希望在读取1和读取2执行结束后,再执行写入,写入完成后再执行读取3和读取4。
  为了实现这个效果,这里可以使用GCD的另一个API:
  1     dispatch_barrier_async(queue, ^{
  2         //数据写入
  3     });
  这样就保证的写入操作的并发安全性。
  对于没有数据竞争的并行操作,则可以使用并行队列(CONCURRENT)来实现。
JOIN行为
  CGD利用dispatch_group_wait来实现多个操作的join行为,代码如下:
1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2     dispatch_group_t group = dispatch_group_create();
3
4     dispatch_group_async(group, queue, ^{
5         sleep(0.5);
6         NSLog(@"1");
7     });
8     dispatch_group_async(group, queue, ^{
9         sleep(1.5);
10         NSLog(@"2");
11     });
12     dispatch_group_async(group, queue, ^{
13         sleep(2.5);
14         NSLog(@"3");
15     });
16
17     NSLog(@"aaaaa");
18
19     dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC);
20     if (dispatch_group_wait(group, time) == 0) {
21         NSLog(@"已经全部执行完毕");
22     }
23     else {
24         NSLog(@"没有执行完毕");
25     }
26
27     NSLog(@"bbbbb");
  这里起了3个异步线程放在一个组里,之后通过dispatch_time_t创建了一个超时时间(2秒),程序之后行,立即输出了aaaaa,这是主线程输出的,当遇到dispatch_group_wait时,主线程会被挂起,等待2秒,在等待的过程当中,子线程分别输出了1和2,2秒时间达到后,主线程发现组里的任务并没有全部结束,然后输出了bbbbb。
  在这里,如果超时时间设置得比较长(比如5秒),那么会在2.5秒时第三个任务结束后,立即输出bbbbb,也就是说,当组中的任务全部执行完毕时,主线程就不再被阻塞了。
  如果希望永久等待下去,时间可以设置为DISPATCH_TIME_FOREVER。
  并行循环
  类似于C#的PLINQ,OC也可以让循环并行执行,在GCD当中有一个dispatch_apply函数:
  1     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  2     dispatch_apply(20, queue, ^(size_t i) {
  3         NSLog(@"%lu", i);
  4     });
  这段代码让i并行循环了20次,如果内部处理的是一个数组,就可以实现对数组的并行循环了,它的内部是dispatch_sync的同步操作,所以在执行这个循环的过程当中,当前线程会被阻塞。
  暂停和恢复
  使用dispatch_suspend(queue)可以暂停队列中任务的执行,使用dispatch_result(queue)可以继续执行被暂停的队列。

posted @ 2014-08-01 09:46 顺其自然EVO 阅读(24930) | 评论 (2)编辑 收藏

攻城师的沟通修炼

  软件开发做久了,都会有一些角色的转变,从最初的直接leader分配具体工作来做,到后来需要和不同部门一起协作开发,沟通都是必备技能与工具,无论升级为TL(team leader)、PM(project manager),还是继续做软件工程师。最初做研发时,可能更多的只是关注技术性的专业知识上,觉得做好分配给自己的本职工作是唯一目标。当你技术能力提升到一定高度的时候,领导会分配更大的项目让你负责,这时你发现,写代码不再是你8小时的全部内容,你需要和产品经理探讨需求,需要和其他攻城师讨论技术方案,需要和其他部门协调项目进度,需要和测试人员确认测试流程。。。。等等等等,你突然发现,沟通变得越来越重要,它已经成为你完成特定行动唯一真正有效的手段。
  这种转型,对很多工程师来说是痛苦的,很多人当初选择做研发是觉得程序开发只面对机器而不是人,正好符合自己不善言辞的木讷内向性格,当自己在这行业学到很多技术,自以为可以松一口气的时候,突然发现自己的性格竟然不适合这个行业了,你是否在有了很多技术经验后有过这样的迷茫?
  你是否在和别人沟通需求时这个表情?
  你是否也像我曾经一样,在别人说观点时一直在寻找一次讲话的机会?或者干脆马上粗暴的打断别人来表达自己的立场?在产品经理提了一个大的需求改动时马上说这个做不了?在提出技术方案后意识到错误,但为了程序员的自尊以及觉得其他人都不懂技术而不去做道歉不做修改?在测试人员指出某个问题时觉得测试人员太过于较真,觉得这种情况一万年也不会出现而懒得修改?
  最近读了一本工程师修炼的书,里面讲了最重要的一条就是沟通。
  无论你从程序员升级为TL,还是PM,还是架构师,或者是高级工程师,其实都是一种晋级。沟通技术知识对你往上升是非常关键的技能。这种技能通常意味着维护你的职位,明确特定项目的潜在风险和当前问题。在单位等级结构的这一层上,你应该阻止产生问题、寻找问题并且解决问题。你的上级都在盯着看你的每一步动作。压力往往会非常大。对于靠技术吃饭的人来说,若想迈出跳至管理的第一步,阶梯上的下个台阶的特性已经大大变化了。尤其是,首先要求的技能是沟通范围、数量大大地拓宽了。
  一、沟通原则
  学习有效沟通是一个终身的过程——永远都有改善的余地。要学习的沟通原则包括:先听后说、专心致志(人和心思在一处)、正面思考等,这些原则有助于建立与别人的信任关系,使你成为更高超的沟通者。
  1. 先听后说
  你有没有发现自己在某次谈话中总是想寻求一次讲话的机会,而没有真正在听别人说什么?当你没有听时,你传递给那个对你讲话的人什么信息呢?
  至少表面上,你显得不在乎别人说什么。大部分人会很快厌倦这样的谈话,因为他们说的话白白在空气中传播却没人去听。说话的人也许会想他还有更好的事要做,而结束此次谈话。如果你只是偶尔这样做,这种行为没什么大不了的。如果这是你的习惯做法,那么你是在自己与别人之间构筑一道墙。
  你听的时候,是不是在找机会纠正对方?即便谈论的话题在往前走,但是你的思路还停留在刚才的某一点上?
  这种情况说明,我们并没有在听别人说什么。讲话的人对你很在乎,从其忙碌的工作中抽出时间,为你提供这些宝贵的信息,所以应该认真去听他说什么。
  当有人与你说话时,要看着他说,并试着理解他想沟通的内容。给对方足够的时间来表达他的观点,然后再向其询问要澄清的问题。向他表达非语言的反馈,例如点头,让他知道你在关注这次谈话。
  我认为罗马人Epictetus说得好:“我们有两个耳朵,一个嘴巴,所以我们应该多听少说。”
 2. 专心致志
  不管你在哪里,都应专心致志。生活中有许多事情要分神,例如这个周末你要做什么,几分钟前开会回来如何解决会上的问题,怎样找个办法告诉老板某个负面的消息,小孩今晚的英式足球比赛几点开场……所有这些琐事都很容易让你想入非非。
  一般来说,人在任何时刻最多只能同时处理7件±2件那么多的事。如果你的脑袋全是一些无关紧要的琐事,你就无法专心致志地做事。要是有人对你说话,你就会完全听不到他在讲什么。倘若他在问你问题,你可能要他们再说一遍。这种情况下,你其实是在浪费别人的时间,他们不会高兴的。如果房间里有个执行官,你就会给人家留下一个持久的坏印象:真是个浪费钱的家伙!
  请你读些时间管理方面的书,列出每天需要关注事情的清单计划。在计划中安排好任务的优先级(当天、本周等),并标识每个任务准备投入的时间。这个办法能够让你通过计划安排好每件事,节省你的精力去记忆周围各种正在发生的事。
  如果某个会议不是真的需要你参加,你就不要去;假如确实需要你参加,就一定要去,而且人和心思都花在那里。
  我发现,坐直、将脚放在座位正下方、做笔记、直视正在讲话的人,能够自然而然地全神贯注于会议正在进行的事情,从说话和肢体语言两方面都给人以积极参与的正面印象。
  不管你做什么,都要专心致志!
  3. 正面思考
  当你表达信息时,总有许多种方法去传递它们。信息需要真实和准确,然而表现出其意义的方式可以多种多样。
  你可以以积极意义或消极意义提供信息。你可以基于所期望的结果选择某种方法。也可以采用不偏不倚的方式,不带情绪地列举事实,尽管这通常很难做到。
  从沟通的观点来看,人们容易注意负面的东西。通常负面消息总会带来恐惧(当我感觉恐惧邻近时,我会把它当做“要求集中精力的行动”的信号)。
  作为架构师,你需要避免不必要的偏见信息,让别人能够选择他们要关注的信息。你可以提供若干种替代方案,但这些方案应当是客观平等的。你需要察觉可能的办法,而不是为别人留下疑惑。
  4. 尽早道歉
  在一天的事务中,你可能注意到对他人做的某个事情不合适或不正确。记住放下自尊去给受影响的对方道一个歉。向别人诚心道歉并不是好玩或者容易之举,但你可以赢得别人的尊敬,展示你在尽力成长,尝试变得更好的意图。
  如果你道歉,对方就有可能重新审视事情,而原谅你带来的任何苦恼伤痛。有些让人尴尬的事情转而有了积极意义。你与那人的关系就有了增进的机会,而不是就此冷淡。
  人的本能倾向就是让冒犯别人后的情势不了了之。遗憾的是,你可能埋下了让它长大成祸患的种子,以致对你造成长期的影响。被得罪的人可能会耿耿于怀,在很长很长时间内记住这件事。那个人也许会把这件事告诉别人,说你是个什么类型的人。你和此人及周围其他人的交往能力可能大打折扣。最后,或许你已经忘记做过的事,但是对方却没有忘记。
  道歉时,你要清楚地表达出要道歉的是什么,你说的是什么意思。如果你不是诚心道歉,虚假的说辞可能把事情弄得更糟。如果你不能表达诚意,就不要道歉,但你的目标应当是努力与你所交往的人修缮积极的关系。避免让道歉使你向错误的方向发展,限制你的个人成长。
  5. 不要在缺陷上招致恼羞成怒
  当你在开评审会(例如产品概念评估、需求评审、设计评审、代码评审、测试评审、产品发布评审)时,通常会检查出评审项目的一些缺陷。评审项目的作者对于这些暴露出的缺陷当然会感到不自在。
  二、沟通策略
  在我们研究了沟通的核心原则后,现在你可以应用一系列策略来展示恒定、高效的沟通风格。
  1,多说“是”,少说“不是”
  多想解决方案,解决问题,而不是粗暴的反对。
  2,抑制想自卫的冲动
  通常在交谈中,当我们听到并不完全对自己有正面意义的事情时,我们可能会找借口,我们可能会找办法转移话题,并责怪他人,以使自己脱离干系。或者我们想强词夺理,以阐述那些语句。应当避免做出此反应的冲动。相反,代之以等待,并接受别人所说的话。
  在上一段描述的反应中,会谈的真正兴趣已经从对别人转移到你身上。听别人说话的行为至少暂时结束了,我们开始与会谈者发出警示信号:“我们把话题引向另一个方向吧,一个和我没关系的方向。”注意你正在用的肢体语言—胳膊交叉在胸前,或者头转向一边告诉别人“我不想听了”。
  问自己这个问题:我能从这个人说的话中学到什么?”通常,他给出的信息也许并不是你乐意听到的,但其动机是好的,仍是你接受信息并获得个人成长的机会。
  抑制想自卫的冲动的一个例外就是当手头的问题涉及企业政策或你的正直时。如果别人说的话使你真正涉及与公司政策冲突或你出于正直未做某事(假如,你已经正确做出了行动)时,你需要立即抨击这些说法。你可能想用澄清问题的办法来明确要点,比如“你的意思是我做过某事吗”。如果别人说“是的”,你就以“这并不准确”来明确回应;倘若人家回答“不是”,要感谢他澄清了此事。

posted @ 2014-08-01 09:44 顺其自然EVO 阅读(164) | 评论 (0)编辑 收藏

测试佳话—人人都是测试大牛

 何为测试?测试就是检测,审查一个事务是否符合既定的标准。测试就像是空气和影子,如此亲密无间。曾听闻有人说“人人都是产品经理”。这话有些过了。今日我说——人人都是测试大牛,这却是实实在在。
  不止以下场景你是否熟悉?
  1)菜的时候看看蔬菜新鲜否?
  2)找钱时候看看真假。
  3)买衣服时看看款式默默料子。
  4)甚至打英雄联盟时,记得插眼看看情况。
  如此之类不胜枚举,其实测试天赋是天生的,只要人有欲有求,就有测试,只有有原则,有所谓的世界观,价值观,就有测试,因为你为人处事,不也拿实际情况和内心的标准做对比嘛?
  我说,其实每个人都是被封印的测试大牛,测试工作,更容易无师自通。测试思维锻炼在于平常。厚积而薄发,迟早你会有功成名就的一天。不是吗?下面我们来看面试官总爱问的一道看似无理的问题“我身边有个桌子,你测试下吧”看似无从着手嘛?错,其实灵感就来源于生活
  1)一个桌子的本质就是放东西吧,测测其承重力。
  2)或许他是一个家具,就测测其样子,形貌。
  3)热的食物放在上面,就是个问题,何不测测其耐热能力,甚至是防火,放水等。
  4)桌子旁边坐的是人,何不测测它的容量。
  5)家里有孩子会不会磕到?测测其安全系数。
  6)桌子的伴侣是椅子,何不测测它的百搭系数?
  7)古语有个拍案而起,生气了喜欢敲桌子,何不测测其抗击打,甚至是抗磨损能力。
  8)如果你用它打游戏,怎能不要求平稳能力?
  诸如此类等等,等等……其实测试思维来源于生活的思考和积累,正如那句话,世界上不是缺少美,而是缺少发现美的眼睛,不用等着所谓的大牛去点化你,只要你够用心,善于总结思考,你就是大牛了。大道至简,最总要返璞归真,测试也不例外,不过是让一个产品的质量尽可能高。
  在此我不妨啰嗦一句,有的不负责任的测试人员说,测试不能提高产品的质量,想要提高只能靠研发,和产品同学。我说大谬,那老板要你吃白饭嘛?所谓不以善小而不为,起码我们能保证用户看到尽可能高质量的产品,能对内提出合理的改进意见,只要言辞得当,相信大家也会乐意配合。天下兴亡匹夫有责,何况测试人员在质量问题上责无旁贷。哪怕你就当自己是个监军,你却真的明白监军的职责和任务嘛?
  自豪的做个测试人员,因为我们是天纵英豪。意随心动,发觉自己的测试天赋。

posted @ 2014-08-01 09:42 顺其自然EVO 阅读(202) | 评论 (0)编辑 收藏

Unity3d与iOS交互开发—接入平台SDK必备技能

前言废话:开发手机游戏都知道,你要接入各种平台的SDK。那就需要Unity3d与iOS中Objective-C的函数有交互,所以你就需要用到如下内容:
  一、Unity3d  To  iOS:
  1、创建一个C#文件 SdkToIOS.cs 这是调用iOS函数的接口:
public class SdkToIOS: MonoBehaviour
{
//平台接入开关
public static bool isOpenPlatform = false;
//导入定义到.m文件中的C函数
[DllImport("__Internal")]
private static extern void _PlatformInit();
//定义接口函数供游戏逻辑调用
public static void InitSDK()
{
if (isOpenPlatform)
{
_PlatformInit();
}
}
}
  2、编写与接口对应的Objective-c函数:
//  MyIOSSdk.h
#import <Foundation/Foundation.h>
@interface MyIOSSdk : NSObject
@end
//  MyIOSSdk.m
#import "MyIOSSdk.h"
//这里引用SDK的头文件
#import "SDKPlatform.h"
#if defined(__cplusplus)
extern "C"{
#endif
extern void UnitySendMessage(const charchar *, const charchar *, const charchar *);
extern NSString* _CreateNSString (const char* string);
#if defined(__cplusplus)
}
#endif
//*****************************************************************************
@implementation MyIOSSdk
//**********************
//message tools
+ (void)sendU3dMessage:(NSString *)messageName param:(NSDictionary *)dict
{
NSString *param = @"";
if ( nil != dict ) {
for (NSString *key in dict)
{
if ([param length] == 0)
{
param = [param stringByAppendingFormat:@"%@=%@", key, [dict valueForKey:key]];
}
else
{
param = [param stringByAppendingFormat:@"&%@=%@", key, [dict valueForKey:key]];
}
}
}
UnitySendMessage("SDK_Object", [messageName UTF8String], [param UTF8String]);
}
//**********************
//SDK fun
//初始化SDK
-(void)SDKInit
{
SDKcfg *cfg = [[[SDKcfg alloc] init] autorelease];
cfg.appid =123456;
cfg.appKey =@"aoaoaoaoaoaoaoaoaoaoaoaoaaoaoaoaoaoaoao";
cfg.orientation = UIDeviceOrientationLandscapeLeft;
//调用SDK的初始化函数
[[SDKPlatform defaultPlatform] SDKInit:cfg];
//添加回调监听
[[SDKPlatform defaultPlatform] addObserver:self selector:@selector(SNSInitResult:) name:(NSString *)kInitNotification object:nil];
}
//获取用户ID
-(NSString*)SDKGetUserID
{
[[SDKPlatform defaultPlatform] SDKGetUserID];
}
//**********************
//call back fun
//初始化更新回调
- (void)SNSInitResult:(NSNotification *)notify
{
[MyIOSSdk sendU3dMessage:@"SDKMsgInit" param:nil];
}
@end
//*****************************************************************************
#if defined(__cplusplus)
extern "C"{
#endif
//字符串转化的工具函数
NSString* _CreateNSString (const char* string)
{
if (string)
return [NSString stringWithUTF8String: string];
else
return [NSString stringWithUTF8String: ""];
}
char* _MakeStringCopy( const char* string)
{
if (NULL == string) {
return NULL;
}
char* res = (char*)malloc(strlen(string)+1);
strcpy(res, string);
return res;
}
static MyIOSSdk *mySDK;
//供u3d调用的c函数
void _PlatformInit()
{
if(mySDK==NULL)
{
mySDK = [[MyIOSSdk alloc]init];
}
[lsSDK SDKInit];
}
//注意这个函数是返回字符串
const char* _PlatformGetUin()
{
if(lsSDK==NULL)
{
lsSDK = [[MyIOSSdk alloc]init];
}
return _MakeStringCopy([[lsSDK SDKGetUserID] UTF8String]);
}
#if defined(__cplusplus)
}
#endif
  值得一提的是在上面的代码中特意写了一个返回字符串的例子,因为你要获取用户ID 、昵称什么的。对应在cs文件中导入函数如下:
  [DllImport ("__Internal")]
  private static extern string _PlatformGetUin();
  这里的const char* 会被C#自动转换成string因为在m文件中使用了内存申请,该段内存自然是处在堆内存中,这样转成string符合c#的内存管理机制,我们不用担心它的释放问题。
  3、在你的工程目录中找个地方保存iOS的文件
  打包出XCode工程后导入进去,加入你的SDK就可以了。
  有一点需要说明,如果存放目录为\Assets\Plugins\iOS,那么Unity3d会自动将该目录下的所有文件(暂不支持子文件夹)当做插件文件打包到Xcode工程下的Libraries目录下,这样你就不需要在手动添加了,否则会报错重复声明什么的。
  这种文件各个平台会有多个,可以使用同一头文件且定义的C函数名也都相同,这样更有利于多版本管理。
  二、iOS To  Unity3d:
  这个在上面的 MyIOSSdk.m 文件中已经有剧透了,就是利用unity3d 的UnitySendMessage函数,其中参数1是场景中接受消息的对象,参数2是要执行的函数名,参数3为传入参数,只要按照如下步骤就可以实现这个机制:
  1、在场景中创建一个对象用于接受iOS消息,或者用现有的也可以;
  2、为SDK消息写一个脚本,里面包含各种消息函数;
  3、将脚本挂到之前创建的对象上完事;
  需要注意:这个对象在场景切换时候要始终存在,或者你在每个场景中都加个这玩意也可以,总之只要能收到消息就行了;
  另外,针对参数的传递对应上面的sendU3dMessage函数,我还在消息接受脚本中写过一个消息参数的解析:
void ParseMsg(string msg, out Dictionary<string, string> dicMsg){
if( null == msg || 0 == msg.Length ){
dicMsg = null;
return;
}
dicMsg = new Dictionary<string, string>();
string[] msgArray = msg.Split('&');
for( int i=0; i<msgArray.Length; i++){
string[] elementArray = msgArray[i].Split('=');
dicMsg.Add(elementArray[0],elementArray[1]);
}
}
  这个我自己都还没有实际使用过,有错误自行解决大致是这样。
  ps:关于SDK的接入还有 android版本的尚未研究网上有很多可以参照;
  pps:这种交互方式在iOS的模拟器上貌似不行,测试请使用真机;

posted @ 2014-07-31 10:09 顺其自然EVO 阅读(3111) | 评论 (1)编辑 收藏

配置Mysql数据库主从同步

项目需求:
  1:主数据库ip:192.168.1.31从数据库ip:192.168.1.32
  2:建立主从关系,实现数据主从同步
  实现步骤:
  1:配置主数据库的主配置文件,重启服务
  [root@zhu~]#vim/etc/my.cnf
  [mysqld]
  server_id=31
  log_bin
  [root@zhu~]#servicemysqlrestart
  2:给从数据库用户授权
  mysql>grantreplicationslaveon*.*toslave@"192.168.1.32"identifiedby"123";
  3:配置从数据库的主配置文件,重启服务
  [root@cong~]#vim/etc/my.cnf
  [mysqld]
  server_id=32
  [root@cong~]#servicemysqlrestart
  4:从数据库指定主数据库参数
  mysql>changemasterto
  ->master_host="192.168.1.31",
  ->master_user="slave",
  ->master_password="123",
  ->master_log_file="localhost-bin.000001",
  ->master_log_pos=472;
  5:开启从数据库同步功能

posted @ 2014-07-31 10:08 顺其自然EVO 阅读(193) | 评论 (0)编辑 收藏

Java路径问题解决方案汇集

Java路径中的空格问题
  1、 URLTest.class.getResource("/").getPath();
  URLTest.class.getResource("/").getFile();
  URLTest.class.getClassLoader().getResource("").getPath();
  Thread.currentThread().getContextClassLoader().getResource("").getPath();等多种相似方式获得的路径,不能被FileReader()和FileWriter()直接应用,原因是URL对空格,特殊字符(%,#,[]等)和中文进行了编码处理。如果文件中URLTest.class.getResource("/").getPath();必须以"/"开头然后再加文件名,而URLTest.class.getClassLoader().getResource("").getPath();不用加"/"可以直接添加文件名。
  路径中包含空格时,如果空格变为"%20"有如下处理方法:
  1)使用repaceAll("%20",' ')替换,但只能解决空格问题,如果路径中包含其他特殊字符和中文就不能解决问题。
  2)使用URLDecoder.decode(str,"UTF-8")解码,但是只能解决一部分,若路径中含有+,也是不能解决的,原因是URL并不是完全用URLEncoder.encode(str,"UTF-8")编码的,+号被解码后,则变成空格。
  3)解决所有的问题,用URLTest.class.getClassLoader().getResource("").toURI().getPath();,但是需要处理URISyntaxException异常,比较麻烦一些。
  2、new URL();的参数可以为正确的URI,或者为URI格式的字符串;若字符串是非完整的URI格式,则创建失败。java.net.URI返回的路径中的空格以“空格”的形式出现方法为Thread.currentThread().getContextClassLoader()http://www.huiyi8.com/jiaoben/网页特效代码 .getResource("").toURI().getPath();但是Thread.currentThread().getContextClassLoader().getResource("").toURI().toString();则会以“%20”的形式出现。java.net.URL返回的一切路径中的空格都是以“%20”的形式出现。URL/URI返回的路径分隔符都是“/”(控制台输出"/")。
  3、new File(String filePath);接受正确URI格式的参数和带“空格”(非%20)的正确相对/绝对字符串路径,否则即使给的路径是正确的也会出现找不到文件的异常。File返回的路径分隔符都为“\”(控制台输出"\"),对于存在的文件返回的路径字符串,空格都以"空格"出现,而不存在的路径new出的file,getPath()返回的路径中的空格,仍是new File(String filePath)的参数中原有的形式,即若filePath中是空格的getPath()返回的仍是空格,是“%20”的仍是“%20”。File.toURI() 会将file的路径名中的“空格”转化为“%20”,然后在路径前加protocol:"file:/",而File.toURL()只会在file路径 前简单的加上protocol:"file:/",而不会将“空格”转化为“%20”,原来的无论是“空格”还是“%20”都只会原样保留。
  实际使用中遇到的问题总结如下:
  1、相对路径(即相对于当前用户目录的相对路径)均可通过以下方式获得(不论是一般的java项目还是web项目)String relativelyPath=System.getProperty("user.dir"); 对于一般的java项目中的文件是相对于项目的根目录,而对于web项目中的文件路径,可能是服务器的某个路径,同时不同的web服务器也不同 (tomcat是相对于 tomcat安装目录\bin)。为此,个人认为,在web项目中,最好不要使用“相对于当前用户目录的相对路径”。然而默认情况下,java.io 包中的类总是根据当前用户目录来分析相对路径名。此目录由系统属性 user.dir 指定,通常是 Java 虚拟机的调用目录。这就是说,在使用java.io包中的类时,最好不要使用相对路径。否则,虽然在SE程序中可能还算正常,但是到了EE程序中,可能会出现问题。
  2、web项目根目录获取
  1)建立一个servlet,在其init()方法中添加如下代码
  ServletContext context = this.getServletContext();
  String strs = context.getRealPath("/");
  2)利用httpServletRequest,得到相应的项目路径
  String pathUrl = request.getSession().getServletContext().getRealPath("/");

posted @ 2014-07-31 10:07 顺其自然EVO 阅读(184) | 评论 (0)编辑 收藏

项目经理应该具备的技能

 作为一个优秀的项目经理应该具备五个方面的技能:
  项目管理知识体系
  应用领域的相关知识、标准和规则
  项目环境知识
  一般管理知识
  软技能/人际关系技能
  项目管理知识体系
  就是要掌握常说的9大知识领域:范围、时间、成本、质量、人力资源、风险、沟通、采购再加上集成管理,以及项目经理使用的工具和技术,包括甘特图、进程报告、范围说明书、WBS等等常见的工具。
  应用领域的相关知识、标准和规则
  以我的理解就是要掌握所管理的项目所在行业的基本的东西,这样有助于获取需求、与客户沟通等等。
  项目环境知识
  这一点对于项目经理很重要。对于不同的组织和项目,项目环境不尽相同,但是有些技能确实可以共享的。这些技能包括感知周围的变化(比如客户单位人员变动、该领域的政策的变化、客户人员之间微妙的复杂关系等等),感知组织如何在其特定的政治、社会和自然环境下运转(比如客户企业在该领域所处地位、其产品情况等等)。另外,在组织中大多数项目都会引起组织的变化,并且许多项目自身也包含着变化,所以项目经理必须善于引导和处理变化。
  一般的管理知识和技能
  项目经理应该了解财务管理、会计、合同、融资、销售、营销、物流、组织结构、人事管理等等。
  软技能/人际关系技能
  这一点对项目经理尤为重要,原因之一,是为了理解、引导和满足利益相关者的需求和期望,项目经理必须带头、沟通、谈判、解决问题并影响组织。另外,他们需要积极倾听他人意见,帮助开发解决问题的新方法,并说服他人为了达到项目目标而一起工作。其次,项目经理必须通过提供愿景、分配工作、创造有活力和积极的环境,以及为其他人树立一个合适、有效的行为榜样来领导项目团队。他需要能够激励不同类型的员工,并且需要保持项目团队和其他项目利益相关者之间培养起团队精神。再者,由于大多数项目存在变化,并且需要保持各个冲突项目目标之间的平衡,随意具有较强的应对技能也是十分重要的。
  根据调查来自不同行业的项目管理专家,得出10项最主要的技能:
  1、人际关系技能
  2、领导能力
  3、善于倾听
  4、正直、道德行为、坚定
  5、善于建立信任关系
  6、口头沟通
  7、善于创建团队
  8、解决冲突、冲突管理
  9、批判式思考,解决问题
  10、理解、权衡优先
  所以项目经理都应该不断丰富自己在项目管理、一般管理、软技能和所在行业所需要的知识和经验。但是很有有IT专业人员在培养自己的商业头脑方面花费时间。任何人,无论在技术方面多么擅长,也都应该培养自己的商业技能和软技能。

posted @ 2014-07-31 10:06 顺其自然EVO 阅读(777) | 评论 (0)编辑 收藏

浅谈软件项目的需求管理

软件项目区别于其它项目的最显著的特征是其不可见性,它不像硬件购销、建筑工程,都是实实在在可见的东西。而软件项目在系统交付之前很长一段时间,客户是无法感知自己想要的系统究竟是什么样子。因此,需求管理就显得十分重要,据相关统计数据分析,软件项目90%以上失败的原因都在于没有重视需求或者需求管理方面做的不到位导致的。
  需求管理作为软件项目管理的一个重要内容,贯穿项目实施的全生命周期。俗话说:万事开头难。需求作为软件开发的第一个环节,其重要性不言而喻。市面上关于需求管理的相关理论和书籍很多,但多数停留在理论层面,实操性不强。本文主要是根据我们以往项目的经验,进行一些需求管理方面的探讨。我们可以简单的将软件项目的需求管理分为需求获取、需求分析与验证、需求变更控制三个核心内容。
  (一)需求获取
  需求获取是软件项目需求管理的第一个过程,在这个过程中我们需要运用科学的方法以及相关的项目经验库辅助我们进行需求获取。需求获取的核心内容是通过调研掌握软件项目的实际需求,以便于指导整个项目的实施。需求获取的主要方法包括:用户访谈、问卷调查、现场观摩、头脑风暴等方法。在实际的项目操作过程中,相对比较明确的需求,我们可采用比较固定的需求获取方式,比如:问卷调查等。而对于相对比较模糊的需求或者说用户无法清晰表述自己需要的是什么的时候,我们可采用比较灵活的方式,例如:用户访谈、现场观摩等。
  需求的类型主要包括:业务需求、用户需求和功能需求。在需求获取的过程中,无论采用哪种方法,我们都需要自顶向下或自下向上去了解用户真实的想法。业务需求的获取对象主要是客户的高层领导,我们都知道,项目的发起、实施、最终的成败很大程度上都取决于高层领导,我们需要对他们进行访谈,了解高层领导的公司战略、发展方向,更为重要的是获取他们对将要开发的软件系统的期望,以及希望该系统在解决现有业务问题,对公司整体战略的支撑方面的期望。帮助我们去更好地理解系统的宏观构想。在掌握了业务需求后,我们需要对中层管理人员进行调研,核心问题是搞清楚在宏观战略目标落地的这层,或者说指标细化并负责实施的中层他们对软件系统的期望以及实际要求,他们或希望此系统能够带来工作便利,或希望此系统能够做到精细化管理,如此等等。但他们都是具体的业务部门负责人,对自身的业务以及系统对业务的促进方面,有比较深刻的体会。最后,我们需要在掌握了业务需求、用户需求的基础之上,通过对IT管理部门、主要操作人员的需求调研或根据我们对需求的理解,细化出系统的功能需求,这个需求是最低层次的需求,也是一个层层落地的过程。
  (二)需求分析与验证
  在获取到软件项目需求后,接下来的工作就是对需求进行分析与验证,在项目的实际操作过程中,主要包括:需求分析建模、需求规格说明书编写和需求评审三个大的阶段。
  需求分析建模主要是对已搜集到的信息进行提炼、分析和仔细审查,为最终用户所看到的系统建立一个概念模型,确保所有干系人都明白其含义,并能找出其中的错误、遗漏或不足。需求分析是软件项目需求管理的最重要一环。
  在需求分析与建模过程中,对于用户需求不确定或用户无法清晰表述需求的,为了加快项目进度,我们往往采用原型法进行需求分析与建模,即根据我们的经验以及对用户基本需求的理解,用Axure等原型设计工具搭建一套原型系统。另外,我们还需要采用UML工具进行用例分析、用例描述等,并最终编写形成《软件需求规格说明书》。
  需求验证或需求评审是衡量需求阶段产出成果的重要手段,在完成需求分析与建模后,项目相关干系人应组织召开需求评审会,邀请相关专家、外部相关单位等进行需求评审,就需求分析的结果《需求规格说明书》、原型系统等进行评审,并对评审结果进行签字确认,确保需求没有偏离用户要求,又略高于用户要求。
  (三)需求变更控制
  需求管理贯穿于软件项目开发的全生命周期,在完成需求获取、分析与验证等任务后,项目组将根据形成的相关报告进行系统设计、编码、测试、发布等工作,这些过程其实都会涉及到需求的变更,这就需要我们有一套较好的机制和方法来管理和控制需求变更,以便于项目能够按期保质又在成本范围内完成。
  通常的做法是我们为了避免需求变更的无序、频繁、过度,在项目启动时会制定一套章程,会有一个CCB(变更控制委员会),通过召开项目启动会的方式,给相关干系人确定项目的实施方法、里程碑、沟通计划,并着重强调需求变更的流程。
  在实际操作中,首先是通过VSS等版本控制工具对需求文档进行管理,建立需求基线,并通过需求跟踪矩阵对需求项进行详细标注。
  其次,是在项目执行过程中对需求进行变更控制,有一套规范的流程,过程虽然繁琐,但能够给项目的风险控制带来很好的效果。用户提出需求变更申请,由项目实施团队进行需求变更的评估,评估包括可能造成的对系统其它功能的影响、实施此次变更需要投入的工作量等,评估完成后由变更控制委员会确定是否同意变更。如果同意,则由项目组进行变更的实施,并对上线后的变更内容以及整个系统进行验证,确保不影响系统运行和操作。如果不同意,则变更不成立,直接驳回用户方。通过这样一种虽然看似繁琐的方法能够很好地进行需求变更的控制,可以有效避免无序、无理、过度的需求变更,确保项目在可控范围内实施。
  以上是我们对软件项目需求管理的一点认识,软件需求管理之所以重要,主要是因为绝大多数项目的失败主要由需求的理解不到位、需求的变更没有得到有效控制等原因造成的。因此,这就要求我们在软件项目的需求管理方面,要下更大的力气去做好需求的获取、分析、变更控制,结合项目管理的相关理论,如PMBOOK、CMMI等,在项目实践中,不断总结经验教训,做好需求管理。

posted @ 2014-07-31 10:05 顺其自然EVO 阅读(208) | 评论 (0)编辑 收藏

Silk Mobile – 缩水移动应用的测试周期

Micro Focus已将从Borland接管的Silk Mobile?投放到市场,作为一种新的强大的移动应用测试解决方案,它将使企业能够开发出更先进更可靠的移动商业软件。
  作为市场上最完整的移动应用测试解决方案,Silk Mobile为移动设备上的应用提供了强大、易用的功能测试方法。Silk Mobile能够进行跨平台的测试,支持Android, iOS, Blackberry, Windows Mobile, Symbian和HTML5在内的多种平台。该工具的手势支持包括了多点触摸,扫,拖动,缩放,滚动,专门为最终用户与设备交互的各种方式而定制。所以,用Silk Mobile编写的测试脚本可以将最终用户和不同设备之间各种交互方式捕获为成合适的信息与数据。为了增加测试脚本的多样性,Silk Mobile测试脚本可以导出为多种脚本语言,包括Java, C#, Python和Perl.
  在当今的数字时代,移动应用在所有的大型企业的商业策略中都扮演着非常重要的角色。事实上,Micro Focus最近委托的一项调查发现,超过75%的调查对象计划在接下来的两年里扩大从大型机到移动设备的各种业务。Silk Mobile促进和提高了移动应用的开发质量,使得商业公司可以和客户保持紧密联系,从而在竞争中获得领先。
  Micro Focus的Borland解决方案部总监Archie Roboostoff说:“开发机构比以往受到更多的持续的压力,他们需要更快速和频繁地交付高质量的移动应用。Silk Mobile为开发团队提供了能够帮助他们按期按预算交付产品的测试工具。我们的目标是让软件开发者的测试变得简单起来,而Silk Mobile恰恰做到了这一点。”
  Silk Mobile是Silk软件测试品牌的一部分,它提供了完整的自动测试以及软件质量管理解决方案。Silk产品和测试集相配合,能够确保整个软件被彻底地测试,从而高质量高可靠地交付客户。配合着Silk Mobile,Borland产品现在提供了一个全面的,端对端的应用测试解决方案,它包括移动自动测试,移动性能测试,移动网络速度模拟,以及移动测试管理。
  使用Silk Mobile创建健壮、可重复、可维护的移动应用测试套件
  Silk Mobile测试工具的特点和优点包括:
  *广泛的移动操作系统平台支持*
  Silk Mobile支持在最新的多种平台上进行软件测试,这些平台包括Android,iOS, Blackberry, Windows Mobile, Symbian和HTML5。无论公司策略如何,客户都有一个抉择:接受BYOD概念(译者注:Bring Your Own Device指携带自己的设备办公)或者继续支持标准设备。不论哪一种选择,Silk Mobile是跨越所有移动应用平台的唯一选择。
  *可视所以易用*
  通过可视化捕捉功能,直接在移动设备上录制测试,并且可视化地创建,回放和编辑这些测试。另外,可视化的操作和验证命令可以直接加入到测试脚本中而不用编写任何代码。
  *原生的图像和光学字符识别*
  测试功能不会因为操作系统的改变而受到影响,因为Silk Mobile使用了“三层方法”来识别导航和屏幕内容。客户永远不用停止测试来等待最新的操作系统发布,这意味着不会耽误自动测试的过程。
  *手势支持,像用户那样使用*
  像最终用户那样和移动设备进行交互。Silk Mobile支持多点触摸,扫,拖拽,放大缩小,滚动等操作。测试应用同时还和手势、虚拟键盘、报警/通知等互补,所以设备信息的获取对于捕捉设备是如何被实际被使用的来说是相当关键的。
  *广泛的脚本语言选择*
  Silk Mobile可以将测试脚本导出为Borland Silk4j, Silk4Net, JUnit, NUnit, C#, MSTest, Python和Perl。现在移动测试可以作为持续交付过程或是已有自动测试套件的一部分来运行。
  *集成的测试管理*
  使用Borland的Silk Center执行,计划,维护导出的移动测试脚本。另外,Silk Center的配置测试功能允许一个私有的设备中枢进行移动应用安全测试的管理。移动测试会变成“仅仅是另一个测试”,允许客户管理和追踪他们的移动应用,而忽略设备平台的差异性。
  关于Borland
  Borland软件公司成立于1983年,是世界级的需求、测试和变更管理解决方案提供商。作为Mirco Focus有限公司(FTSE 250)的一部分, Borland提供了横跨整个应用开发生命周期的多种开放、敏捷的工具,使得客户可以更好更快地开发软件。

posted @ 2014-07-31 10:03 顺其自然EVO 阅读(197) | 评论 (0)编辑 收藏

仅列出标题
共394页: First 上一页 74 75 76 77 78 79 80 81 82 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜