翻译:@shiweifu
本文连接:http://segmentfault.com/blog/shiweifu
原文连接:http://rubymotion-tutorial.com/8-testing/
目标读者:["想了解RubyMotion开发模式", "想学习RubyMotion", "对移动端测试感兴趣"]
翻译者按:测试是移动开发的一个痛点。这篇文章讲述了使用RubyMotion如何进行有效的测试,能够看出相对于原生开发环境的测试,简化不少。这部分也是RubyMoiton的一大特点。segmentfault
除了语法不一样,你可能以为 RubyMotion 不过是提供了一条使用 Ruby 语法来编写 Cocoa 程序的方式。和使用 Objective-C 来实现一样的功能相比,并没有特殊之处。本章将介绍 RubyMotion 中独有的特性。windows
自动化测试是屌炸天的事儿。屌在哪?它能使你的程序更加健壮,经过一些测试代码,能让你及时发现问题所在,是否是很棒?ruby
是的,你们都认为测试是个好东西,但事实上大多数工程师并不能坚持写测试。它不能让你有足够的成就感,不是能和其余人吹嘘的牛逼特性或者性能提高。但它是一种保障,特别是项目常常改变,它能让你知道你的代码究竟可不可用。app
接下来咱们将了解 RubyMotion 中的测试,简单编写,覆盖度高。ide
在 Ruby 社区,你们都很重视测试,Ruby 的测试相对 iOS App,也相对简洁。若是要在iOS 中实现自动化测试,每每须要借助第三方库或者使用JavaScript。RubyMotion 的测试库要好用的多。性能
到底有多屌?学习
使用 motion create Tests
命令新建一个项目而后cd
进去。咱们讨论里面的spec
文件夹。测试
这个文件夹里有一个名为./spec/main_spec.rb
的文件,这是建立项目的时候自动生成的。在 RubyMotion 的测试工做的时候,它会加载这个文件夹里面的*.rb
文件。咱们来看下这个文件的内容:ui
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,就测试失败。spa
.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,希望是真的解决了吧。
咱们已经看到了如何去检查一个对象的属性,这颇有用但还不够。有时候咱们会触发事件,好比咱们敲击了一个按钮,它调用了一个内部方法,这种状况下,咱们要测试须要去手动调用这个方法,而后进行测试……事实上有更好的作法。
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 <class>
链接咱们的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中编写测试是多么的简单。若是你以前由于繁琐不写测试,如今没理由懒惰了。
咱们学到了啥?
./spec
目录。describe
和it
中,后面跟随着标签,用来标识和组织。<any object>.should
进行断言。例:greeting.should == "hello"
。UIViewControllers
能够进行funcational。 tests,能够模拟一些像tap
、pinch
触发事件。在你的describe
代码块中,使用tests <controller class>
去使用这些特性。tap <accessibility label>
是访问View的accessibilityLabel
属性。