Groovy & Spock Test Code Conventions
Resources
Naming conventions
Specification classes are named as per the following:
Unit test specifications:
<class_under_test>Spec
Integration test specification (using dependency instantiations or running containers):
<class_under_test>IntegrationSpec
Use of Spock Blocks and Labels
The test 'def' should describe a high level scenario (not set up details or expected outcome)
Each label should have a description. The label plus description should form a readable English sentence preferring high level (Product Owner)
type language over code level langue i.e. no method names etc.
(since the label is the first word of the sentence the description should start with a lower case character and end with a full stop)
Note. Well known acronyms should remain in uppercase as in normal (writing) language. e.g. 'the ROP period'All blocks should be separated by a blank line except and 'and: ' block which really is a continuation of the previous block
Given-when-then v. expect. Given-when-then is the preferred format but expect block is useful in situations where it 'is more natural to describe
stimulus and expected response in a single expression' see also the Spock PrimerExpect and Labels Example
@Unroll def 'Celltrace scanner activation request with invalid ropPeriod value of #ropPeriod.'() { given: 'scanner details with an invalid rop period' eventHeaders = eventHeadersForCellTrace(ropPeriod, 'STREAMING') expect: 'the outbound request XML contains the default rop period of "FIFTEEN_MIN"' activateEventJobRequest.getRequest(eventHeaders).contains("<reportingPeriod>FIFTEEN_MIN</reportingPeriod>") where: 'ROP period is any of these invalid values' ropPeriod << [ null, 0, -1, 123 ] }
Expected outputs in Spock data tables should be separated with a double pipe symbol (||) to visually set them apart
Use of assert keyword
The assert keyword is optional in 'expect:' or 'then:' blocks. Groovy will automatically 'assert' any Boolean expression in these blocks.
Depending on the complexity of the expression and the amount of code in this block it might help to highlight what is being asserted using the 'assert' keyword.
Current findings is that assertions are sometimes refactored into separate code blocks (methods or closures) and the DO NEED to use the assert keyword! But that often gets forgotten then and tets seems to pass but the assertions are no longer being checked and can lead to future false positives. To prevent this it is now recommended to always use the assert keyword.
In the example above we should have used
Expect and Labels Example
expect: 'the outbound request XML contains the default rop period of "FIFTEEN_MIN"'
assert activateEventJobRequest.getRequest(eventHeaders).contains("<reportingPeriod>FIFTEEN_MIN</reportingPeriod>")
Note. 'assert' is required in method or closures that arre called from these block if those methods contain Boolean expressions that need to be asserted!
Some more tips from the Spock Primer
"Try to keep the number of conditions per feature method small. One to five conditions is a good guideline. If you have more than that, ask
yourself if you are specifying multiple unrelated features at once. If the answer is yes, break up the feature method in several smaller ones. If your
conditions only differ in their values, consider using a data table."Leverage Groovy JDK methods like any() and every() to create more expressive and succinct conditions.
Mixing of Java & Groovy
Groovy files should be 'pure' groovy as far as possible, which would imply some rules like
No access modifiers
No semi-colons
To prevent conflict between java and groovy style code groovy classes should use groovy style methods using 'def'.
In some cases it might be useful (more readable) to specify the return typeGroovy method examples
def eventHeadersForCellTrace(ropPeriod, outputMode, eventDetails) { return [nodeAddress : 'NetworkElement=LTE07dg2ERBS00001', SUBSCRIPTION_TYPE : 'CELLTRACE', eventDetails : eventDetails, ropPeriod : ropPeriod, scannerId : '10003', streamInfoAddress : '1.2.3.5', streamInfoPort : '14'] } def clickCreateSubscription(final String subscriptionType) { final String selector = getSubscriptionTypeSelector(subscriptionType) 0..4.each { falseOnException { attemptClick(selector) } } } Map<String, String> getCreateSubscriptionDropdownOptions() { click(createSubscriptionDropdown) def options = [:] def elements = root.findElements(By.xpath("//div[contains(@class,'ebComponentList-item')]")) for (def i = 1; i < elements.size(); i++) { options.put(elements.get(i).text, elements.get(i).text) } click(createSubscriptionDropdown) return options }
All strings should be enclosed using single apostrophes, unless it is a Groovy template (see https://stackoverflow.com/questions/6761498/whats-the-difference-of-strings-within-single-or-double-quotes-in-groovy)
please note #parameter in Spock labels does not rely on GString functionality, so labels can be enclosed in single apostrophes too.Label Example
Anti patterns
Test duplication instead of using of data table or data pipes
TBD: Create examplesAbuse of Spock tables; combining many tests (for different behavior) into one large table
TBD: Create examplesJUnit legacy behavior : All conditions and expectations in the test name/scenario
Instead of using the given-when-then label descriptions the developer puts all those in the scenario/test title.