介绍一下自己几个月以来学习 Calabash 的经验,从一个对 Calabash 完全不了解的新手开始,到用 calabash 写过一个 iPad 项目,一个 Android 平板项目,外加一个 iPhone 项目的自动化测试,双手已经沾上一些 Calabash 的味道。好比一个小兵,端起枪上过战场,打过两场小规模战斗,长了点见识之后仍然是一个小兵,从一个完全的新手,变成一个尝过一点 Calabash 味道的新手。废话少说,下面介绍一下自己学习 Calabash 的经验:
最开始的时候,安装一个稳定的版本组合很重要。
具体的安装步骤,请参考官方文档。这里只是提醒一下,对于 Calabash 来说,不同的版本组合会碰到不同问题,至少我自己一开始碰到不少问题,经过测试的版本组合才能推荐给其他人,才能升级 CI 环境。
对于一个新手来讲,选择错误的版本组合,意味着你碰到奇怪错误的机会增加。而刚开始就不能顺利上手,而是去研究 Calabash,OS,iOS,SDK, 模拟器等各种版本兼容性问题是一件非常恼人的事情。虽然排错是很好的学习方式,但一开始就碰到兼容性问题可能会降低你的学习热情,以为 Calabash 只是一个不成熟的玩具,其实不然。
如果你刚接触 Calabash, 要看的东西并不多,下面这两个地方仔细看一遍就足够了。
iOS:
https://github.com/calabash/calabash-ios/wiki/_pages
Android:
https://github.com/calabash/calabash-android
其中的两个章节:03.5 Calabash iOS Ruby API 和 05 Query syntax, 在开始写脚本的时候会不断回头参考。
下面假设你的环境没问题了,接下来该找个 iOS 或 Android 的具体项目开始写测试,光看懂 Github 上那个简单的 demo,理解大致的原理远远不够, 真正动手写 Calabash 脚本的时候,你会碰到很多问题。具体来说,刚一开始你最常碰到的问题是:
对于第一个问题,如果你的 app 比较简单,元素的 id, name 属性齐全,可能问题不大。如果能看懂并修改源代码,或者让开发人员帮你补上帮助识别元素的属性最好。如果这个方法行不通,很多时候识别一个元素有很多种写法,99% 的可能你能找到替代的写法。这个时候是你熟悉 Calabash console, 熟悉 Query syntax 的最好时机。启动 Calabash console, 定位你想要的元素,尝试使用各种 Query syntax 定位元素。
相对于 Web 浏览器上的开发工具,Calabash 识别元素的方法还比较 “原始”,需要用 start_test_server_in_background 打开 console, 然后利用 query 去搜索想要的元素,比在 Web 浏览器上定位一个元素麻烦很多。calabash-ios 提供了一个 flash 方法,用来确定元素找得是否准确,但 calabash-android 没有提供对等的方法,所以不能用元素高亮来定位。
值得一提的是,在我测试的 Android 应用上面,通常 id, name 这样的属性不唯一,利用 id 或 name 去 query, 往往会返回一组 id 相同的元素,其中大部分的 x,y 坐标不在可见范围内,这种情况需要利用坐标继续过滤返回结果。一个比较好的方法是自己写一个类去继承 Calabash::ABase,然后自定义一个 query(uiquery, *args) 方法,过滤掉 x,y 坐标不在可见范围的元素。
对于第二个问题,用 Calabash 写 UI 自动化集成测试和回归测试,如果运行结果不稳定的话价值就不大了。这个时候是练习使用 trait, await, wait_for, element_exists 等方法,熟悉 Calabash API 的最好时机。
写 Calabash 测试,或者写其他任何 UI 测试的时候,直觉上想消灭所有 sleep 语句。但完全消灭 sleep 并不简单,尤其对于 calabash-android 来说, 目前没有 wait_for_none_animating 方法, 需要自己寻找等待策略,但这个时候也是积累经验的最好时机,多尝试一些 wait_for 和 element_exists 方法,让你的测试脚本变得更稳定。
如果进行到此一切顺利,你已经对元素定位,提高脚本的稳定性有了一些经验,接下来你可以写很多代码开始做项目了。
当你的代码量不断增加,你会开始考虑如何组织你的测试代码,如何写易于维护的代码这个大问题。Page Object Model 是一个不错的选择,也是 Calabash 官方推荐的方式。关于 Page Object Model,网上已经有很多讨论,以后有时间的时候可以另开一贴讨论。
除了 POM 以外,有些其他话题可以先讨论一下:
避免嵌套使用 macro 语法
完全不使用 macro 难免会写很多重复的 steps, 但 macro 嵌套使用会大大增加维护成本,代码也将变得不易读。建议:可以使用 macro, 但一定避免嵌套使用。
避免过于琐碎的 cucumber story
比如一个 Successful login scenario 可以像下面这样写:
Given I am on login screen
And I input my username
And I input my password
And I click on "Login" button
Then I should login successfully.
单独写这样一个 login scenario 没关系,但是几乎每个 scenario 都需要 login, 因此需要再写一个简短版本的 login 语句。
上面只是一个简单的例子, Cucumber 可以有两种书写风格 imperative 和 declarative,前一种书写过多细节,后一种隐藏细节,两种风格任意一种走向极致都不好,但书写过于具体的步骤肯定不推荐,可以阅读《The Cucumber Book》中的第六章 “When Cucumbers Go Bad” 避免写出琐碎且不易为维护的 story。
跨平台测试的代码如何组织
通常一个移动 app 会有 iOS 和 Android 两个版本,功能界面和用户体验在两个平台上大致相同,因此测试的 features 和 scenarios 会有大部分重叠。不仅如此,写测试脚本通常要为两个平台写很多辅助代码, 比如调用 backend service, 访问数据库,以及一些自定义辅助方法很可能在两个平台共享。从代码重用的角度,有理由把 iOS 和 Android 的测试代码放在一起,比如官方提供个一个组织跨平台代码的例子:https://github.com/calabash/x-platform-example
然而,把 Android 和 iOS 的测试代码放在一起也有一些很明显的缺点:
总之,要不要放在一起要看测试代码的规模,两个平台上实现的异同,共用代码的数量,多少人共同开发维护脚本来决定。个人建议:对于比较简单的项目,可以把两个平台的测试代码放在一起,否则还是分开更好。
最后,用一个采用 PMO style 写 Calabash 测试可能遇到的一个小错误作为结尾:
下面这段代码来自网上(http://rubygemtsl.com/tag/calabash-android/)
class WordPressApp < DroidPress
def welcome_screen
@welcome_screen ||= page(WelcomeScreen)
end
def login_screen
@login_screen ||= page(LoginScreen)
end
def home_screen
@home_screen ||= page(HomeScreen)
end
end
welcome_screen 方法第一次被调用的时候从 page 方法返回一个 object, 之后的调用直接返回@welcome_screen,经常写 ruby 代码的同学会习惯这样写,但是上面这段代码当 welcome_screen 更新后结果就不一定准确了,因此要写成下面这种 “低效” 的方式,每次使用 welcome_screen 都要从 page() 那里再次获取
class WordPressApp < DroidPress
def welcome_screen
page(WelcomeScreen).await
end
end
实际上,对于通过 UI 进行测试的代码,慢的地方一般不在 ruby 写法是否高效,即使每次都从 page() 方法获取 object 和直接返回@welcome_screen完全感觉不到区别。
没想到会写这么多, 抛砖引玉,欢迎各位指教。