Objective
This document outlines the conventions for developing consistent, readable, and maintainable CycleScript code.
Philosophy
- Ensure readability for technical and non-technical users.
- Validate state before action.
- Use modularity to prevent code repetition.
- Convention over configuration.
- Adhere to the API Mandate.
What do those mean?
- CycleScript is designed to be a common language between business stakeholders and technical engineers. In order to ensure this, CycleScript should not be written like a programming language, but like a naturally readable standard operating procedure using business and comment steps.
- Here’s an example of harder to read code:
Given I assign 0 to variable "x"
While I verify number $x is less than 5
Then I execute scenario "Unload the Truck"
And I increase variable "x" by 1
EndWhile
- The previous example lacked indentation, didn’t have a step to describe the goal of the code chunk, and used ambiguous variable names. Here’s an example of easier to read code:
Given I "unload the truck five times"
Given I assign 1 to variable "times_unloaded"
While I verify number $times_unloaded is not equal to 5
Then I execute scenario "Unload the Truck"
And I increase variable "times_unloaded" by 1
EndWhile
- Before you perform any action in CycleScript, first validate that you are where you want to be - whether that’s a web page, terminal screen, or application. Confounding variables such as system lag, application errors, and unexpected behavior can affect state and therefore break your action.
- Use steps with a within clause to validate state while accounting for variable response times.
- Use an I see cursor step in the terminal before interacting with it to ensure the terminal screen has fully rendered.
- Use an I see element step in the web before interacting with the element to ensure the web page has rendered correctly and entirely.
- Within reason, extract code duplication to their own scenarios, so that maintenance of that logical component is centralized. This keeps code clean by reducing repetition.
- Here’s an example of WET pseudo-code:
Scenario: Plan Shipment from Order
Given I "obtain the Order Number"
Given I "obtain a dynamic value from the screen I am on"
When I "build a custom query by concatenating that dynamic value"
And I "query the appropriate data source for the order number"
Then I "plan shipment from order"
Given I "navigate to the correct screen"
And I "open the shipment planning modal"
When I "select the order"
And I "plan the shipment"
Then I "verify the shipment is planned"
Scenario: Allocate Shipment
Given I "obtain the Order Number"
Given I "obtain a dynamic value from the screen I am on"
When I "build a custom query by concatenating that dynamic value"
And I "query the appropriate data source for the order number"
Then I "allocate shipment"
Given I "navigate to the correct screen"
And I "open the allocation modal"
When I "filter by the order number"
And I "select the shipment"
And I "allocate it"
Then I "verify the shipment is allocated"
- The functionality to obtain the order number can be extracted. Here’s an example of DRY pseudo-code:
Scenario: Plan Shipment from Order
Then I "plan shipment from order"
Given I "navigate to the correct screen"
And I "open the shipment planning modal"
When I "select the order"
And I "plan the shipment"
Then I "verify the shipment is planned"
Scenario: Allocate Shipment
Then I "allocate shipment"
Given I "navigate to the correct screen"
And I "open the allocation modal"
When I "filter by the order number"
And I "select the shipment"
And I "allocate it"
Then I "verify the shipment is allocated"
Scenario: Obtain Order Number
Given I "obtain the Order Number"
Given I "obtain a dynamic value from the screen I am on"
When I "build a custom query by concatenating that dynamic value"
And I "query the appropriate data source for the order number"
- Rather than reinventing the wheel for standard implementations and practices, stay consistent with existing CycleScript and conventions.
- Here’s an example of inconsistent coding practices:
@wip @receive-pallet
Scenario: Receive Pallet
# DESCRIPTION: THIS CASE RECEIVES A PALLET
# INPUT: SPECIFY THE LODNUM TO RECEIVE
# OUTPUT: THE LODNUM WILL BE RECEIVED
Given I "navigate to the receiving terminal screen"
When I "navigate there"
And I "type menu options"
And I "receive the pallet"
When I "type in the lodnum"
And I "perform countback"
And I "deposit it"
@wip @receive_pallet
################################################
# Scenario: Receive Case
# Description: This scenario, given a load
# number, will start at the
# Undirected Menu and go receive
# the case.
# Input: $lodnum
# Output: N/A.
################################################
Scenario: Receive Case
Given I "navigate the receiving terminal screen"
When I "navigate there"
And I "type menu options"
And I "receive the pallet"
When I "type in the lodnum"
And I "perform countback"
And I "deposit it"
- You can see that both scenarios use different indentation conventions, scenario headers, and tags. This makes code harder to maintain over time - especially as projects scale. Pick one standard for everyone and stick to it.
- On a Feature File level, separate public and private scenarios via tags to expose and hide external and internal functionality to other features. This helps convey the developer’s intent and allows the external API to have consistency, while the internal API can change over time with development.
- Here’s an abridged example of how to do so:
Feature: Count Back
@wip @public
Scenario: Count Back
Given I "calculate the remainder"
Given I execute scenario "Screen Scan"
When I execute scenario "Calculate Remainder"
When I "perform Count Back"
When I execute scenario "Screen Input"
###############################
# Private Scenarios:
###############################
@wip @private
Scenario: Screen Scan
When I "extract the pallets, cases, and eaches listed on the screen"
@wip @private
Scenario: Screen Input
When I "input the pallets, cases, and eaches remaining"
@wip @private
Scenario: Calculate Remainder
Given I execute scenario "Check Database for Picked"
When I "calculate the remainder in the location"
@wip @private
Scenario: Check Database for Picked
When I "query the database to determine how much was picked"
- You can see certain scenarios, below a comment header, are tagged as private. The intent of the developer is that whoever imports this scenario, only the scenario Count Back should be called. This can be done for a variety of reasons, like the private scenarios having internal dependencies.
Terminology
Customer Terminology
It’s important that, when interfacing with customers, everyone uses the same terminology. Even though we may use different words internally, we should be consistent with customers.
Our accepted terminology:
- A Scenario is a logical block of actions defined in Cycle.
- A Feature File contains Scenarios.
- A Utility is a scenario or feature file that is imported and called by a Test Case.
- A Test Case is a scenario or feature file that performs a standalone Unit or End-to-End Test.
- A Regression Test is a collection of one or many Test Cases that run standalone or on a playlist.
- A Volume Test comprises of one or many Test Cases which are run concurrently by independent workers in a grouptest.
- Continuous Testing is the process of running Regression or Volume tests on a set trigger, usually changes to the configuration, code, and/or environment of an application.
Industry Jargon
Despite our accepted terminology, you may hear several other terms used interchangeably. It’s important that all developers are on the same page when communicating with each other; for that purpose, we’ve designated a few pieces of common terminology.
- A Scenario is the same thing as a Function, Method, and a Routine.
- A Utility Scenario is the same thing as a Helper Scenario and a Utility.
- A Utility Scenario is always a @WIP Scenario.
- A Utility Feature File is a Feature File that only contains Utility Scenarios.
- A Test Case is sometimes referred to as a Script.
- One or multiple Test Cases can be in a single Feature File.
- A Feature File with a single scenario that is a Test Case may also be referred to as a Test Case.
- Environment Variables are variables called in an Environment.feature file, typically in the Background. They are instantiated once and expected to remain static.
Feature File Structure
All Feature Files, whether they are utilities or test cases, will follow the same structure.
Utility Feature Files will often not need the Background and After Scenario components.
There may be many more occurrences of Scenarios and their associated Scenario Header, especially if it is a Utility Feature File.
We advise a single scenario per Test Case Feature File, but have no defined limits for Utility Feature Files.
- Feature Header
- Licensing and Copyright Information
- Meta-Data
- Feature Declaration
- Background
- Background Header
- Dependency Imports
- Environment Setup
- Setup Functions
- After Scenario
- After Scenario Header
- Interface Logout
- Data Cleanup, if applicable
- Reporting Mechanisms, if applicable
- Scenario(s) and Scenario Outline(s)
- Scenario Header
- Scenario Implementation
- Interface Login
Naming Conventions
While parsing the project directory and feature files at a glance, these naming conventions are designed for readability - they should speak like natural language.
Conversely, while parsing code, you should be able to glance at a variable and by its convention, deduce whether it is an environment variable.
- Folders, Feature Files, and Scenarios use title casing.
- Title Casing Tool
- Examples:
Utilities
Web Services
Receiving Staging
Deposit Load
Find Part for All Shipments - Title Casing - capitalize everything but:
- Articles:
- a
- an
- the
- Coordinate conjunctions:
- for
- and
- nor
- but
- or
- yet
- so
- Prepositions: (According to the Chicago Manual of Style)
- such as at
- around
- by
- after
- along
- for
- from
- of
- on
- to
- with
- without
- Environment Variables, Variables, and Script Filenames should use be lowercase, with words separated by underscores.
- Examples:
loop_count
generate_lpn.msql
unidentifiable
You can find the Headers in this BitBucket Snippet for ease of use.
Some meta-data tags are demarcated as such: <Test Case|Utility>. In the event that the Feature File is a Test Case, you would select the left-hand option for the tag and value, and vice versa for a Utility Feature File.
This would mean that Public Scenarios are listed for Utilities and Input Sources and Inputs are listed for Test Cases.
Some meta-data tags have an option of other - over time, by necessity, this may evolve into new categories.
###########################################################
# Copyright 2021, Cycle Labs, Inc.
# All rights reserved. Proprietary and confidential.
#
# This file is subject to the license terms found at
# https://cyclelabs.io/terms/
#
# The methods and techniques described herein are considered
# confidential and/or trade secrets.
# No part of this file may be copied, modified, propagated,
# or distributed except as authorized by the license.
############################################################
# <Test Case|Utility>: <file_name>
#
# Functional Area: <jda_functional_area>
# Author: Cycle Labs
# JDA WMS Version: <version_developed_on>
# Test Case Type: <volume|regression|utility|other> (comma-separated, if applicable)
# JDA Interfaces Interacted With: <Native|WEB|Terminal|MOCA|Voice|Mobile|API|Other> (comma-separated, if applicable)
# Bundle Revision: <bundle_version_developed_on|None>
#
# Description:
# <single_or_multi_line_description>
#
# <Input Source|Public Scenarios>: <path_to_input_source|>
# <Required Inputs:|>
# <tabbed_input_parameters|> - <brief_description_of_input_parameters|>
# - <|tabbed_list_of_defined_scenarios_and_description>
# <Optional Inputs:|>
# <tabbed_input_parameters|> - <brief_description>
#
# <Input Detail Source>: <path_to_input_detail_source|> (OPTIONAL)
# <Detail Required Inputs:|> (OPTIONAL)
# <tabbed_input_detail_parameters|> - <brief_description_of_input_parameters|>
# <Detail Optional Inputs:|> (OPTIONAL)
# <tabbed_input_detail_parameters|> - <brief_description>
#
# Assumptions:
# - <single_or_multi_line_assumptions>
#
# Notes:
# - <any_additional_details>
#
############################################################
Inputs that are occasionally required should be under the <Required Inputs:> section, with a description delineating how to handle it.
If you are using Scenario Outlines with CSV Examples and have multiple (non-header) example rows in your Input CSV file, please use the following format to document the example rows (only needed if you have more than 1 non-header row in the input CSV).
Please add to the Notes: section of the Test Case Feature File.
# - Test Case Inputs (CSV) - Examples:
# Example Row: OUTCAP_ONLY serialization prtnum being received
# Example Row: CRDL_TO_GRAVE serialization with receive quantity of 1
# Example Row: CRDL_TO_GRAVE serialization with receive quantity of 20
Background:
#############################################################
# Description: <description_of_background_especially_unique_attributes>
#############################################################
After Scenario:
#############################################################
# Description: <description_of_after_scenario_especially_unique_attributes>
#############################################################
Private Scenario Headers are placed in between the last public scenario implementation and the first private scenario implementation.
#############################################################
# Private Scenarios:
# <tabbed_list_of_defined_scenarios_and_description>
#############################################################
Scenarios in Utility Feature Files should be tagged with @public and @private accordingly.
Please note that ‘arguments’, ‘parameters’, ‘inputs’ to a scenario are not locally scoped - these are global variables the scenario expects to be set to function properly.
Test Case Scenarios map directly to the Feature Header and do not need an associated Scenario Header, but do need an associated short code tag @BASE-RCV-0010 affixed to the Scenario.
@wip <@public|@private>
Scenario: Operation Example
#############################################################
# Description: This scenario performs an operation based on arguments.
# MSQL Files:
# <tabbed_list_of_called_MSQL_Scripts>
# <Groovy Files:>
# <optional_tabbed_list_of_called_Groovy_Scripts>
# <API Endpoints:>
# <endpoint>
# Inputs:
# Required:
# devcod - The terminal device code
# devtype - Determines the scenario's behavior based on device type
# Optional:
# srcloc - The source location for the work
# oprcod - The operation code
# Outputs:
# oprcod - The wrkque.oprcod value
# success - Whether the operation completed successfully
# 1 - The operation completed successfully
# 0 - The operation failed
#############################################################
In some cases, such as a scenario which only has external scenario calls, it may be redundant to document the required inputs and outputs. In these cases, make a brief reference to the appropriate scenarios in which to find the inputs and outputs.
All dataset load and cleanup scripts should have the following header and should follow the guideline of commenting each logical block of code. An Example:
/*
Dataset Name: <Name of Dataset>
Description: <Description of MSQL Script>
*/
/* Description Line */
publish data where...
|
/* Description Line */
[<MSQL Statement>]
|
/* Description Line */
[<MSQL Statement>]
Each MSQL Script File should have the following header and follow guidelines of commenting each logical block of code. An Example:
/*
MSQL File: .msql
Description: Description of this MSQL Script
*/
/* This exposes the interface variables. */
publish data
where srcloc = @@uc_cyc_srcloc
and prtnum = @@uc_cyc_prtnum
and client_id = @@uc_cyc_client_id
and wh_id = @@uc_cyc_wh_id
|
/* This gets a lodnum with from inventory_view realtive to footprint detail */
[select iv.lodnum
from inventory_view iv
join prtftp_dtl pd
on iv.prtnum = pd.prtnum
and iv.prt_client_id = pd.prt_client_id
and iv.wh_id = pd.wh_id
and iv.ftpcod = pd.ftpcod
where iv.stoloc = rtrim(ltrim(@srcloc))
and iv.prtnum = rtrim(ltrim(@prtnum))
and iv.prt_client_id = rtrim(ltrim(@client_id))
and iv.wh_id = @wh_id
and pd.pal_flg = 1
and rownum < 2
order by iv.untqty % pd.untqty asc]
Each Groovy Script File should have the following header and follow guidelines of commenting each logical block of code. An Example:
/*
Groovy File: .groovy
Description:
Description of this Groovy Script
Input:
Required:
None
Optional:
None
Output:
None
*/
/* This comment describes first logical block of code. */
if (last_line.contains("Logout") && last_line.contains("Y|N")) {
prompt = "Logout";
Step Usage
Keywords
As a general rule,
- Given is used to verify and transition the application to the intended state - whether that’s the correct web page, terminal screen, importing utilities, and/or collecting the necessary background data - to perform the action.
- When performs the actual action - whether that’s clicking a certain button, inputting certain data, and/or performing the primary calculation.
- Then validates the result of the action - through a database query, image validation, and/or other mechanisms.
- Subsequent to a Business/Activity/Comment Step, the Then keyword can be used if the relevant code chunk is small and would flow more smoothly verbally.
- But is used for negative validation.
- And is commonly appended to any of the above keywords as a continuation if the task can not be completed in one step.
- In the context of a Control Structure, like an If, Else, and While, it acts as a conjoint clause, meaning the condition associated with the And is also calculated alongside the condition associated with the If, Else, and While.
- In this example, please note that the And is on the same indentation level as the Control Structure keyword:
While I "verify that there are still items to receive"
And I "verify that the items are not damaged"
Then I "receive an item"
EndWhile
If I "am logged into the Terminal"
And I "am logged in as a handheld device"
Then I "do handheld work"
EndIf
- Given, When, Then, But, and And should form a logical hierarchy at all times - at their indentation level. You should never use two keywords, other than And, in the same User Story.
- Here is an example of two different layers of indentation levels having logical hierarchy:
Given I "navigate to the Receiving Menu"
Given I "open the terminal"
When I "login"
And I "input the menu option for the Receiving Menu"
Then I "validate I am at the Receiving Menu"
When I "begin Receiving"
Given I "query the database for the receiving line"
When I "input the receiving data"
Then I "validate the Receiving process"
Then I "validate that the line has been received"
And I "exit the Receiving Menu"
Given I "return to the Undirected Menu"
When I "logout of the Terminal"
Then I "validate I am logged out"
- The first layer of indentation has its own logical hierarchy of keywords, and each chunk of the second layer of indentation also has its own logical hierarchy of keywords.
- Once replaces the Then and Given keywords for validation when a standard wait time, configurable in the Project Settings and defaulted to 120 seconds, needs to be enforced. This would be because the execution time of the step is unknown or variable due to latency or other reasons. Judicious use of the Once keyword allows for robust tests that take into consideration different systems' delays and will avoid the usage of many within clauses or I wait steps in your code.
- Here is an example of using Once to replace Given and Then keywords:
Given I "open the policy screen"
Given I "click the policy menu option"
Once I "see the policy screen"
When I "change a policy"
Once I "see the policy tables have loaded"
And I "click on a policy"
When I "change the policy's value"
Once I "validate the policy has changed in the database"
Steps
- Use in app, in web browser, in element, and in terminal steps instead of generic steps - affix your steps with the most specificity possible.
- We advise using I wait as rarely as possible.
- If an I wait step is necessary, use the I "<TEXT>" which can take between <NUMBER> seconds and <NUMBER> seconds step instead - as this is more semantic for both maintainability and the resulting reports.
- Always use I verify screen is done loading in terminal within <NUMBER> seconds after performing an action in the terminal and before a subsequent action.
- Use I type option for “<MENU_NAME>” menu in terminal instead of manual number entry.
- Use I execute scenario instead of inline scenario calls - scenario calls become more evident with this approach.
- Never declare wait times inline - use Environment Variables instead.
- Always validate that a variable is assigned before validating its value.
If I verify variable "example" is assigned
And I verify text $example is equal to "VALUE"
Then I "do something"
EndIf
If I verify variable "example2" is assigned
And I verify number $example2 is equal to 0
Then I "do something"
EndIf
Business Steps
Each logical chunk of CycleScript should be prefixed with a Business Steps.
A Business Step is a non-action step that uses I “<TEXT>” to describe a general action.
These steps should not be indented, and logical code chunks associated to them should be indented directly beneath them.
Here’s an example:
Given I "check the inventory status"
Given I "obtain the load number"
And I "open a terminal"
And I "navigate to the Undirected Menu"
And I "navigate to the Inventory Menu"
When I "filter by the load number"
And I "grab the status from the screen"
Then I "validate the inventory status"
As shown in the example, the logic associated to the Business Step is indented one level. This allows for CycleScript to map directly to work instructions - you can port work instructions line by line before writing a single functional piece of CycleScript.
There are some cases where the work instruction can have child logic of high complexity. In this case, you can indent Business Steps, which use the same I “<TEXT>” step, but on an indented level.
You can think of a Business Step as a programmatic comment to indicate what the subsequent chunk of CycleScript does.
Here is an example:
Given I "send a shipment to a customer"
Given I "allocate the shipment"
Given I "navigate to the allocation screen"
And I "filter for the shipment"
When I "allocate it"
Then I "validate it is allocated"
And I "pick the shipment"
Given I "navigate to the Terminal Picking screen"
When I "pick all the items"
Then I "validate it is fully picked"
When I "load the truck"
Given I "navigate to the Loading screen"
When I "place all items in the truck"
Then I "validate all items are in the truck"
Then I "dispatch the truck"
Given I "handle all safety checks"
Then I "send the truck off"
The use of Business Steps makes CycleScript much easier to read and therefore more maintainable.
You can use the traditional CycleScript comments to denote other additional information as long as it’s not relevant to the generated report - but valuable to the developers maintaining and developing the code.
# I chose to not include Undirected Putaway because the client
# specified that that should never happen.
# You can expand this branch by adding an extra 'Elsif' on line 32.
Business Steps are not necessary in Backgrounds, After Scenarios, Utilities, and external scenario calls if and only if the associated code blocks are simple and self-evident.
Here is an example of simple and self-evident code:
Given I execute scenario "Allocate Wave"
And I execute scenario "Validate Pick Release"
When I execute scenario "Perform Pallet Picking"
Then I execute scenario "Vaidate Pick Completion"
- These formatting rules are designed to make CycleScript readable and follow the existing conventions that the editor already uses.
- Here is a series of rules to follow to ensure written CycleScript maximizes legibility:
- Use Forward Slashes for File Paths.
- Business/Activity/Comment Steps should describe and precede an indented code block.
- Business/Activity/Comment Steps and their code blocks should be separated by a single newline.
- Each layer of indentation, excluding While, If, and Else code blocks should have their own logical hierarchy of keywords to form a logical user story.
- While, If, Elsif, and Else code blocks should all be indented.
- Conjoint And keywords used in conjunction with a Control Structure should be on the same indentation level.
- EndWhile and EndIf should have the second word capitalized to be consistent with Cycle’s auto-fill mechanism.
- Feature/Background/After Scenario/Scenario declarations and their associated headers have no separation.
- Scenario Outlines and Data Sources and their implementation should be separated by a single newline.
- Tabs should consist of four spaces - the Cycle editor interprets a tab keystroke as four spaces.
- Capitalize keywords.
Given I "am correct"
but I "am incorrect and unprofessional looking"
- Capitalize the “i” in the step.
Given I "am correct"
But i "am incorrect"
- Do not leave extra spaces between words in a Cycle step.
Given I "am correct"
But I " am incorrect"
- Casing of the remainder of the step should be consistent with Cycle’s auto-fill mechanism.
# One of these is different than the others.
Then I type option for "Load Receive" menu in terminal
Then I TYpE oPtION FoR "LOAd ReCeIvE" MENU in TeRmINaL
Project Structure
All Regression Projects should follow the model that the Bundle has established in order to maintain consistency across projects and allow for smooth transitions onto a project’s development team.
Volume Tests do not currently have a widely accepted format.
- Data
- The Data folder contains flat and static data that is used when performing processes. Note the distinction between Test Case Inputs which is flat and static data that is used to drive tests.
- Some examples of subfolders include Interfaces, Locators, and Serial Numbers, which contain XML Transaction Files, xPath Mapping CSVs, and Serial Number TXT Files.
- Data Extract Models
- The Data Extract Models folder contains .cycextract files used to pull data from the specified environment with the Extract Tool dialog.
- Datasets
- There are Base and Custom sub directories to provide a user driven layer.
- The Datasets folder should contain a semantically named folder for each Dataset.
- Documentation
- Test Specifications
- Test Case Specifications lists all the Test Cases and associated meta-data.
- Utilities Specifications lists all the Utilities and associated meta-data.
- The Cycle Bundle User Guide is available here.
- Environments
- The Environments folder contains a folder for each Environment the project can interface with. Each of these folders contain a default Environment CSV of key-value pairs and an Environment Override CSV of key-value pairs.
- The Environment.csv points to the Environment that the project uses on a general basis.
- Wait Times are defined here in a CSV file.
- Playlists
- There are Base and Custom sub directories to provide a user driven layer.
- The Playlists folder contains .cycplay files which represent a series of test cases.
- Scripts
- The scripts folder should be divided into additional folders for different languages if appropriate.
- There may be a Groovy and a MSQL folder - the relevant scripts should be stored accordingly.
- There are Base and Custom sub-directories to provide a user-driven layer.
- Test Case Inputs
- The Test Case Inputs folder contains CSV files mapped to a test case that represent the data elements needed to execute the test.
- Details
- The Details folder contains any CSV files mapped to a Test Case Input CSV.
- Test Cases
- The Test Cases folder contains the Test Case feature files.
- There are Base and Custom sub directories to provide a user driven layer.
- Utilities
- The Utilities folder contains Utility feature files.
- There are Base and Custom sub-directories to provide a user-driven layer.
- README.md
- The README is a special git file that is displayed on the project repository page.
- It should summarize the project, any special requirements, and any other relevant meta-data such as the Cycle version, JDA WMS version, release information, and project usage.
- CONTRIBUTING.md
- The CONTRIBUTING file is a special git file. It should delineate the CycleScript coding conventions deviant to this document, as well as the git workflow adopted for the project.
- .gitignore
- The gitignore file should include the reports folder so reporting isn’t version controlled and the Environment Override files so user-specific configurations are not committed to version control.
Addendums
- Usage of xPaths
- Avoid absolute xPaths - these are more prone to break given updates to a web page.
- Use relative xPaths with semantic fields - these give more insight to what the xPath is grabbing.
- Avoid using auto-generated IDs - these will break every time a web page updates.
- Feature File Width
- We advise your text editor be set to 80 columns width for ease of readability.
- Keyboard Shortcuts
- Use Keyboard Shortcuts via CycleScript if and only if they are necessary.
- Usage of Groovy/MOCA
- If your test includes an inline Groovy or MOCA statement that requires horizontal scrolling - move the logic into a script.
- Use Cycle Steps instead of Groovy logic if the number of Cycle steps is equivalent to the lines of Groovy logic.
- Here’s an example of what should be a Cycle step:
# This is a groovy one-liner that should be a Cycle step!
# This is harder to read for non-technical users and starts a groovy runtime.
Given I execute groovy "count = count + 1"
# This is a Cycle one-liner that is the equivalent of the above.
# This reads inline with CycleScript syntax.
Given I increase variable "count" by 1