The term “mielie pap” refers to a culturally significant South African dish. In this context, mielie pap is a descriptive analysis, development, and testing language. Similar to how everyone can share from a single pot of mielie pap, this language allows for collaborative use. Inspired by Cucumber, it incorporates additional features and modifications. While it emphasizes UI test cases, it is versatile enough to be used for broader applications, such as defining business rules or application logic.
Index:
Goal
The goal is to create a standardized simple language that is:
- Clear
- Precise
- Maintainable
Expressing intent throughout the SDLC is crucial.
It’s most effective when everyone uses the same language and standards.
Mielie pap is used to express:
- Business rules
- Process flow
- Logic flow
- Test cases
- Automation scenarios
- and anything else you can think of 🙂
Basic Structure
The language structure consists of keywords followed by an description.
The keywords and descriptions are separated by a colon ” : “.
For example, Scenario: my test case
Tab (4 spaces) indentation is used to indicate structural hierarchy for example,
Feature: Users
Scenario: Create a new user
Given: Some condition
...
Scenario: Apply user rights
...
Child elements are indented.
Siblings are aligned so that the colons align on the right.
For example:
Scenario: Something
Given: Condition
And: Another condition
Then: Something happened
In the above example, Scenario is the parent, Given, And and Then are siblings in the context of Scenario.
The “root” keyword is always “Feature,” indicating the feature that the following content pertains to. You may choose to have multiple documents describing different cases for the same feature. In all of those documents, the feature name and description should remain consistent. For example, if I have two or more documents about “Users,” they should all refer to the feature “Users.”
For testing documents, whether they are manual or automated tests, the following structure applies.
Feature: Users Rights
Prerequisites:
Scenario: User "Test" defined
Scenario: ....
Test Case: Setting up users rights
Scenario: Create new admin right
....
As you can see, this document has three levels. The first, or root level, is the Feature. The second level separates Prerequisites from Test Cases.
The Prerequisites section outlines the conditions that must be met before executing the test case. This goes beyond simple requirements like “you have logged in” and focuses on necessary setups such as “the following needs to be in place before testing can occur.” For instance, you can’t test user rights if users have not been set up.
Both Prerequisites and Test Cases contain “Scenarios” as their child elements. Scenarios form the core of the document, outlining actions to take, logic flow, business rules, or validations.
Keywords
This is a list of keywords used in mielie pap, along with their functions, governing rules, and usage examples.
Keywords are always defined with the first letter capitalized. Keywords can consist of multiple words, forming key phrases. Key phrases should be short and descriptive, clearly reflecting the intent described in their definitions.
Definitions are treated as new sentences and therefore start with a capital letter.
Index
- Feature
- Prerequisites
- Test Case
- Description
- Scenario
- Navigate
- Edit
- Create
- Set
- Select
- Submit
- Profiles
- Verify
- Given
- When
- On
- Then
- Rule
- For Each
- Step
- Condition
Feature
A feature of an application is a distinct functionality or characteristic designed to provide a specific value or service to the user. Features are individual components that collectively form the overall capabilities of the application. They can range from simple elements like a search bar to complex systems like user authentication or real-time data processing. Features are typically designed to meet user needs, enhance the user experience, and fulfill the application’s intended purpose.
The “Feature” keyword defines the application features that provide the context for the following definitions. It is always defined first in the document structure.
Example:
Feature: Work orders
Prerequisites
Prerequisites refer to conditions or requirements that must be met before executing a test case. This keyword serves as a grouping tool and does not require a definition.
If a prerequisite scenario is a reference scenario, it means you need to complete those scenarios before performing the test case.
Example:
Prerequisites:
Scenario: #CreateTestUser
Scenario: #CreateRights
Test Case: Link admin right to user and ensure you can change settings
Scenario: Link admin rights to user
When: Assigning admin right to test user
Then: User can view all settings
There should be a blank line before and after this keyword. Reference scenarios can follow one another without a space in between. However, detailed scenarios must have a space above them. For more details, refer to the scenario keyword.
Prerequisites should specify what must be in place for the following scenarios to function. For example, you need to have users to define user rights. We won’t detail how to create users here—that should be covered elsewhere. However, it is crucial to emphasize that users are required before you can define user rights.
Note that I did not use the “Given” keyword in this case. It is implied that if something is listed in the “Prerequisites,” it must be in place before the defined scenarios can occur.
Also see “Reference Scenarios”
Test Case
“Test Case” is a grouping tool for scenarios and should include a definition that clearly describes what is being tested. This is typically documented by a tester, defining the method of how to test a feature.
If a longer description is needed, instead of making the definition overly lengthy, add a “description” keyword as a child of the test case to provide a more detailed explanation.
Example:
Test Case: Creating a user
Description: Some long description ...
Scenario: Create the user
....
There should be a blank line before and after this keyword. The exception to this rule is the “description” property, which should appear on the following line.
Description
A description provides additional context, clarification, or information about another keyword. It should be placed close to the keyword it describes, typically on the very next line. As a child element, the description should be indented below its target keyword. When a description does not fit on one line you can define it on multiple lines.
Function: Load As
Description: Some text on line 1
Some text on line 2
Some text on line 3
Parameters:
...
As you can see the text aligns left with the keyword for the new lines.
We know the text ends on the first white space.
Scenario
A scenario defines a specific situation or use case that demonstrates how a particular feature or function of an application should behave under certain conditions. It outlines a sequence of steps or actions to be taken, often including expected outcomes, to test or illustrate the intended functionality of the application.
There are two types of scenarios:
- Reference scenario
- Detailed scenario
Reference Scenario
These scenarios reference existing scenarios by file name. The definition is prefixed with a hash to indicate that the scenario exists outside the current scope, such as #CreateTestUser
. The #
symbol represents the root folder of the repository. If the file is located in a subfolder, the folder name should be included in the definition, for example, #MyFolder/CreateTestUser
. The file extension is not included in the definition. The #
symbol must be the first character, signaling to readers and systems that this refers to a file path rather than a scenario name.
Example:
Scenario: #Users/CreateTestUser
Scenario: #Users/CreateUserRight
Scenario: #Users/ValidateUser
As shown in the example above, reference scenarios do not require any blank lines between them.
Detailed Scenario
These keywords act as grouping tools for steps, actions, and expectations. The definition provides a descriptive name for what is accomplished by the steps and actions. The following keywords outline how to achieve the scenario’s outcome.
Example:
Scenario: Edit user from dashboard
Navigate: /system/users/dashboard
Edit: dbl-click-row, code: "User1"
In this example, we use the keywords “navigate” and “edit” to clarify the intent definition. The context, which involves working with the dashboard, is specified in the scenario definition.
The navigation intent outlines the menu path to follow on the navigation element to access the user’s dashboard screen.
The edit intent explains how to open the edit screen. Here, we need to locate the row on the dashboard where the “code” field is “User1.” Once found, double-clicking the row will open the edit screen.
The term “dbl-click-row” is referred to as an action descriptor. See that section for more details.
A scenario should define a goal to achieve. For example, creating a new work order involves capturing different types of data, with each type representing a comprehensive step. In these cases, you define a single scenario, with its children being steps. Each step clearly outlines the details of what needs to be done for that particular step. Together, all the steps fulfill the goal of the scenario.
Scenario: Create new work order
Step: Capture header information
Given: You are on the header tab.
Set: Code: "ABC123"
...
Step: Capture work required
...
Step: Capture tasks
...
Navigate
The navigate
keyword specifies the intention to move to a different screen. This action is typically performed using the main navigation component, such as the primary menu. The navigate
definition includes the path to the desired menu item. If the item is located within a nested tree structure, define the path by separating each level with a forward slash.
Example:
Navigate: /system/users/dashboard
If you want to navigate to a specific URL, prefix the definition with “url:”. This indicates that the following path is a URL, not a menu structure.
Navigate: url: https://www.google.com
Navigate: url: #screen?parameter=value
Edit
The term “edit” signals the intent to modify a record, though its meaning can vary depending on the context. For example, to edit an item on a dashboard, you typically double-click the relevant row to access the item’s edit screen. On edit screens that feature cross-references, often represented by inputs with lookups, you can also double-click the input to open the edit screen for that specific record. The precise definition of “edit” depends on the context and begins with an action descriptor, which specifies the required action and provides any additional details needed for completion.
The action descriptor “dbl-click-row” applies to tables or grids, while “dbl-click-input” pertains to individual inputs. Grids are often found on separate tabs within edit screens.
In the context of edit screens, grids and inputs may be located on specific tabs. The action descriptor, whether “dbl-click-row” or “dbl-click-input,” can specify the tab where the element is located using tab markup in the definition.
Row Examples:
Edit: dbl-click-row, Code: "User1"
Edit: dbl-click-row, Code: "User1", tab: "Data"
Input Example:
Edit: dbl-click-input, tab: "Details", input: "User Code"
Create
This process is similar to the edit function, operating as a generic concept across different screens and heavily context-dependent. Generally, creation actions are initiated by clicking a button within the specified context. However, there are exceptions. For instance, on a creation screen with a cross-reference input, double-clicking that input should open the creation screen for that cross-reference. Once the creation is complete, the input field should be populated with data from the newly created record.
Input Example:
Create: dbl-click-input, tab: "Details", input: "User Code"
Button Example:
Create: click-button, tab: "Details", button: "Create User"
When referring to a “create” screen, it includes a create button. On a dashboard, “create” implies the create button located on the dashboard. If the button is located elsewhere, such as on a tab, you need to specify that tab as the context for finding the button. Specify the tab only if navigation is required; otherwise, it’s assumed that the context includes the button to act on.
Dashboard Example:
Create: click-button, button: "Create"
Set
The term “set” refers to changing a value. It is a generic term that, depending on the context, could mean either filling in an input field or making cell-level updates in a data grid. The context is crucial for understanding the specific action, but the general principle is that user input is directed towards updating a field in that context.
When making changes to a grid cell, you first need to select the row you want to update using the “Select” keyword.
The set intent can be described in plain English as “set the value of ‘field 1’ to ‘value.’”
Set: Name: "John"
Set: Age: 32
Set: Is Active: True
Note that string data types are enclosed in quotes, whereas other data types are not. The left side of the “:” specifies the field name or label, and the right side of the “:” specifies the value.
Data Grid Example:
Select: Code: "A1000"
Set: Name: "John"
Set: Age: 32
Set: Is Active: True
Edit Screen Example:
Scenario: Edit user from dashboard
Navigate: /system/users/dashboard
Edit: dbl-click-row, Code: "A1000"
Set: Name: "John"
Set: Age: 32
Set: Is Active: True
Submit: "Save and Close"
When using mieliepap for technical specifications, “Set” can be used to set a field value or assign a property to a specific value. When setting a property value, you must also include the instance name or class name (if static) as part of the field definition.
Set: config.default: 10
If the context is already focused on a class, you can omit the prefix and simply define the property or field name.
Select
“Select” means marking an item as selected within a collection, such as a list or data grid. In the select definition, you can specify one or multiple fields to identify the item to select. If a single unique field is available, it is preferred to use that, though this is not always possible. The syntax is similar to “set,” with the field name on the left of the “:” and the value on the right. To define multiple criteria, separate them with commas. If you want to select multiple items, use a select keyword for each item unless selecting a range.
Single Example:
Select: Code: "A1000"
Multiple Example:
Select: First Name: "John", Age: 32
Select: First Name: "Koos", Age: 20
You can also select a range of items. This could include selecting all items, deselecting all items (indicated by “None”), or selecting items from a given start index to an end index.
Select All Items Examples:
Select: All
Select None Example:
Select: None
Select Range Example:
Select: 1 - 5
The range example demonstrates how to select the first five items. While the start index of a collection is typically 0 in programming, this can be confusing. Therefore, we define the first index as 1. In plain English, this reads as: “Select the items from the first item to the fifth item.” You can also use the terms “first” and “last” were applicable.
Select: first - 5
Select: 3 - last
Submit
“Submit” is widely used to indicate the action of saving changes. Different actions can be associated with submitting, such as:
- Save
- Save and New
- Save and Close
When using the “submit” keyword, you need to specify the associated action.
Example:
Submit: Save and Close
Profiles
Profiles are a common pattern for saving and loading different application configurations. Profile actions include:
- Select: Specify the profile name to select.
- Save: No additional information is required, the currently select profile is updated.
- Save as New: Provide a new profile name to save as.
- Delete: Specify the profile name that must be deleted.
To simplify this pattern, the profile keyword should start with the action, followed by the profile name if needed.
Examples:
Profile: Select, "my profile"
Profile: Save
Profile: Save as New, "new profile name"
Profile: Delete, "my profile"
Verify
“Verify” means to confirm or validate that something is accurate, correct, or functioning as intended. In technical contexts, it often involves checking that data, configurations, or processes meet specified criteria or standards. This might include:
- Confirming the accuracy of data.
- Ensuring that system functionalities work correctly.
- Validating that configurations meet required specifications.
In summary, to “verify” is to ensure that something is correct or operational according to predefined conditions or standards.
Use this keyword to verify that expectations are met. The definition should be in plain text, describing what needs to be checked.
Example:
Verify: Status is "in progress"
Do not add multiple verifications on the same line, instead use a new line for each verification separating the concern of one verification from the other.
Given
The Given
keyword is used to define the initial context or setup for a scenario. It specifies the state or conditions that must be in place before the execution of a particular test scenario. The Given
step typically describes the preconditions or the starting situation of the system under test.
For example:
- Given a user is logged in
- Given the shopping cart is empty
These steps establish the starting point for the test scenario, ensuring that the system is in the correct state before the actions (described by When
) and the expected outcomes (described by Then
) are tested.
Example:
Given: The user exists
When
The When
keyword is used to define the actions or events that trigger a change in the system’s state. It describes what happens during the scenario and specifies the user interactions or system operations that lead to the expected outcome.
For example:
- When the user adds an item to the shopping cart
- When the user clicks the “Submit” button
The When
steps detail the actions that occur after the initial conditions set by the Given
steps and before the expected results described by the Then
steps.
Action Example
When: Clicking on the menu item
Event Example
When: Successfully saved
The When
keyword can also be used in technical specifications to indicate events that occur but is not subscribed events.
When: Property value has changed
We also have subscribed events, which are events you listen for using an event listener, such as a click event. For these events, use the On
keyword instead of the When
keyword.
On
The On keyword is used to define when a subscribed event fires.
This could be user input related or custom events from the system.
Examples include:
- On click
- On key down
- On loaded
- On data retried
Different Examples:
On: Click
On: Context click
On: Ready
This is combined with the “Then” keyword, stipulating the result of the event.
Example:
On: Click
Then: Make input read only
Then
In Cucumber, the Then
keyword is used to define the expected outcome or result of a test scenario. It specifies the assertions that need to be checked to verify that the system behaves as expected after the actions described in the When
steps.
For example:
- Then the user should see a confirmation message
- Then the item should be added to the shopping cart
The Then
steps validate that the system’s state or output meets the predefined conditions and confirms the success of the scenario.
When Example:
When: The user confirms the checkout
Then: Perform transaction
When using the Then
keyword in technical documentation, it can specify an action or state resulting from an On
event.
On Example:
On: Click
Then: Make input read only
Rule
In Cucumber, the Rule
keyword is used to group together scenarios or examples that illustrate a specific aspect of a feature. It helps to organize related scenarios under a common context, making the feature file more readable and structured.
Structure of a Rule
- Rule: This keyword starts the section and is followed by a description of the rule.
- Background (optional): Defines steps that are common to all scenarios within the rule.
- Scenario/Scenario Outline: Lists individual scenarios that fall under the rule.
Example:
Feature: User Login
Rule: Valid login attempts
Description: This rule covers all valid login attempts.
Scenario: User logs in with correct credentials
Given: the user is on the login page
When: the user enters valid credentials
Then: the user should be redirected to the dashboard
Rule: Invalid login attempts
Description: This rule covers all invalid login attempts.
Scenario: User logs in with incorrect password
Given: the user is on the login page
When: the user enters an incorrect password
Then: the user should see an error message
For Each
The For Each
keyword is used to iterate through each item in a collection and perform a specified task on it.
For Each: fields
Scenario: #SelectTab
Scenario: #ExpandGroup
Scenario: #FocusInput
In the example above, we loop through a collection named “fields.” For each item, we first execute the predefined scenario “SelectTab,” followed by the scenario “ExpandGroup,” and finally the scenario “FocusInput.” Each of these predefined scenarios includes specific details on how they are executed.
Step
The step keyword is just a simple way to define a process.
Processes are defined as a sequence of steps. For example,
- Create read stream
- Read stream to Text
- Transform Text to JSON
The step keyword is a grouping keyword that contains details about the step.
This can be When-Then statements, Condition evaluations or another sequence of steps.
Step: Load file content in stream (have)
When: Call the process API "files" module, "load" action
Then: Transform stream
Step: Transform stream
Condition: If the format is "Text"
Then: Return stream as text
Condition: If the format is "JSON"
Then: Return stream as JSON
Condition
The condition
keyword specifies the actions to take when a certain condition is met. It represents a truthy statement, meaning it evaluates to true. Typically, the condition
statement is followed by a “Then” keyword, but this is not mandatory. If there are additional scenarios relevant to the given condition, you can define them as children of the condition.
Simple Example:
Condition: If value is 10
Then: Color is red
And Example:
Condition: If value is 10
And: Is active is True
Then: Color is red
Action Descriptors
Action descriptors specify the types of actions to be performed, including various kinds of inputs. When common actions arise, it is useful to create action descriptors for them. For instance, double-clicking a grid row is a frequent action, so having a dedicated action descriptor for it is practical. Similarly, selecting single or multiple items in a grid can also benefit from having its own action descriptor.
Index:
- dbl-click-row: Perform a double-click action on a row in a grid.
- dbl-click-input: Perform a double-click action on an input field.
- context-click-row: Right-click on a row in a grid, expecting a context menu.
- context-click-element: Right-click action on an UI element, expecting a context menu.
- click-row: Left-click on a row in a grid.
- click-button: Left-click on a button.
- click-element: Left-click on a UI element.
- set: Enter a value by typing into a focused element.
Technical Specification
This applies to development technical specifications.
Technical specifications are typically defined by a developer and defines how to build a feature. This would define development methodologies, including classes, functions, Enums and interfaces.
The purpose is to design systems without writing code, facilitating the thought process around what needs to be done. This approach also helps with proper separation of concerns, a pattern that can be challenging for some.
We will primarily use standard development terminology, such as class, method, private, and public, among others.
Simple Example:
Feature: Load file as
Enum: FileFormats
Text: "text", "utf8 text format"
Json: "json", "json format"
Function: load_as
Description: In process api "files" add load as function to load
file content in different formats
Parameters:
Dialog: Type: Boolean, Default: False
FileFormat: Type: FileFormats, Default: FileFormats.Json
Step: Load file content in stream (have)
When: Call the process api "files" module action "load"
Then: Transform stream
Step: Transform stream
Condition: If format is text
Then: Return text
Condition: If format is json
Then: Return json
Result: Type: Array, "Array of file content of defined format"
Classes
Defining class structures is crucial for several reasons:
- Clarify the Class’s Role: It helps in clearly thinking through the signature of the class—what the class does and its responsibilities.
- Member Visibility: It delineates which members (attributes and methods) are public and which are private, ensuring proper encapsulation.
- External Interactions: By defining public methods, it specifies how external entities interact with the class, outlining the inputs these methods require and the outputs or state changes they produce.
- Separation of Concerns: It promotes the separation of concerns by encapsulating related functionality within the class, making the code more modular and maintainable.
- Design and Planning: It aids in the design and planning phase of development, allowing developers to foresee potential issues and dependencies before actual coding begins.
- Documentation: It serves as a form of documentation, providing a blueprint for how the class should be implemented and used.
Using standard development terminology, such as class, method, private, and public, ensures clear communication and understanding among developers.
Example:
Class: Person
Description: "Class representing a person"
Fields:
... same structure as properties
Private Properties:
...
Public Properties:
FirstName: Type: String, Allowed Values: ["John", "Jane"]
LastName: Type: String
Age: Type: Number, Default: 10, Min: 10, Max: 20
Private Methods:
...
Public Methods:
Clone:
Description: Create a new copy of this person
Result: Clone of current Person
SetName:
Description: Update the FirstName,
Result: None
Parameters:
Name: Type: String, Required: True, Default: ""
GetAge:
Description: Get the age value of the person
Result: Type: Number
Events:
Change:
Description: Custom event that fires when a change occurred
Parameters:
Field: Type: String, name of the field that changed
Value: Type: Any, the new value
When defining fields, properties, or parameters, several attributes can be specified based on the context:
- Type: This required attribute defines the data type of the field, property, or parameter.
- Default: Specifies the default value if none is provided. If not defined, it defaults to NULL.
- Min: Defines the minimum value for numeric or date types, or the minimum length for strings.
- Max: Defines the maximum value for numeric or date types, or the maximum length for strings.
- Required: Indicates that a value must be provided.
- Allowed Values: Specifies a collection of valid values, and the provided value must be one of the items in this collection.
To maintain a clear and consistent format for class definitions, the following specifications should be adhered to:
- Indentation and Whitespace:
- Use four spaces for each level of indentation (Tab).
- Ensure consistent use of spaces around colons for readability, right aligning items at the colon.
- Class Definition:
- Begin with the
Class
keyword followed by the class name. - Provide a
Description
on a new line, indented one level from the class name.
- Begin with the
- Fields and Properties:
- Group fields and properties under
Fields
,Private Properties
, orPublic Properties
headers. - Align colons in the attribute definitions for readability.
- Group fields and properties under
- Methods:
- Group methods under
Private Methods
orPublic Methods
headers. - Indent method descriptions and parameters for clarity.
- Methods can also have 1:N scenarios defined when a finer breakdown is required.
- Events are treated as if they are methods but defined in a “Events” definition.
- Group methods under
Functions
Example:
Function: Add
Description: "return the sum of value1 and value2"
Parameters:
Value1: Type: Number, Default: 0
Value2: Type: Number, Default: 0
Result: Type: Number
The function
keyword defines a programming function, its parameters, and its result. Despite appearances, the result
keyword is not a child of parameters
but a sibling. It specifies the return type of the function. You can see the use of white space to indicate separation from the parameters.
You can omit optional parts: if your function has no parameters, you don’t need to define them, and if it doesn’t return a result, you can leave out the result
. However, you must always include the description
keyword, as it outlines the function’s purpose.
Enums
Example:
Enum: ConnectionOptions
Description: This is a bitwise operator for the connections used.
Local: 1, "connect to local machine"
Remote: 2, "connect to a remote server"
When defining the enum we must provide a description to define the intent of the enum, why it exists and how it is used. What follows is the enum properties and their definitions. Note the whitespace between description and the properties to denote separation. The enum property descriptors define the value and description of the property. The value is defined first and the descriptor follows after a comma. The descriptor is a string value describing the purpose of the property. You may not always need a value for a enum property so in those cases you can leave the value part out and leave the descriptor.
Call
The call
keyword is used to define the invocation of functions. This can encompass various scenarios, including:
- Calling local functions
- Invoking methods on instances or static members
- Making remote REST API calls
Any time you need to call a type of function to perform a task, use the call
keyword to specify what is being called.
Local Function Example:
Call: #functionName
Parameters:
Value1: 10
Value2: 10
Note that the function name is defined using a #, indicating that this refers to a named item. In this example we are defining parameters and their values. This counts for all function calls when parameters are used.
Method Example
Call: #myInstance.methodName
Remote Example
Call: "https://my_server/my_get/
Type: Get
Parameters:
Id: 1000