翻译:@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
describe
和 it
组成了一个用来实现测试的结构。在上面的这个describe
结构中,传达了两个意思:"[Test that] Application"
,被测试对象有一Window
。一个describe
结构中可以包含多个it
,it
中又可以包含多个测试断言。你如果喜欢的话,也可以写多个describe
。
每一个将被测试的内容都会先执行before
中的内容。应该把一些初始化的代码丢到这里。
接下来在终端中,运行rake spec
,看看测试的结果。
还是挂了……提示说没有找到window。让我们在AppDelegate
中修复:
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.applicationFrame)
@window.makeKeyAndVisible
true
end
end
再执行一次rake spec
,这次正确了:
咦,似乎已经通过自动化测试解决了一个Bug,但愿是真的解决了吧。
我们已经看到了如何去检查一个对象的属性,这很有用但还不够。有时候我们会触发事件,比如我们敲击了一个按钮,它调用了一个内部方法,这种情况下,我们要测试需要去手动调用这个方法,然后进行测试……事实上有更好的做法。
Funcational Testing
RubyMotion 对 Funcational Testing
有着很好的支持,比如你想测试触发 UI 事件,如Tap
、Swipe
然后检查它的结果,你不用去button.callback.call
,你可以用等效的方法:tap button
,测试用例很整洁,对不对?
Funcational Testing
虽然屌屌的,但它的局限是它只能测试一个UIViewController
。所以像push
和pop
这种就无能为力了。需要注意。
要接着跑通这个例子,我们需要一个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已经设置好这个属性。
最后,我们把Controller
和AppDelegate
关联起来:
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
连接我们的describe
,指定UIViewController
。它的行为很清晰:将要测试的UIViewController
放在了一个新的UIWindow
中。我们能使用self.window
和self.controller
访问其。这样做也确保了被测试的Controller
不会被干扰。
tap
可以替换成flick
、drag
、pinch_close
、pinch_open
和rotate
对应相应的行为。查看RubyMotion's full documentation具体的细节。
rake spec
,它已经能正常工作啦:
2 specifications (2 requirements), 0 failures, 0 errors
总结
我们看到了在RubyMotion中编写测试是多么的简单。如果你之前因为繁琐不写测试,现在没理由懒惰了。
我们学到了啥?
- RubyMotion加载测试用例从
./spec
目录。 - 测试用例包含在
describe
和it
中,后面跟随着标签,用来标识和组织。 - 使用
进行断言。例:.should greeting.should == "hello"
。 -
UIViewControllers
可以进行funcational。 tests,可以模拟一些像tap
、pinch
触发事件。在你的describe
代码块中,使用tests
去使用这些特性。 -
tap
是访问View的accessibilityLabel
属性。