3

翻译:@shiweifu
本文链接:http://segmentfault.com/blog/shiweifu
原文链接:http://rubymotion-tutorial.com/8-testing/
目标读者:["想了解RubyMotion开发模式", "想学习RubyMotion", "对移动端测试感兴趣"]
翻译者按:测试是移动开发的一个痛点。这篇文章讲述了使用RubyMotion如何进行有效的测试,可以看出相对于原生开发环境的测试,简化很多。这部分也是RubyMoiton的一大特色。


除了语法不同,你可能觉得 RubyMotion 不过是提供了一条使用 Ruby 语法来编写 Cocoa 程序的方式。和使用 Objective-C 来实现同样的功能相比,并无特殊之处。本章将介绍 RubyMotion 中独有的特性。

自动化测试是屌炸天的事儿。屌在哪?它能使你的程序更加健壮,通过一些测试代码,能让你及时发现问题所在,是不是很棒?

是的,大家都认为测试是个好东西,但事实上大多数工程师并不能坚持写测试。它不能让你有足够的成就感,不是能和其他人吹嘘的牛逼特性或者性能提升。但它是一种保障,特别是项目经常改变,它能让你知道你的代码究竟可不可用。

接下来我们将了解 RubyMotion 中的测试,简单编写,覆盖度高。

在 Ruby 社区,大家都很重视测试,Ruby 的测试相对 iOS App,也相对简洁。如果要在iOS 中实现自动化测试,往往需要借助第三方库或者使用JavaScript。RubyMotion 的测试库要好用的多。

到底有多屌?

Unit Testing

使用 motion create Tests 命令新建一个项目然后cd进去。我们讨论里面的spec文件夹。

这个文件夹里有一个名为./spec/main_spec.rb的文件,这是创建项目的时候自动生成的。在 RubyMotion 的测试工作的时候,它会加载这个文件夹里面的*.rb文件。我们来看下这个文件的内容:

describe "Application 'Tests'" do
  before do
    @app = UIApplication.sharedApplication
  end

  it "has one window" do
    @app.windows.size.should == 1
  end
end

可以看到一个简单的表达式:@app.windows.size.should == 1,它的意思看起来是如果.size不为1,就测试失败。

.should 支持以下的判断类型:

@app.nil?.should == false

[1,2,3].should.not == [1,2,3,4]

@model.id.should == example_id

describeit 组成了一个用来实现测试的结构。在上面的这个describe结构中,传达了两个意思:"[Test that] Application",被测试对象有一Window。一个describe结构中可以包含多个itit中又可以包含多个测试断言。你如果喜欢的话,也可以写多个describe

每一个将被测试的内容都会先执行before中的内容。应该把一些初始化的代码丢到这里。

接下来在终端中,运行rake spec,看看测试的结果。

image

还是挂了……提示说没有找到window。让我们在AppDelegate中修复:

class AppDelegate
  def application(application, didFinishLaunchingWithOptions:launchOptions)
    @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
    @window.makeKeyAndVisible
    true
  end
end

再执行一次rake spec,这次正确了:

image

咦,似乎已经通过自动化测试解决了一个Bug,但愿是真的解决了吧。

我们已经看到了如何去检查一个对象的属性,这很有用但还不够。有时候我们会触发事件,比如我们敲击了一个按钮,它调用了一个内部方法,这种情况下,我们要测试需要去手动调用这个方法,然后进行测试……事实上有更好的做法。

Funcational Testing

RubyMotion 对 Funcational Testing 有着很好的支持,比如你想测试触发 UI 事件,如TapSwipe 然后检查它的结果,你不用去button.callback.call,你可以用等效的方法:tap button,测试用例很整洁,对不对?

Funcational Testing虽然屌屌的,但它的局限是它只能测试一个UIViewController。所以像pushpop这种就无能为力了。需要注意。

要接着跑通这个例子,我们需要一个UIViewController的子类,创建./app/ButtonController.rb,然后增加一个按钮和回调事件。代码如下:

class ButtonController < UIViewController
  def viewDidLoad
    super

    @button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
    @button.setTitle("Test me title!", forState:UIControlStateNormal)
    @button.accessibilityLabel = "Test me!"
    @button.sizeToFit
    @button.frame = CGRect.new([10, 70], @button.frame.size)
    self.view.addSubview(@button)

    @button.addTarget(self, action:'tapped', forControlEvents:UIControlEventTouchUpInside)
  end

  def tapped
    p "I'm tapped!"
    @was_tapped = true
  end
end

标准的代码,除了accessibilityLabel。这是每个View都包含的一个String类型属性。当使用VoiceOver功能的时候,系统依赖这个属性(所以别随便设置这个属性的值)。为啥我们会提到这一点?因为 RubyMotion 的Funcational Testing就依赖这个属性。所以确保要测试的View已经设置好这个属性。

最后,我们把ControllerAppDelegate关联起来:

def application(application, didFinishLaunchingWithOptions:launchOptions)
  @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
  @window.makeKeyAndVisible

  @view_controller = ButtonController.alloc.initWithNibName(nil, bundle:nil)
  @window.rootViewController = @view_controller

  true
end

你可以使用rake执行一下,看看在你触发按钮事件的时候终端有没有输出I'm tapped!。接下来我们写测试代码,看看变量是否真的被赋值。

main_spec.rb,添加describe块及代码:

describe "button controller" do
  tests ButtonController

  it "changes instance variable when button is tapped" do
    tap 'Test me!'
    controller.instance_variable_get("@was_tapped").should == true
  end
end

让我们来看看有啥新东西。

tests <class>连接我们的describe,指定UIViewController。它的行为很清晰:将要测试的UIViewController放在了一个新的UIWindow中。我们能使用self.windowself.controller访问其。这样做也确保了被测试的Controller不会被干扰。

tap可以替换成flickdragpinch_closepinch_openrotate对应相应的行为。查看RubyMotion's full documentation具体的细节。

rake spec,它已经能正常工作啦:

2 specifications (2 requirements), 0 failures, 0 errors

总结

我们看到了在RubyMotion中编写测试是多么的简单。如果你之前因为繁琐不写测试,现在没理由懒惰了。

我们学到了啥?

  • RubyMotion加载测试用例从./spec目录。
  • 测试用例包含在describeit中,后面跟随着标签,用来标识和组织。
  • 使用 <any object>.should 进行断言。例:greeting.should == "hello"
  • UIViewControllers可以进行funcational。 tests,可以模拟一些像tappinch触发事件。在你的describe代码块中,使用tests <controller class>去使用这些特性。
  • tap <accessibility label>是访问View的accessibilityLabel属性。

shiweifu
2k 声望744 粉丝