Test Case Templates
Test Case Templates provide features to help make Test Cases more easily re-usable after copying from a Library or another Draft.
Template Syntax
Test Case Templates use the popular Python Jinja2 template syntax, particularly if conditions and expressions
See also
For more information about Jinja2 syntax
Note
If template control structures such as 'if' or 'for' are used in a Test Case then, if there are errors in the Test Case, TrialGrid cannot determine on which line the error has occurred. The errors will be displayed underneath the Test Case but the line(s) will not be highlighted. This is because template control structures might change the number of lines in the final Test Case and its not possible to accurately highlight the line in the original source.
Optional Fields
It is common for Forms in Libraries to contain all Fields which might ever be needed with the expectation that some Fields will be removed or inactivated after copying into a study Draft because those Fields are not relevant for that study.
For example a Library Inclusion/Exclusion Form might contain 5 Inclusion Fields, INC01, INC02, etc but in a study the Field INC04 is not relevant and is removed from the Form. The Library contains an Edit Check to raise a query if any Inclusion Criteria have not been met:
SCR.IE.INC01.CodedValue == "N"
or SCR.IE.INC02.CodedValue == "N"
or SCR.IE.INC03.CodedValue == "N"
or SCR.IE.INC04.CodedValue == "N"
or SCR.IE.INC05.CodedValue == "N"
The study version of the Edit Check will not include the INC04 Field:
SCR.IE.INC01.CodedValue == "N"
or SCR.IE.INC02.CodedValue == "N"
or SCR.IE.INC03.CodedValue == "N"
or SCR.IE.INC05.CodedValue == "N"
The Library contains a Test Case for this Edit Check, with a test scenario to verify the query has been raised:
Scenario: Check Actions are run
When I enter and save data
| DataPoint | Value |
| SCR.IE.INC01[0] | YES |
| SCR.IE.INC02[0] | YES |
| SCR.IE.INC03[0] | NO |
| SCR.IE.INC04[0] | NO |
| SCR.IE.INC05[0] | NO |
| SCR.IE.IE[0] | YES |
Then I should see the following query on "SCR.IE.INC_EXCL[0]"
"""
One or more of the Inclusion/Exclusion criteria was not met, yet subject was checked as eligible for the study. Please reconcile, else clarify.
"""
This Test Case can be copied into the study Draft but cannot be run as-is because the INC04 field does not exist in the draft. One option is to manually remove the data entry line for INC04 but this requires effort and will make the study version different from the library version for standards compliance. Instead the Library Test Case can use an 'if' template condition:
Scenario: Check Actions are run
When I enter and save data
| DataPoint | Value |
| SCR.IE.INC01[0] | YES |
| SCR.IE.INC02[0] | YES |
| SCR.IE.INC03[0] | NO |
{% if draft.has_form_field("IE", "INC04") %}
| SCR.IE.INC04[0] | NO |
{% endif %}
| SCR.IE.INC05[0] | NO |
| SCR.IE.INC_EXCL[0] | YES |
Then I should see the following query on "SCR.IE.INC_EXCL[0]"
"""
One or more of the Inclusion/Exclusion criteria was not met, yet subject was checked as eligible for the study. Please reconcile, else clarify.
"""
When the library test case is copied into the study draft it does not need to be manually changed. TrialGrid will check if the draft has an active Field with OID 'INC04' on an active Form with OID 'IE' and if not then it will not attempt to enter that data into Rave when running the Test Case.
The definition of the Test Case does not have to be modified and it will be standards compliant with the library.
Optional Forms
Test Case Templates can test if a Form with the specified OID is present and active in the draft:
Scenario: Forms are added
When I enter and save data
| DataPoint | Value |
| SCR.IE.INC_EXCL[0] | YES |
{% if draft.has_form("EX1A") %}
Then I should see form "EX1A" in folder "V1"
{% endif %}
{% if draft.has_form("EX1B") %}
And I should see form "EX1B" in folder "V1"
{% endif %}
And I should see form "VT" in folder "V1"
Optional Folders
Test Case Templates can test if a Folder with the specified OID is present and active in the draft:
Scenario: Forms are added
When I enter and save data
| DataPoint | Value |
| SCR.IE.INC_EXCL[0] | YES |
{% if draft.has_folder("V2") %}
Then I should see form "EX1A" in folder "V2"
{% endif %}
And I should see form "VT" in folder "V1"
Custom Properties
Test Case Templates can use Draft and Project Custom Properties:
Scenario: Forms are added
When I enter and save data
| DataPoint | Value |
| SCR.IE.INC_EXCL[0] | YES |
{% if draft.property("TA") == "Oncology" %}
Then I should see form "EX1A" in folder "V1"
{% endif %}
{% if project.property("US") %}
And I should see form "EX1B" in folder "V1"
{% endif %}
{% if draft.property("Version")|int > 2 %}
And I should see form "VT" in folder "V1"
{% endif %}
In the above example the Project property "US" is a boolean property. "TA" is a text property which can be compared against string values. To do a numeric comparison the custom property must be converted into an integer as in the "Version" example above.
Custom Property values can be inserted into the Test Case using expressions:
Scenario: Scenario 1
When I enter and save data
| DataPoint | Value |
| AE_FOLDER.AE.AETERM[1] | {{ project.property("Study Drug") }} |
When TrialGrid runs the Test Case it will use the value of the Project's Custom Property to enter data into the AETERM Field.
Folder placeholders
To avoid having to specify a Folder in a Test Case a Folder placeholder can be used:
@EditCheck: AE001
Scenario: Folder placeholder
Given folder "{{ folder }}" exists
The Test Case must have an Edit Check reference. If more than one Edit Check reference is present then the one which occurs first in the Test Case will be used.
TrialGrid will analyze the Edit Check and find a Folder to insert into the Test Case. If the Edit Check contains steps where the Folder is not specified ('wild-carded') then TrialGrid will insert an appropriate Folder. If the Edit Check contains more than one Folder then an error will be raised since TrialGrid does not know which one to choose.
Action datapoint placeholders
To avoid having to specify the datapoint on which a Check Action will operate a placeholder can be used:
@EditCheck: AE001
Scenario: Folder placeholder
Then I should see "{{ action_datapoint }}" is empty
The Test Case must have an Edit Check reference. If more than one Edit Check reference is present then the one which occurs first in the Test Case will be used.
TrialGrid will analyze the Edit Check and find a Check Action target datapoint to insert into the Test Case. 'IsPresent' Check Actions will be ignored, unless no other Check Actions are present as will 'Custom Function' Check Actions which reference a Custom Function which has code 'return true;' (an 'Always True' Custom Function). If the Edit Check contains more than one Check Action (other than IsPresent and Always True Custom Functions) it will be marked as an error.
Using For loops to avoid repetition
To avoid having to repeat similar steps a template For loop can be used.
For example several scenarios can be created for different folders like this:
{% for oid in ['V1', 'V2', 'V3'] %}
Scenario: Test Folder {{ OID }}
Given folder "{{ oid }}" exists
When I enter and save data
| DataPoint | Value |
| {{oid}.AE.AETERM[1] | HEADACHE |
{% endfor %}
This will be expanded automatically into:
Scenario: Test Folder V1
Given folder "V1" exists
When I enter and save data
| DataPoint | Value |
| V1.AE.AETERM[1] | HEADACHE |
Scenario: Test Folder V2
Given folder "V2" exists
When I enter and save data
| DataPoint | Value |
| V2.AE.AETERM[1] | HEADACHE |
Scenario: Test Folder V3
Given folder "V3" exists
When I enter and save data
| DataPoint | Value |
| V3.AE.AETERM[1] | HEADACHE |
Important
When using For loops it is tempting to create Test Cases which, for example, loop through all Folders in a study. This can lead to very large Test Cases, with a long run time. If a study has 100 Folders, TrialGrid can loop through all of them, but this will take approximately 100 times longer than testing in 1 Folder. Please consider whether it is necessary to test in all Folders. Normally it will be sufficient to test in a small number.
For loops can be used for Test Case content, not only OIDS:
Scenario: Test Valid Data Values
{% for data in [1, 2, 4] %}
When I enter and save data
| DataPoint | Value |
| DAY1.VISIT.VISITTYPE[0] | {{data}} |
Then I should not see queries on DAY1.VISIT.VISITTYPE[0]
{% endfor %}
This will be expanded automatically into:
Scenario: Test Valid Data Values
When I enter and save data
| DataPoint | Value |
| DAY1.VISIT.VISITTYPE[0] | 1 |
Then I should not see queries on DAY1.VISIT.VISITTYPE[0]
When I enter and save data
| DataPoint | Value |
| DAY1.VISIT.VISITTYPE[0] | 2 |
Then I should not see queries on DAY1.VISIT.VISITTYPE[0]
When I enter and save data
| DataPoint | Value |
| DAY1.VISIT.VISITTYPE[0] | 4 |
Then I should not see queries on DAY1.VISIT.VISITTYPE[0]
For loops can be nested:
Scenario: Test Valid Data Values
When I enter and save data
| DataPoint | Value |
{% for folder in ["VISIT1", "VISIT2", "VISIT3"] %}
{% for form in ["VS1", "VS2"] %}
| {{folder}}.{{form}}.VISITTYPE[0] | 1 |
{% endfor %}
{% endfor %}
This will be expanded automatically into:
Scenario: Test Valid Data Values
When I enter and save data
| DataPoint | Value |
| VISIT1.VS1.VISITTYPE[0] | 1 |
| VISIT1.VS2.VISITTYPE[0] | 1 |
| VISIT2.VS1.VISITTYPE[0] | 1 |
| VISIT2.VS2.VISITTYPE[0] | 1 |
| VISIT3.VS1.VISITTYPE[0] | 1 |
| VISIT3.VS2.VISITTYPE[0] | 1 |
Important
Nested For loops can quickly lead to large Test Cases. As above please consider whether all Test Steps are necessary
Sometimes you may wish to combine two or more sets of data, for example:
Scenario: Test Folder/Forms
{% for folder,form,dt in [("VISIT1", "VS1", "1 JAN 2022"), ("VISIT2", "VS1A", "10 JAN 2022"), ("VISIT3", "VS1", "12 JAN 2022")] %}
When I enter and save data
| DataPoint | Value |
| {{folder}}.{{form}}.VSDT[0] | {{ dt }} |
{% endfor %}
This will be expanded automatically into:
Scenario: Test Valid Data Values
When I enter and save data
| DataPoint | Value |
| VISIT1.VS1.VSDT[0] | 1 JAN 2022 |
When I enter and save data
| DataPoint | Value |
| VISIT2.VS1A.VSDT[0] | 10 JAN 2022 |
When I enter and save data
| DataPoint | Value |
| VISIT3.VS1.VSDT[0] | 12 JAN 2022 |
Note
The maximum length of a Test Case is 1 million characters and 20000 lines after processing any template control structures.
Variables
Variables can be defined using the 'set' statement.
This example sets a variable called FORM_OID and uses it to insert the value into the data entry step.
{% set FORM_OID = "IE" %}
Scenario: Forms are added
When I enter and save data
| DataPoint | Value |
| SCR.{{FORM_OID}}.INC_EXCL[0] | YES |
Including content from a Template Test Case
A Test Case can be set to be a 'Template' by checking the Template checkbox. A Template Test Case can be included into another Test Case. Both Test Cases must be in the same Draft.
If there is a Template Test Case called 'Standard Background' with content:
Given I am logged in with role "Investigator"
And I create or select the subject named "{testcase_name}_{scenario_name}"
then it can be included into another Test Case using the 'include' command:
Background:
{% include 'Standard Background' %}
Scenario: Forms are added
When I enter and save data
| DataPoint | Value |
| SCR.{{FORM_OID}}.INC_EXCL[0] | YES |
The steps are copied when the Test Case is viewed or run. These will be the steps which are run:
Background:
Given I am logged in with role "Investigator"
And I create or select the subject named "{testcase_name}_{scenario_name}"
Scenario: Forms are added
When I enter and save data
| DataPoint | Value |
| SCR.{{FORM_OID}}.INC_EXCL[0] | YES |
Note
If a Template Test Case is imported or included in other Test Cases then its name cannot be changed.
Note
A Template Test Case cannot import or include another Template Test Case.
Note
When saving a Template Test Case you may see errors or warnings, for example that the Template has no Scenarios. The Template will be saved, and can be included or imported into other Test Cases.
Importing a Template Test Case into a Test Case
A Template Test Case can be 'imported' into another Test Case. Both Test Cases must be in the same Draft.
'Importing' a Template Test Case is different from 'Including' in that any variables defined in the Imported Test Case are available for use in the main Test Case.
If there is a Template Test Case called 'Settings' with content:
{% set FORM_OID = "IE" %}
{% set FOLDER_OID = "SCR" %}
then it can be imported into another Test Case using the 'import' command:
Background:
{% import 'Settings' as settings with context %}
Scenario: Forms are added
When I enter and save data
| DataPoint | Value |
| {{settings.FOLDER_OID}}.{{settings.FORM_OID}}.INC_EXCL[0] | YES |
The import must be given a name, 'settings' in the above example, but this can be any name you wish to use.
Commenting out a Template structure
If you wish to comment out a Test Case Template structure such as a 'for' loop, the '#' symbol should replace the '%' symbol, like this:
Scenario: Test Valid Data Values
{# for data in [1, 2, 4] #}
When I enter and save data
| DataPoint | Value |
| DAY1.VISIT.VISITTYPE[0] | 1 {# {{data}} #} |
Then I should not see queries on DAY1.VISIT.VISITTYPE[0]
{# endfor #}
Placeholders inside the 'for' loop will also need to be replaced, or commented out, as above.