Robot 0.1.0

Robot 0.1.0

测试已测试
语言语言 Obj-CObjective C
许可证 Apache 2
发布日期最后发布日期2015年5月

Jeff Hui 维护。



Robot 0.1.0

  • 作者:
  • Jeff Hui

UIKit 集成测试库。通过 UIKit 便捷地模拟高级用户交互。

与 KIF 不同,Robot 旨在不是完美地模拟用户如何与系统交互。相反,它试图复制相同的行为,同时最大限度地减少基于时间的操作的开销。一个完美的例子是禁用动画以加速测试的运行。

与 KIF 一样,Robot 也不是一个完整的集成测试解决方案。它更倾向于依赖其他测试框架来进行断言和运行。除了 XCTest 之外,还有一些流行的行为驱动测试框架:

  • Cedar
  • Specta / Expecta
  • Kiwi

同样,它也使用私有 API。

// tap on a cancel button/label
tapOn(theFirstView(withLabel(@"Cancel")));

安装

使用 Cocoapods

pod 'Robot'

或者将其作为子项目导入并链接 IOKit。最简单的方法是在您的 "其他链接器标志" 构建设置中添加它。

-framework IOKit

组件

此库包括四个主要部分:

  • 视图查询 - 提供查找视图和管理警报的 API。
  • 键盘 - 提供与系统默认键盘交互的 API。
  • 触摸 - 提供与视图交互的 API。
  • 时间缩放 - 提供加速基于时间的操作(动画 + 运行循环)的 API。

视图查询

此组件提供了一种用于查找视图的 DSL。它们是基于递归子视图遍历和 NSPredicate 构建。

以下是一些查找视图的核心函数:

  • RBViewQuery *allViews(NSPredicate *predicate) - 返回所有(包括子视图)满足给定范围中谓词的视图。默认范围为 keyWindow。
  • RBViewQuery *allSubviews(NSPredicate *predicate) - 返回所有满足给定范围中谓词的子视图。默认范围为 keyWindow。
  • RBViewQuery *theFirstView(NSPredicate *predicate) - 返回给定范围中满足谓词的第一个视图(或子视图)。默认范围为 keyWindow。
  • RBViewQuery *theFirstSubview(NSPredicate *predicate) - 返回给定范围中满足谓词的第一个子视图。默认范围为 keyWindow。

谓词

所有核心方法都接受一个谓词来检查每个视图是否满足要求。您可以从 NSPredicate 创建自己的,但 Robot 还提供了一些内置的谓词以便组合使用。

where(NSString *formatString, ...) 是对 +[NSPredicate predicateWithFormat:predicateFormat, ...] 的一个别名。同样地,NSPredicate *where(BOOL(^)(UIView *view)) 是对 +[NSPredicate predicateWithBlock:matcher] 的一个别名

// finds all views that have more than 2 subviews
allViews(where(@"subviews[SIZE] > %@", @2));

// finds all views that have a tag of 3
allViews(where(^BOOL(UIView *view){
    return view.tag == 3;
}));

类似地,matching(...) 宏是对 +[NSCompoundPredicate andPredicateWithSubpredicates:@[...]] 的一个别名

// find all views with tag of 3 with more than 2 subviews.
allViews(matching(where(@"subviews[SIZE] > 2"), where(@"tag == 3")));

可以通过视图类对视图进行过滤的方法

// find all UITextViews, but not subclasses
allViews(ofExactClass([UITextView class]));
allViews(ofExactClass(@"UITextView"));

// find all UIButtons and subclasses
allViews(ofClass([UIButton class]));
allViews(ofClass(@"UIButton"));

您也可以使用另一个谓词过滤父视图

// find all views that have UIViews as superviews
allViews(withParent(ofExactClass([UIView class])));

// find all views that have superviews that have UIView classes. This includes
// the root view.
allViews(includingSuperViews(ofExactClass([UIView class])));

// all views, excluding the root view
allViews(withoutRootView());

或者通过视图内容

// find any views with the text of "Cancel"
allViews(withText(@"Cancel"));

// find any views with the text or accessibilityLabel of "Cancel"
allViews(withLabel(@"Cancel"));

// find any views with the EXACT image
allViews(withImage([UIImage imageNamed:@"myImage"]));

// find any views behaviorally acts like a button
allViews(withTraits(UIAccessibilityTraitButton));

// find any views that are accessible
allViews(withAccessibility(YES));

最后,通过可见性

// find all views that are visible (isHidden = NO and alpha > 0 and a drawable pixel)
// a drawable pixel is where clipsToBounds is NO or a non-zero size
allViews(withVisibility(YES));

// find all views that are on screen. On screen means the view's rect intersects or is
// inside the window. If not in a window, the root view is used instead.
allViews(onScreen(YES));

// find all views that are visible and on screen -- including all their superviews.
// This is a combination of withVisibility() and onScreen() with includingSuperViews().
allViews(onScreenAndVisible(YES));

细化查询

所有核心查询方法都返回 RBViewQuery,它是视图的懒加载 NSArray。它们可以通过属性块进一步细化。例如,要将查询限制为特定视图

// returns views with text "hello" that are either myView or any of its subviews
allViews(withText(@"Hello")).inside(myView);

// if you want to search inside multiple disperate view hierarchies
allViews(withText(@"Hello")).insideOneOf(@[myView1, myView2]);

也可以使用 NSSortDescriptors 数组来应用排序

// sort all the views by smallest origin first. Smallest is by y first, then x.
allViews(...).sortedBy(@[smallestOrigin()]);
// reverse sort
allViews(...).sortedBy(@[largestOrigin()]);

// sort all the views by smallest size first. Smallest is by height first, then width.
allViews(...).sortedBy(@[smallestSize()]);
// reverse sort
allViews(...).sortedBy(@[largestSize()]);

所有这些都可以链式调用

allViews(...).inside(myView).sortedBy(@[smallestOrigin()]);

表格视图

如果没有模型来检查滚动之外的内容,那么检查表格视图的行为仍然很麻烦。请使用 RBTableViewCellsProxy

RBTableViewCellsProxy *cells = [RBTableViewCellsProxy cellsFromTableView:tableView];
cells[0] // -> Returns proxy to the first table view's cell
[cells[0] textLabel].text // works as expected
cells[100] // -> Returns another proxy
[cells[100] textLabel].text // works. Table view is scrolled before accessed.

键盘

Robot 利用基本接口封装了 UIKit 的键盘,以控制它。该接口位于 RBKeyboard

// focus a text field to get keyboard focus
tapOn(textField);

// type through the keyboard
[[RBKeyboard mainKeyboard] typeString:@"Hello World!"];

// dismiss the keyboard - you must always do this otherwise the next
// time you use the keyboard it might crash.
[[RBKeyboard mainKeyboard] dismiss];

要在键盘上键入特殊字符,请使用 -[typeKey:] 取代

// press delete key
[[RBKeyboard mainKeyboard] typeKey:RBKeyDelete];

触摸

Robot 实现了自己的 UITouch 子类,RBTouch,通过您的应用程序来模拟触摸事件。您可以使用此类来模拟任何复杂的触摸交互。

除了 RBTouch 之外,还有 DSL 函数,它可以保持测试中的语法简洁。

最常见的操作是点击元素

tapOn(myButton);
tapOn(myViewQuery);

但它支持更复杂的手势

swipeLeftOn(myView);
swipeUpOn(myView);
swipeDownOn(myView);
swipeRightOn(myView);

时间流逝

根据需要,Robot 可以选择性地加快特定操作。要禁用测试时的动画并调用任何完成块,请使用 -[disableAnimationsInBlock:] API

[RBTimeLapse disableAnimationsInBlock:^{
    [UIView animateWithDuration:2 animations:^{
        view.x = 200;        
    } completion:^(BOOL finished){
        view.hidden = YES;
    }];
}];

view.isHidden // => YES;

内部,RBTimeLapse 将在禁用动画的同时推进运行循环,并将计时器延迟设置为零。

如果您只想要后者而不禁用动画,可以这样做

[logger performSelector:@selector(logMessage:) withObject:@"hello" afterDelay:1];
[RBTimeLapse advanceMainRunLoop]; // calls [logger logMessage:@"hello"]

对于 tapOn,时间流逝是自动的,但对于任何其他手势则不是