Functional Test Refactoring: Move
Move test steps into the sections that execute before/after test cases
This is one of the 6 Functional Test Refactorings:
Move
Extract Page Function
Introduce Page Object
Rename
The structure of this article is the same as “Extract Function”. The test project that helps you do refactoring exercises quickly:
> cd my-working-dir
> git clone https://github.com/testwisely/agiletravel-ui-tests
The test project is at my-working-dir/agiletravel-ui-tests/pre-refactoring
.
Motivation
You have the same set of beginning or ending test steps in multiple test cases.
Long and similar test cases are harder to maintain. By grouping test cases and utilize shared sections, it is possible to make test cases concise and more distinguishable.
Sample Test (before)
The test script file in the sample project: spec/flight_spec.rb
before(:all) do
# ...
endit "One-way trip" do
login("agileway", "testwise")
visit("/flights/start")
driver.find_element(:xpath, "//input[@name='tripType' and @value='oneway']").click
# ...
endit "Return trip" do
login("agileway", "testwise")
visit("/flights/start")
driver.find_element(:xpath, "//input[@name='tripType' and @value='return']").click
# ...
end
Issues with the test
The
login('agileway', 'testwise')
step is in both test cases, which are unnecessary (or wrong). We only need to log in once (for this case).The
visit('flights/start')
is necessary for each test case, but it is better moved out of the main test flow. Imagine if you need to add more test scenarios in this test script file.
Action
Move common test steps into shared sections that run before or after each test case.
Prerequisite
Multiple (grouped) test cases are allowed in a single test script file
Shared sections (execution hooks) where the test steps are executed before or after each test case
Those shared sections (a.k.a. execution hooks) exist in most of test syntax frameworks:
RSpec:
before(:all), before(:each), after(:each), after(:all)
Mocha:
before, beforeEach, afterEach, after
PyTest:
setUpClass, setUp, tearDown, tearDownClass
JUnit:
setUp, tearDown
or@BeforeClass, @Before, @After @AfterClass
annotations
Because there are four shared sections, e.g. before(:all), before(:each), after(:each), after(:all)
, some might name this ‘Move’ refactoring as four separate refactorings, such as “Move to Before Each Test” and “Move to After All Tests”, but they all work the same way.
Test Design conventions with shared sections
Start a driver (Selenium WebDriver for web testing) in
before(:all)
Quit the driver in
afer(:all)
If one test user performs all test cases in one test script file, a login step shall be in
before(:all)
If every test case starts with a different user, a logout step shall be in
after(:each)
If every test case (in a test script file) needs to start with a certain page, use
visit('/dashboard')
(example) inbefore(:each)
step.
Mechanics
Identify the same test steps that are common to each test case
Select the test steps in one test case
Move them to corresponding sections that run before or after all/each test case
Delete repeated test steps in remaining test cases
Run all the test cases that have changed
Refactoring Steps in TestWise
Select the test steps that might be better before/after all or each test case
Invoke refactoring
Choose the target, e.g.
before(:each)
orafter(:all)
Preview
Apply the refactoring
Expected Result
The test steps moved to the shared sections, test execution is not affected.
Scope
Within the same test script file.
Sample Test (after)
before(:all) do
# ...
login("agileway", "testwise")
end
before(:each) do
visit("/flights/start")
endit "One-way trip" do
driver.find_element(:xpath, "//input[@name='tripType' and @value='oneway']").click
# ...
end
it "Return trip" do
driver.find_element(:xpath, "//input[@name='tripType' and @value='return']").click
# ...
end
Demonstration (animated GIF)
The refactoring applies the same way for each of the four before(:all), before(:each), after(:each), after(:all)
shared sections. I will just show two examples here.
Move test steps before all test cases, via Refactoring Menu
2. Move test steps before each test case, via a keyboard shortcut
This time, the before(:each)
did not exist in the test script, TestWise will insert it into the test script.
Demonstration (Video)
Move one test step before all test cases, via Refactoring Menu
Move test steps before each test case, via a keyboard shortcut
Benefits
Concise
Focused tests
When maintaining a large test suite (>200 UI tests), it is quite useful that the first test step line reveals the test case’s purpose. Study the same test scripts (before and after) and then imagine you will be maintaining hundreds of them running in a CT Server many times of day, …Reduce Duplication, DRY (Don’t Repeat Yourself)
Exercises
Move one test step to
before(:all), before(:each), after(:each), after(:all)
Move multiple steps to the four shared sections
Invoke refactoring using the keyboard shortcut
Move one step to a shared section, e.g.,
before(:each)
, which is not defined in the test script fileMove one step to a shared section that already exists in the test script file
Move one step that repeats in other test cases (in the same test script file) to a shared section. Check the ‘Replace Occurences in other test cases’ checkbox.