In the previous article in this series, I demonstrated the entire process of writing an automated test script and made some preliminary optimizations through the typical function point of system login.

In this article, I will focus on how to implement the organization and management of Engineering Nation for automated test scripts.

Test script, “Engineering Nation”

First, let’s talk about engineering test scripts.

From our previous work, we were able to get a single automated test case running. However, this is just a demo, and things are just getting started.

Imagine a project with hundreds to thousands of automated test cases. How are these automated test cases organized? How to achieve a better reusable mechanism? How to achieve a better extensibility mechanic? These are all things that we don’t have in our current demo, and that’s why we need to engineer it.

Introducing Minitest/RSpec

In Ruby, the first thing that comes to mind when you think of testing is Minitest or RSpec, the two most commonly used testing frameworks in Ruby. With these frameworks, we can manage Ruby test cases very well.

Similarly, since our automated test scripts are written in Ruby, we can also use Minitest/RSpec to manage our automated test cases.

Based on this idea, we used RSpec to initialize the engineering structure of the previous system login test case. For those of you who are familiar with Ruby programming, or who have some code background, it is natural to initialize the test case framework with the following structure.

├ ─ ─ Gemfile ├ ─ ─ android │ └ ─ ─ appium. TXT ├ ─ ─ common │ ├ ─ ─ the requires the rb │ └ ─ ─ spec_helper. Rb └ ─ ─ ios ├ ─ ─ appium. TXT └ ─ ─ The spec └ ─ ─ login_spec. RbCopy the code

In Gemfile, you specify the libraries that the project depends on.

source 'https://gems.ruby-china.org'
gem 'rspec'
gem 'appium_lib'
gem 'appium_console'Copy the code

In common/spec_helper.rb, the simulator and RSpec initialization code are defined.

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
  endCopy the code

In common/requires. Rb, references to related library files are implemented.

require 'rspec'
require 'appium_lib'
require_relative 'spec_helper'Copy the code

The ios simulator information and test package path are configured in ios/appium. TXT.

[caps]
platformName = "ios"
deviceName = "iPhone 6s"
platformVersion = "9.3"
app = "/Users/Leo/MyProjects/AppiumBooster/ios/app/test.app"Copy the code

In the ios/spec/ directory, this is the content of the test case. For example, ios/spec/login_spec.rb corresponds to the system login test case.

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 '[email protected]' } wait { id('sectxtfieldPassword').type '123321' } wait { id('btnLogin').click } wait { id 'tablecellMyMessage' } end endCopy the code

Through the above code structure initialization, our test case framework is formed. Next, switch to the project root directory in Terminal and run the test cases in the ios directory through the rspec ios command.

➜ rspec ios
Finished in 2 minutes 7.2 seconds (files took 1.76 seconds to load)
1 example, 0 failuresCopy the code

Refer to the 1.firstTest branch of debugtalk/AppiumBooster for the complete code.

Add a second test case

Now let’s try to add a second test case to the current test framework.

For example, the second test case is to switch from the current locale to China after startup. Add 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 endCopy the code

Refer to the 2.SecondTest branch of debugTalk /AppiumBooster for the complete code.

Now that we look at the two test cases that have been added, are there any problems?

Yes, there is too much duplicate code. At each step, the id is used to locate the control, and wait is used to implement the wait mechanism.

In addition, the biggest problem with the current code is the mix of test cases and control mappings. As a result, this code has to be modified whether the control mapping changes or the test case needs to be modified, which is difficult to maintain.

Refactoring: Separation of test case and control mapping

Based on the above, our first transformation task was to separate the test case from the control mapping.

Considering that there are only a few commonly used control manipulation methods (click, type), we can encapsulate control manipulation methods as a separate module as a public module.

module Actions
  def click
    wait { @found_cell.click }
  end
  def type(text)
    wait { @found_cell.type text }
  end
  endCopy the code

Then, each page in APP is encapsulated as a module, the controls in the page are mapped as static methods of the module, and the method module is introduced through include mechanism.

For example, the login page could be wrapped in the following code.

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
  endCopy the code

A bit of Ruby metaprogramming is used here, which is to encapsulate the page module as a method and add it to the Kernel module. The advantage of this is that we can manipulate the control anywhere in the project directly through login. button_login. click.

After the above modification, the system login test case can be written in the following format.

describe 'Login' do
  it 'with valid account' do
    my_account.button_My_Account.click
    inner_screen.has_control 'uiviewMyAccount'
    my_account.button_Login.click
    inner_screen.has_control 'uiviewLogIn'
    login.field_Email_Address.type '[email protected]'
    login.field_Password.type '123321'
    login.button_Login.click
    inner_screen.has_control 'tablecellMyMessage'
  end
  endCopy the code

Refer to the 3.RefactorV1 branch of debugtalk/AppiumBooster for the complete code.

To be continued…

After this round of refactoring, our test case and control mapping have been separated, and the reusability and extensibility of test cases have been greatly improved.

However, in the current mode, all test cases still exist as code, and new and modified test cases require editing Ruby files in the project directory.

Is it possible to just maintain an automated test case in a table (as shown below) and have the code read the table to automate the test?

Yes, this is the next iteration of the Test framework, and that is what AppiumBooster is now.

We’ll explore this in more detail in the next article.