원클릭으로
test-quality
Spock/Groovy testing standards and best practices for CPS project.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Spock/Groovy testing standards and best practices for CPS project.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | test-quality |
| description | Spock/Groovy testing standards and best practices for CPS project. |
This document describes important aspects of our Spock/Groovy test framework, conventions, and best practices observed in the CPS-NCMP codebase.
spock.lang.SpecificationListAppender)Mono, Flux)Spec suffixDataJobServiceImplSpec, TrustLevelManagerSpec, NetworkCmProxyFacadeSpecdef 'Description of what is being tested'()then: blocks with descriptions to express expectations insteaddef 'Registration with invalid cm handle name.'()def 'Registration of invalid cm handle throws exception.'()def 'Read data job request.'()def 'DMI Registration: Create, Update, Delete & Upgrade operations are processed in the right order'()def 'Initial cm handle registration with a cm handle that is not trusted'()mock (e.g., mockInventoryPersistence, mockDmiSubJobRequestHandler)objectUnderTest (not custom names like parameterMapper or class-specific names)result for the outcome of method callsmy or some for values that don't affect the test (e.g., 'my user', 'some scope')Tests use labeled blocks for clarity and readability:
def 'Test description'() {
given: 'context setup description'
// Setup code
and: 'additional context'
// More setup
when: 'action being tested'
// Execute the method under test
then: 'expected outcome'
// Assertions and verifications
and: 'additional expectations'
// More assertions
}
Pattern with constructor injection and mocks:
class DataJobServiceImplSpec extends Specification {
def mockWriteRequestExaminer = Mock(WriteRequestExaminer)
def mockDmiSubJobRequestHandler = Mock(DmiSubJobRequestHandler)
def objectUnderTest = new DataJobServiceImpl(
mockDmiSubJobRequestHandler,
mockWriteRequestExaminer,
mockJsonObjectMapper
)
}
Alternative with Spy for partial mocking:
def objectUnderTest = Spy(new CmHandleRegistrationService(
mockPropertyHandler,
mockInventoryPersistence,
...
))
Stubbing return values:
mockInventoryPersistence.getYangModelCmHandle('ch-1') >> new YangModelCmHandle(id: 'ch-1')
Verifying method calls:
1 * mockInventoryEventProducer.sendAvcEvent('ch-1', 'trustLevel', 'NONE', 'COMPLETE')
0 * mockInventoryEventProducer.sendAvcEvent(*_) // No calls expected
Argument matching:
1 * mockWriteRequestExaminer.splitDmiWriteOperationsFromRequest('my-job-id', dataJobWriteRequest)
mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> [] // Any arguments
Use where block for data tables:
def 'Determining cloud event using ce_type header for a #scenario.'() {
given: 'headers contain a header for key: #key'
headers.lastHeader(key) >> header
expect: 'the check for cloud events returns #expectedResult'
assert objectUnderTest.isCloudEvent(headers) == expectedResult
where: 'the following headers (keys) are defined'
scenario | key || expectedResult
'cloud event' | 'ce_type' || true
'non-cloud event' | 'other' || false
}
Conventions:
#variableName in test name to include parameter values in test output| to separate input columns, || before expected output columnswhere block are accessible throughout the testdef setup() {
setupLogger(Level.DEBUG)
mockAlternateIdChecker.getIdsOfCmHandlesWithRejectedAlternateId(*_) >> []
}
def cleanup() {
((Logger) LoggerFactory.getLogger(DataJobServiceImpl.class)).detachAndStopAllAppenders()
hazelcastInstance.shutdown()
}
Groovy assert (preferred):
assert result.statusCode == HttpStatus.OK
assert trustLevelPerCmHandleId.get('ch-1') == TrustLevel.COMPLETE
Spock with block for complex assertions:
with(logger.list.find { it.level == Level.DEBUG }) {
assert it.formattedMessage.contains("Initiating WRITE operation")
}
JUnit-style assertions (less common):
assertEquals(expected, actual)
assertTrue(condition)
def logger = Spy(ListAppender<ILoggingEvent>)
def setup() {
def setupLogger = ((Logger) LoggerFactory.getLogger(DataJobServiceImpl.class))
setupLogger.setLevel(Level.DEBUG)
setupLogger.addAppender(logger)
logger.start()
}
def 'Test with logging verification'() {
when: 'method is called'
objectUnderTest.someMethod()
then: 'correct log message is generated'
def loggingEvent = logger.list[0]
assert loggingEvent.level == Level.INFO
assert loggingEvent.formattedMessage.contains('Expected message')
}
def 'Get resource data from DMI (delegation).'() {
given: 'a cm resource address'
def cmResourceAddress = new CmResourceAddress('ncmp-datastore:operational', 'ch-1', 'resource-id')
and: 'get resource data returns a Mono'
mockHandler.executeRequest(cmResourceAddress, 'options', NO_TOPIC, false, 'auth') >>
Mono.just('dmi response')
when: 'get resource data is called'
def response = objectUnderTest.getResourceDataForCmHandle(cmResourceAddress, ...).block()
then: 'response is correct'
assert response == 'dmi response'
}
then: blocks with descriptions insteaddef 'Registration with invalid cm handle name.'() with then: 'a validation exception is thrown'def 'Registration of invalid cm handle throws exception.'()def 'Initial cm handle registration with a cm handle that is not trusted'()def 'test1'()and blocks to break up complex setupsgiven: 'a invalid cm handle name'
cmHandle.id = 'invalid,name'
when: 'permission is checked for unauthorized user'
objectUnderTest.checkPermission(someCmh, 'my user', 'some scope')
then: 'exception message contains the user id'
thrown(PermissionException).message.contains('my user')
setup() when applicableassert over JUnit assertionsnew CmHandle(id: 'invalid,name')NO_TOPIC = null)my or some0 * to ensure methods are NOT called when expected*_ (any arguments) sparingly; prefer specific argument matching#paramNamethen: blocks insteadobjectUnderTest and result, not custom namesmy or some prefixes for arbitrary values to distinguish from important onesgiven:, when:, then: descriptions synchronized with codewhere blocks: Don't duplicate similar tests; parameterize insteadcps-ncmp-service/src/test/groovy/