在本系列的上一篇文章中,我通过系统登录这一典型功能点,演示了编写自动化测试脚本的整个流程,并对测试脚本进行了初步优化。
在本文中,我将重点介绍如何对自动化测试脚本实现⎡工程化⎦的组织和管理。
测试脚本⎡工程化⎦
首先说下什么是测试脚本的工程化。
通过之前的工作,我们已经可以让单个自动化测试用例正常运行起来了。然而,这还只算是一个demo
,一切才刚刚开始。
试想,一个项目的自动化测试用例少则数百,多则成千上万。如何将这些自动化测试用例组织起来?如何实现更好的可重用机制?如何实现更好的可拓展机制?这些都还是我们当前的demo所不具备的,也是我们需要通过“工程化”手段进行改造的原因。
引入Minitest/RSpec
在Ruby中,说到测试首先就会想到Minitest或RSpec,这是Ruby中用的最多的两个测试框架。通过这些框架,我们可以很好地实现对Ruby测试用例的管理。
同样地,由于我们的自动化测试脚本是采用Ruby编写的,因此我们也可以使用Minitest/RSpec来管理我们的自动化测试用例。
基于该想法,我们采用RSpec对之前的系统登录测试用例进行工程结构初始化。对于熟悉Ruby编程,或者有一定代码基础的同学而言,很自然地,可以将测试用例框架初始化为如下结构。
1
2
3
4
5
6
7
8
9
10
| ├── Gemfile
├── android
│ └── appium.txt
├── common
│ ├── requires.rb
│ └── spec_helper.rb
└── ios
├── appium.txt
└── spec
└── login_spec.rb
|
在Gemfile
中,指定了项目依赖的库。
1
2
3
4
5
6
| # filename: Gemfile
source 'https://gems.ruby-china.org'
gem 'rspec'
gem 'appium_lib'
gem 'appium_console'
|
在common/spec_helper.rb
中,定义了模拟器和RSpec初始化相关的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| # filename: common/spec_helper.rb
def setup_driver
return if $driver
appium_txt = File.join(Dir.pwd, 'ios', 'appium.txt')
caps = Appium.load_appium_txt file: appium_txt
Appium::Driver.new caps
end
def promote_methods
Appium.promote_appium_methods RSpec::Core::ExampleGroup
end
setup_driver
promote_methods
RSpec.configure do |config|
config.before(:each) do
$driver.start_driver
wait { alert_accept }
end
config.after(:each) do
driver_quit
end
end
|
在common/requires.rb
中,实现了对相关库文件的引用。
1
2
3
4
5
6
7
8
| # filename: common/requires.rb
# load lib
require 'rspec'
require 'appium_lib'
# setup rspec
require_relative 'spec_helper'
|
在ios/appium.txt
中,对iOS模拟器信息和测试包路径进行了配置。
1
2
3
4
5
| [caps]
platformName = "ios"
deviceName = "iPhone 6s"
platformVersion = "9.3"
app = "/Users/Leo/MyProjects/AppiumBooster/ios/app/test.app"
|
在ios/spec/
目录中,则是测试用例的内容。例如,ios/spec/login_spec.rb
对应的就是系统登录的测试用例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # filename: ios/spec/login_spec.rb
require_relative '../../common/requires'
describe 'Login' do
it 'with valid account' do
wait { id('btnMenuMyAccount').click }
wait { id 'uiviewMyAccount' }
wait { id('tablecellMyAccountLogin').click }
wait { id 'uiviewLogIn' }
wait { id('txtfieldEmailAddress').type 'leo.lee@debugtalk.com' }
wait { id('sectxtfieldPassword').type '123321' }
wait { id('btnLogin').click }
wait { id 'tablecellMyMessage' }
end
end
|
通过以上代码结构初始化,我们的测试用例框架的雏形就形成了。接下来,在Terminal中切换到项目根目录,然后通过rspec ios
命令就可以执行ios目录中的测试用例了。
1
2
3
4
5
| ➜ rspec ios
.
Finished in 2 minutes 7.2 seconds (files took 1.76 seconds to load)
1 example, 0 failures
|
完整的代码请参考debugtalk/AppiumBooster
的1.FirstTest
分支。
添加第二条测试用例
现在,我们尝试往当前的测试框架中添加第二条测试用例。
例如,第二条测试用例要实现启动后从当前地区切换至中国。那么,就可以新增ios/spec/change_country_spec.rb
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # filename: ios/spec/change_country_spec.rb
require_relative '../../common/requires'
describe 'Change country' do
it 'from Hong Kong to China' do
wait { id('btnMenuMyAccount').click }
wait { id 'uiviewMyAccount' }
wait { id('tablecellMyAccountSystemSettings').click }
wait { id 'txtCountryDistrict' }
wait { id('txtCountryDistrict').click }
wait { id 'uiviewSelectCountry' }
wait { id('tablecellSelectCN').click }
wait { id('btnArrowLeft').click }
wait { id 'uiviewMyAccount' }
end
end
|
完整的代码请参考debugtalk/AppiumBooster
的2.SecondTest
分支。
现在我们凝视已经添加的两个测试用例,有发现什么问题么?
是的,重复代码太多。在每一步操作中,都要用id
来定位控件,还要用wait
来实现等待机制。
除此之外,当前代码最大的问题就是测试用例与控件映射杂糅在一起。造成的后果就是,不管是控件映射发生变动,还是测试用例需要修改,都要来修改这一份代码,维护难度较大。
重构:测试用例与控件映射分离
基于以上问题,我们首要的改造任务就是将测试用例与控件映射进行分离。
考虑到常用的控件操作方法就只有几个(click
,type
),因此我们可以将控件操作方法单独封装为一个模块,作为公共模块。
1
2
3
4
5
6
7
8
9
10
11
| module Actions
def click
wait { @found_cell.click }
end
def type(text)
wait { @found_cell.type text }
end
end
|
然后,将APP中每一个页面封装为一个模块(module
),将页面中的控件映射为模块的静态方法(method
),并通过include
机制引入方法模块。
例如,登录页面就可以封装为如下代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| module Pages
module Login
class << self
include Actions
def field_Email_Address
@found_cell = wait { id 'txtfieldEmailAddress' }
self
end
def field_Password
@found_cell = wait { id 'sectxtfieldPassword' }
self
end
def button_Login
@found_cell = wait { id 'btnLogin' }
self
end
end
end
end
module Kernel
def login
Pages::Login
end
end
|
这里还用到了一点Ruby元编程技巧,就是将页面模块封装为一个方法,并加入到Kernel
模块下。这样做的好处就是,我们可以在项目的任意地方直接通过login.button_Login.click
这样的形式来对控件进行操作了。
完成以上改造后,系统登录测试用例就可以采用如下形式进行编写了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| describe 'Login' do
it 'with valid account' do
# switch to My Account page
my_account.button_My_Account.click
inner_screen.has_control 'uiviewMyAccount'
# enter login page
my_account.button_Login.click
inner_screen.has_control 'uiviewLogIn'
# login
login.field_Email_Address.type 'leo.lee@debugtalk.com'
login.field_Password.type '123321'
login.button_Login.click
inner_screen.has_control 'tablecellMyMessage'
end
end
|
完整的代码请参考debugtalk/AppiumBooster
的3.RefactorV1
分支。
To be continued …
经过这一轮重构,我们的测试用例与控件映射已经实现了分离,测试用例的可重用性与可扩展性也得到了极大的提升。
然而,在当前模式下,所有的测试用例仍然是以代码形式存在的,新增和修改测试用例时都需要到工程目录下编辑Ruby文件。
那有没有一种可能,我们只需要在表格中维护自动化测试用例(如下图),然后由代码来读取表格内容就可以自动执行测试呢?
是的,这就是我们对测试框架进行⎡工程化⎦改造的下一个形态,也就是AppiumBooster
现在的样子。
在下一篇文章中,我们再进行详细探讨。