Kiwi,BDD行为测试框架–iOS攻城狮进阶必备技能

简介: Kiwi,BDD行为测试框架–iOS攻城狮进阶必备技能 http://www.ios122.com/ 简介 Kiwi 是一个适用于iOS开发的行为驱动测试框架,旨在提供一个足够简单易用的BDD库. 项目主页: https://github.com/kiwi-bdd/Kiwi 示例下载: https://github.com/ios122/ios1

Kiwi,BDD行为测试框架–iOS攻城狮进阶必备技能

http://www.ios122.com/

简介

Kiwi 是一个适用于iOS开发的行为驱动测试框架,旨在提供一个足够简单易用的BDD库.

使用Cocopods 安装

把 AmazingAppTests 改为你自己的工程中的Tests target的名字,比如我的是 iOS122Tests,然后更新即可:

为了快速测试Kiwi是否安装成功,你可以用下面的代码替换到你的 Tests目录下已有的文件中的默认内容,然后点击Xcode导航栏 Product->Test(或者使用快捷键 cmd + u),此时如果提示你 Test Failed,点击错误提示,会在左侧第四导航栏看到类似下面的错误:

如果不能看到上述错误信息,说明你的工程配置可能有问题,可以参考这里详细微调下: Getting Started with Kiwi 2.0

规则

Kiwi的规则由以下元素组成

  • #import "Kiwi.h" 导入Kiwi库.这应该在规则的文件开始处最先导入.
  • SPEC_BEGIN(ClassName) 和 SPEC_END 宏,用于标记 KWSpec 类的开始和结束,以及测试用例的分组声明.
  • registerMatchers(aNamespacePrefix) 注册所有使用指定命名空间前缀的匹配器.除了Kiwi默认的匹配器,这些匹配器也可以在当前规则中使用.
  • describe(aString, aBlock) 开启一个上下文环境,可包含测试用例或嵌套其他的上下文环境.
  • 为了使一个block中使用的变量真正被改变,它需要在定义时使用 __block 修饰符.
  • beforeAll(aBlock) 在所有内嵌上下文或当前上下文的itblock执行之前执行一次.
afterAll(aBlock) 在所有内嵌上下文或当前上下文的 it block执行之后执行一次. beforeEach(aBlock) 在所有包含的上下文环境的  itblock执行之前,均各执行一次.用于初始化指定上下文环境的代码,应该放在这里. afterEach(aBlock) 在所有包含的上下文环境的  itblock执行之后,均各执行一次. it(aString, aBlock) 声明一个测试用例.这里描述了对对象或行为的期望. specify(aBlock) 声明一个没有描述的测试用例.这个常用于简单的期望. pending(aString, aBlock) 可用于标记尚未完成的功能或用例,仅会使Xcode输出一个黄色警告.(有点TODO的赶脚) let(subject, aBlock) 声明一个本地工具变量,这个变量会在规则内所有上下文的每个  itblock执行前,重新初始化一次.

示例.

期望

期望,用来验证用例中的对象行为是否符合你的语气.一个期望,具有如下形式:[[subject should] someCondition:anArgument].此处 [subject should]是表达式的类型, ... someCondition:anArgument] 是匹配器的表达式.

示例:

should 和 shouldNot

[subject should] 和 [subject shouldNot] 表达式,类似于一个接收器,用于接收一个期望匹配器.他们后面紧跟的是真实的匹配表达式,这些表达式将真正被用于计算.

默认地,主语守卫(一种机制,可以保证nil不引起崩溃)也会在[subject should ]和 [subject shouldNot]被使用时创建.给 nil 发送消息,通常不会有任何副作用.但是,你几乎不会希望:一个表达式,只是为了给某个对象传递一个无足轻重的消息,就因为对象本身是nil.也就说,向nil对象本身发送消息,并不会有任何副作用;但是在BBD里,某个要被传递消息的对象是nil,通常是非预期行为.所以,这些表达式的对象守卫机制,会将左侧无法判定为不为nil的表达式判定为 fail失败.

标量装箱

“装箱”是固定术语译法,其实即使我们iOS常说的基本类型转NSObject类型(事实如此,勿喷).

部分表达式中,匹配器表达式的参数总是NSObject对象.当将一个标量(如int整型,float浮点型等)用于需要id类型参数的地方时,应使用theValue(一个标量)宏将标量装箱.这种机制也适用于: 当一个标量需要是一个表达式的主语(主谓宾,基本语法规则,请自行脑补)时,或者一个 存根 的值需要是一个标量时.

示例:

消息模式

在iOS中,常将调用某个实例对象的方法成为给这个对象发送了某个消息.所以”消息模式”中的”消息”,更多的指的的实例对象的方法;”消息模式”也就被用来判断对象的某个方法是否会调用以及是否会按照预期的方式调用.

一些 Kiwi 匹配器支持使用消息模式的期望.消息模式部分,常被放在一个表达式的后部,就像一个将要发给主语的消息一样.

示例:

期望:数值 和 数字

  • [[subject shouldNot] beNil]
  • [[subject should] beNil]
  • [[subject should] beIdenticalTo:(id)anObject] – 比较是否完全相同
  • [[subject should] equal:(id)anObject]
  • [[subject should] equal:(double)aValue withDelta:(double)aDelta]
  • [[subject should] beWithin:(id)aDistance of:(id)aValue]
  • [[subject should] beLessThan:(id)aValue]
  • [[subject should] beLessThanOrEqualTo:(id)aValue]
  • [[subject should] beGreaterThan:(id)aValue]
  • [[subject should] beGreaterThanOrEqualTo:(id)aValue]
  • [[subject should] beBetween:(id)aLowerEndpoint and:(id)anUpperEndpoint]
  • [[subject should] beInTheIntervalFrom:(id)aLowerEndpoint to:(id)anUpperEndpoint]
  • [[subject should] beTrue]
  • [[subject should] beFalse]
  • [[subject should] beYes]
  • [[subject should] beNo]
  • [[subject should] beZero]

期望: 子串匹配

  • [[subject should] containString:(NSString*)substring]
  • [[subject should] containString:(NSString*)substring options:(NSStringCompareOptions)options]
  • [[subject should] startWithString:(NSString*)prefix]
  • [[subject should] endWithString:(NSString*)suffix]

示例:

期望: 正则表达式匹配

  • [[subject should] matchPattern:(NSString*)pattern]
  • [[subject should] matchPattern:(NSString*)pattern options:(NSRegularExpressionOptions)options]

期望: 数量的变化

  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; }]
  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; } by:+1]
  • [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; } by:-1]

示例:

期望: 对象测试

  • [[subject should] beKindOfClass:(Class)aClass]
  • [[subject should] beMemberOfClass:(Class)aClass]
  • [[subject should] conformToProtocol:(Protocol *)aProtocol]
  • [[subject should] respondToSelector:(SEL)aSelector]

期望: 集合

对于集合主语(即,主语是集合类型的):

  • [[subject should] beEmpty]
  • [[subject should] contain:(id)anObject]
  • [[subject should] containObjectsInArray:(NSArray *)anArray]
  • [[subject should] containObjects:(id)firstObject, ...]
  • [[subject should] haveCountOf:(NSUInteger)aCount]
  • [[subject should] haveCountOfAtLeast:(NSUInteger)aCount]
  • [[subject should] haveCountOfAtMost:(NSUInteger)aCount]

对于集合键(即此属性/方法名对应/返回一个集合类型的对象):

  • [[[subject should] have:(NSUInteger)aCount] collectionKey]
  • [[[subject should] haveAtLeast:(NSUInteger)aCount] collectionKey]
  • [[[subject should] haveAtMost:(NSUInteger)aCount] collectionKey]

如果主语是一个集合(比如 NSArray数组), coollectionKey 可以是任何东西(比如 items),只要遵循语法结构就行.否则, coollectionKey应当是一个可以发送给主语并返回集合类型数据的消息.

更进一步说: 对于集合类型的主语,coollectionKey的数量总是根据主语的集合内的元素数量, coollectionKey 本身并无实际意义.

示例:

期望: 交互和消息

这些期望用于验证主语是否在从创建期望到用例结束的这段时间里接收到了某个消息(或者说对象的某个方法是否被调用).这个期望会同时存储 选择器或参数等信息,并依次来决定期望是否满足.

这些期望可用于真实或模拟的独享,但是在设置 receive 表达式时,Xcode 可能会给警告(报黄).

对参数无要求的选择器:

  • [[subject should] receive:(SEL)aSelector]
  • [[subject should] receive:(SEL)aSelector withCount:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector withCountAtLeast:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector withCountAtMost:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCount:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtMost:(NSUInteger)aCount]

含有指定参数的选择器:

  • [[subject should] receive:(SEL)aSelector withArguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCount:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCountAtLeast:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector withCountAtMost:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withArguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCount:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount arguments:(id)firstArgument, ...]
  • [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtMost:(NSUInteger)aCount arguments:(id)firstArgument, ...]

示例:

注意你可以将 any()通配符用作参数.如果你只关心一个方法的部分参数的值,这回很有用:

期望:通知

  • [[@"MyNotification" should] bePosted];
  • [[@"MyNotification" should] bePostedWithObject:(id)object];
  • [[@"MyNotification" should] bePostedWithUserInfo:(NSDictionary *)userInfo];
  • [[@"MyNotification" should] bePostedWithObject:(id)object andUserInfo:(NSDictionary *)userInfo];
  • [[@"MyNotification" should] bePostedEvaluatingBlock:^(NSNotification *note)block];

Example:

期望: 异步调用

  • [[subject shouldEventually] receive:(SEL)aSelector]
  • [[subject shouldEventually] receive:(SEL)aSelector withArguments:(id)firstArgument, ...]

期望: 异常

  • [[theBlock(^{ ... }) should] raise]
  • [[theBlock(^{ ... }) should] raiseWithName:]
  • [[theBlock(^{ ... }) should] raiseWithReason:(NSString *)aReason]
  • [[theBlock(^{ ... }) should] raiseWithName:(NSString *)aName reason:(NSString *)aReason]

示例:

自定义匹配器

Kiwi中,自定义匹配器的最简单方式是创建KWMatcher的子类,并以适当的方式重写下面示例中的方法.

为了让你自定义的匹配器在规则中可用,你需要在规则中使用registerMatchers(namespacePrefix)进行注册.

看下Kiwi源文件中的匹配器写法(如KWEqualMatcher等),将会使你受益匪浅.

示例:

模拟对象

模拟对象模拟某个类,或者遵循某个写一个.他们让你在完全功能完全实现之前,就能更好地专注于对象间的交互行为,并且能降低对象间的依赖–模拟或比避免那些运行规则时几乎很难出现的情况.

模拟 Null 对象

通常模拟对象收到一个非预期的选择器或消息模式时,会抛出异常(PS:iOS开发常见错误奔溃之一).在模拟对象上使用 stub 或 receive期望,期望的消息会自动添加到模拟对象上,以实现对方法的模拟.

如果你不关心模拟对象如何处理其他非预期的消息,也不想在收到非预期消息时抛出异常,那就使用 null 模拟对象吧(也即 null 对象).

模拟类的实例

创建类的模拟实例(NSObject 扩展):

  • [SomeClass mock]
  • [SomeClass mockWithName:(NSString *)aName]
  • [SomeClass nullMock]
  • [SomeClass nullMockWithName:(NSString *)aName]

创建类的模拟实例:

  • [KWMock mockForClass:(Class)aClass]
  • [KWMock mockWithName:(NSString *)aName forClass:(Class)aClass]
  • [KWMock nullMockForClass:(Class)aClass]
  • [KWMock nullMockWithName:(NSString *)aName forClass:(Class)aClass]

模拟协议的实例

创建遵循某协议的实例:

  • [KWMock mockForProtocol:(Protocol *)aProtocol]
  • [KWMock mockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol]
  • [KWMock nullMockForProtocol:(Protocol *)aProtocol]
  • [KWMock nullMockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol]

存根

存根,能返回指定定选择器或消息模式的封装好的请求.Kiwi中,你可以存根真实对象(包括类对象)或模拟对象的方法.没有指定返回值的存根,将会对应返回nil,0等零值.存根需要返回标量的,标量需要使用 theValue(某个标量)宏 装箱.

所有的存根都会在规范的一个例子的末尾(一个itblock)被清除.

存根选择器:

  • [subject stub:(SEL)aSelector]
  • [subject stub:(SEL)aSelector andReturn:(id)aValue]

存根消息模式:

  • [ [subject stub] *messagePattern*]
  • [ [subject stubAndReturn:(id)aValue] *messagePattern*]

示例:

捕捉参数

有时,你可能想要捕捉传递给模拟对象的参数.比如,参数可能没有是一个没有很好实现 isEqual: 的对象,如果你想确认传入的参数是否是需要的,那就要单独根据某种自定义规则去验证.另外一种情况,也是最长遇到的情况,就是模拟对象接收的消息的某个参数是一个block;通常必须捕捉并执行这个block才能确认这个block的行为.

示例:

存根的内存管理问题

未来的某天,你或许需要存根alloc等法官法.这可能不是一个好主意,但是如果你坚持,Kiwi也是支持的.需要提前指出的是,这么做需要深入思考某些细节问题,比如如何管理初始化.

Kiwi 存根遵循 Objective-C 的内存管理机制.当存根将返回值写入一个对象时,如果选择器是以alloc,或new开头,或含有 copy时,retain消息将会由存根自动在对象发送前发送.

因此,调用者不需要特别处理由存根返回的对象的内存管理问题.

警告

Kiwi深度依赖Objective-C的运行时机制,包括消息转发(比如forwardInvocation:).因为Kiwi需要预先判断出来哪些方法可以安全调用.使用Kiwi时,有一些惯例,也是你需要遵守的.

为了使情况简化和有条理,某些方法/选择器,是决不能在消息模式中使用,接收期望,或者被存根;否则它们的常规行为将会被改变.不支持使用这些控制器,而且使用后的代码的行为结果也会变的很奇怪.

在实践中,对于高质量的程序代码,你可能不需要担心这些,但是最好还是对这些有些印象.

黑名单(使用有风险):

  • 所有不在白名单中的NSObject类方法和NSObject协议中的方法.(比如-class-superclass-retain-release等.)
  • 所有的Kiwi对象和方法.

白名单(可安全使用):

  • +alloc
  • +new
  • +copy
  • -copy
  • -mutableCopy
  • -isEqual:
  • -description
  • -hash
  • -init
  • 其他任何不在NSObject类或NSobject协议中的方法.

异步测试

iOS应用经常有组件需要在后台和主线程中内容沟通.为此,Kiwi支持异步测试;因此就可以进行集成测试-一起测试多个对象.

expectFutureValue() 和 shouldEventually

为了设置异步测试,你 必须 使用 expectFutureValue 装箱,并且使用shouldEventually 或 shouldEventuallyBeforeTimingOutAfter来验证.

shouldEventually 默认在判定为失败前等待一秒.

标量的处理

当主语中含有标量时,应该使用 expectFutureValue中使用 theValue装箱标量.例如:

shouldEventuallyBeforeTimingOutAfter()

这个block默认值是2秒而不是1秒.

反转

也有shouldNotEventually和 shouldNotEventuallyBeforeTimingOutAfter 的变体.

一个基于LRResty的示例:

这个block会在匹配器满足或者超时(默认: 1秒)时完成.

This will block until the matcher is satisfied or it times out (default: 1s)

目录
相关文章
|
1天前
|
Java 中间件 测试技术
深入理解自动化测试框架Selenium的设计与实现
【5月更文挑战第10天】 本文旨在深度剖析自动化测试工具Selenium的核心架构与实现机制,通过对其设计理念、组件结构以及在实际软件测试中的应用进行详细解读,使读者能够全面理解Selenium在现代Web应用测试中的重要性和有效性。文章首先介绍Selenium的发展背景及其解决的问题,然后详细探讨其架构设计,包括各种驱动和API的作用,最后结合实际案例分析Selenium如何提高测试效率和准确性。
|
3天前
|
测试技术
测试基础 Junit单元测试框架
测试基础 Junit单元测试框架
11 2
测试基础 Junit单元测试框架
|
4天前
|
Java 测试技术 持续交付
自动化测试框架选型与实战:深入探索与应用
【5月更文挑战第8天】本文探讨了自动化测试框架的选型与实战应用,强调了其在软件质量保障中的重要性。选型原则包括考虑项目需求、技术栈、可扩展性和可维护性,以及社区支持和文档。介绍了Selenium、Appium、JUnit和Pytest等常用框架,并概述了实战应用的步骤,包括明确需求、搭建环境、编写测试用例、执行测试、分析结果、维护代码和持续集成。合理选型与实践能提升测试效率,保障项目成功。
|
6天前
|
敏捷开发 测试技术 持续交付
深入理解自动化测试:框架与实践
【5月更文挑战第5天】 在现代软件开发周期中,自动化测试已成为确保产品质量和加速交付过程的关键环节。本文将深入探讨自动化测试的核心概念、框架选择以及实际实施过程中的最佳实践。通过分析各种自动化测试工具和技术的优缺点,我们旨在为读者提供一种系统化的方法来构建和维护有效的自动化测试环境。
|
7天前
|
机器学习/深度学习 人工智能 自然语言处理
自动化测试中AI驱动的决策框架设计与实现
【5月更文挑战第5天】 在软件测试领域,自动化测试已成为提升测试效率和质量的关键手段。然而,随着软件系统的复杂性增加,传统的自动化测试方法面临挑战,尤其在测试用例的生成、执行及结果分析等方面。本文提出一种基于人工智能(AI)的自动化测试决策框架,旨在通过智能化的算法优化测试过程,并提高异常检测的准确率。该框架结合机器学习和深度学习技术,能够自学习历史测试数据,预测高风险变更区域,自动生成针对性强的测试用例,并在测试执行过程中实时调整测试策略。此外,通过自然语言处理(NLP)技术,该框架还能对测试结果进行语义分析,进一步提供更深入的洞察。本研究不仅增强了自动化测试工具的智能性,也为软件质量保证提
|
11天前
|
数据管理 测试技术
深入理解自动化测试框架:以Selenium为例
【4月更文挑战第30天】 随着软件开发的快速发展,自动化测试已经成为保证软件质量和提升开发效率的重要手段。本文将深入探讨自动化测试框架的核心概念,并以广泛应用的开源工具Selenium为例,解析其架构、原理及在实际项目中的运用。通过实例分析与性能评估,旨在为读者提供一套系统的自动化测试解决方案,并探讨其在复杂应用场景下的优化策略。
|
11天前
|
敏捷开发 前端开发 JavaScript
深入理解自动化测试框架:以Selenium为例
【4月更文挑战第30天】 在现代软件开发过程中,自动化测试已成为确保产品质量和加快市场投放的关键步骤。本文聚焦于流行的自动化测试框架——Selenium,探讨其架构、核心组件以及如何有效地利用Selenium进行Web应用测试。通过分析真实案例,我们将揭示Selenium在实际项目中的应用优势与面临的挑战,并提出优化策略。文章的目的在于帮助测试工程师深入理解Selenium,提升其在复杂项目中的运用效率。
|
11天前
|
前端开发 IDE 数据可视化
深入理解与应用自动化测试框架Selenium的最佳实践
【4月更文挑战第30天】 本文将深入剖析自动化测试框架Selenium的核心原理,并结合最佳实践案例,探讨如何有效提升测试覆盖率和效率。文中不仅涉及Selenium的架构解析,还将提供针对性的策略来优化测试脚本,确保测试流程的稳定性与可靠性。通过实例演示,读者可以掌握如何在不同测试场景中灵活运用Selenium,以及如何处理常见的技术挑战。
|
11天前
|
JavaScript 安全 编译器
【TypeScript 技术专栏】TypeScript 与 Jest 测试框架
【4月更文挑战第30天】本文探讨了TypeScript与Jest测试框架的结合在确保代码质量和稳定性上的重要性。Jest以其易用性、内置断言库、快照测试和代码覆盖率分析等特点,为TypeScript提供全面的测试支持。两者结合能实现类型安全的测试,提高开发效率,并涵盖各种测试场景,包括异步操作。通过实际案例分析,展示了如何有效利用这两个工具提升测试质量和开发效率,为项目成功奠定基础。
|
11天前
|
敏捷开发 监控 前端开发
深入理解与应用自动化测试框架:以Selenium为例
【4月更文挑战第30天】 在软件开发的快速迭代周期中,质量保证(QA)团队面临持续的压力,需确保产品在每次发布时都达到预期的质量标准。为了应对这一挑战,自动化测试成为了关键工具,它不仅提高了测试效率,还确保了测试的一致性和可重复性。本文将探讨自动化测试框架Selenium的核心组件、工作原理及其在实际测试中的应用。通过分析Selenium的优势和面临的常见问题,我们将讨论如何有效地集成Selenium到现有的测试流程中,以及如何克服常见的技术障碍。我们的目标是为读者提供一个清晰的指南,帮助他们理解和利用自动化测试框架来优化他们的软件测试实践。