Skip to content

Commit 46fcd2a

Browse files
Divide Spring unit and integration testing in codegen (#2230)
1 parent ab88dce commit 46fcd2a

File tree

28 files changed

+491
-260
lines changed

28 files changed

+491
-260
lines changed

docs/UnitTestBotDecomposition.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ We use an outdated approach with the [Soot](https://github.com/soot-oss/soot) fr
5959
The current domain of code generation is specific for generating tests, though it could be reused for other purposes. Currently, the engine can be used to generate tests for different test frameworks. One can use the code generator to generate test templates inside the IntelliJ-based IDEs.
6060

6161
Entry and exit point:
62-
`org.utbot.framework.codegen.CodeGenerator#generateAsStringWithTestReport`
62+
`org.utbot.framework.codegen.generator.CodeGenerator#generateAsStringWithTestReport`
63+
64+
Note that for Spring projects `SpringCodeGenerator` is used. It supports both unit and integration tests generation.
6365

6466
## SARIF report visualizer
6567

utbot-cli/src/main/kotlin/org/utbot/cli/GenerateTestsAbstractCommand.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@ import org.utbot.common.PathUtil.toURL
1616
import org.utbot.common.toPath
1717
import org.utbot.engine.Mocker
1818
import org.utbot.framework.UtSettings
19-
import org.utbot.framework.codegen.CodeGenerator
2019
import org.utbot.framework.codegen.domain.ForceStaticMocking
2120
import org.utbot.framework.codegen.domain.MockitoStaticMocking
2221
import org.utbot.framework.codegen.domain.NoStaticMocking
2322
import org.utbot.framework.codegen.domain.ProjectType
2423
import org.utbot.framework.codegen.domain.StaticsMocking
2524
import org.utbot.framework.codegen.domain.testFrameworkByName
25+
import org.utbot.framework.codegen.generator.CodeGenerator
2626
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
2727
import org.utbot.framework.plugin.api.ClassId
2828
import org.utbot.framework.plugin.api.CodegenLanguage

utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,7 +1341,7 @@ class SpringApplicationContext(
13411341
): Boolean = field.fieldId in classUnderTest.allDeclaredFieldIds && field.declaringClass.id !in springInjectedClasses
13421342
}
13431343

1344-
enum class SpringTestType(
1344+
enum class SpringTestsType(
13451345
override val id: String,
13461346
override val displayName: String,
13471347
override val description: String,
@@ -1363,7 +1363,7 @@ enum class SpringTestType(
13631363

13641364
companion object : CodeGenerationSettingBox {
13651365
override val defaultItem = UNIT_TESTS
1366-
override val allItems: List<SpringTestType> = values().toList()
1366+
override val allItems: List<SpringTestsType> = values().toList()
13671367
}
13681368
}
13691369

utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ package org.utbot.external.api
33
import org.utbot.common.FileUtil
44
import org.utbot.common.nameOfPackage
55
import org.utbot.framework.UtSettings
6-
import org.utbot.framework.codegen.CodeGenerator
76
import org.utbot.framework.codegen.domain.ForceStaticMocking
87
import org.utbot.framework.codegen.domain.Junit5
98
import org.utbot.framework.codegen.domain.NoStaticMocking
109
import org.utbot.framework.codegen.domain.ProjectType
1110
import org.utbot.framework.codegen.domain.StaticsMocking
1211
import org.utbot.framework.codegen.domain.TestFramework
12+
import org.utbot.framework.codegen.generator.CodeGenerator
1313
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
1414
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionData
1515
import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.utbot.framework.codegen.domain.builtin
2+
3+
import org.utbot.framework.plugin.api.BuiltinClassId
4+
5+
internal val autowiredClassId = BuiltinClassId(
6+
canonicalName = "org.springframework.beans.factory.annotation",
7+
simpleName = "Autowired",
8+
)

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/TestClassModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package org.utbot.framework.codegen.domain.models
22

3-
import org.utbot.framework.codegen.domain.UtModelWrapper
3+
import org.utbot.framework.codegen.domain.models.builders.TypedModelWrappers
44
import org.utbot.framework.plugin.api.ClassId
55

66
/**
@@ -30,7 +30,7 @@ class SpringTestClassModel(
3030
classUnderTest: ClassId,
3131
methodTestSets: List<CgMethodTestSet>,
3232
nestedClasses: List<SimpleTestClassModel>,
33-
val injectedMockModels: Map<ClassId, Set<UtModelWrapper>> = mapOf(),
34-
val mockedModels: Map<ClassId, Set<UtModelWrapper>> = mapOf(),
33+
val thisInstanceModels: TypedModelWrappers = mapOf(),
34+
val thisInstanceDependentMocks: TypedModelWrappers = mapOf(),
3535
): TestClassModel(classUnderTest, methodTestSets, nestedClasses)
3636

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/models/builders/SpringTestClassModelBuilder.kt

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,25 @@ import org.utbot.framework.plugin.api.UtPrimitiveModel
1919
import org.utbot.framework.plugin.api.UtVoidModel
2020
import org.utbot.framework.plugin.api.isMockModel
2121

22+
typealias TypedModelWrappers = Map<ClassId, Set<UtModelWrapper>>
23+
2224
class SpringTestClassModelBuilder(val context: CgContext): TestClassModelBuilder() {
2325

2426
override fun createTestClassModel(classUnderTest: ClassId, testSets: List<CgMethodTestSet>): SpringTestClassModel {
2527
val baseModel = SimpleTestClassModelBuilder(context).createTestClassModel(classUnderTest, testSets)
26-
val (injectedModels, mockedModels) = collectInjectedAndMockedModels(testSets)
28+
val (thisInstanceModels, dependentMockModels) = collectThisInstanceAndDependentModels(testSets)
2729

2830
return SpringTestClassModel(
2931
classUnderTest = baseModel.classUnderTest,
3032
methodTestSets = baseModel.methodTestSets,
3133
nestedClasses = baseModel.nestedClasses,
32-
injectedMockModels = injectedModels,
33-
mockedModels = mockedModels
34+
thisInstanceModels = thisInstanceModels,
35+
thisInstanceDependentMocks = dependentMockModels
3436
)
3537
}
3638

37-
private fun collectInjectedAndMockedModels(testSets: List<CgMethodTestSet>): Pair<Map<ClassId, Set<UtModelWrapper>>, Map<ClassId, Set<UtModelWrapper>>> {
38-
val thisInstances = mutableSetOf<UtModelWrapper>()
39+
private fun collectThisInstanceAndDependentModels(testSets: List<CgMethodTestSet>): Pair<TypedModelWrappers, TypedModelWrappers> {
40+
val thisInstanceModels = mutableSetOf<UtModelWrapper>()
3941
val thisInstancesDependentModels = mutableSetOf<UtModelWrapper>()
4042

4143
with(context) {
@@ -46,7 +48,7 @@ class SpringTestClassModelBuilder(val context: CgContext): TestClassModelBuilder
4648
setOf(execution.stateBefore.thisInstance, execution.stateAfter.thisInstance)
4749
.filterNotNull()
4850
.forEach { model ->
49-
thisInstances += model.wrap()
51+
thisInstanceModels += model.wrap()
5052
thisInstancesDependentModels += collectByThisInstanceModel(model)
5153
}
5254
}
@@ -58,10 +60,10 @@ class SpringTestClassModelBuilder(val context: CgContext): TestClassModelBuilder
5860
val dependentMockModels =
5961
thisInstancesDependentModels
6062
.filterTo(mutableSetOf()) { cgModel ->
61-
cgModel.model.isMockModel() && cgModel !in thisInstances
63+
cgModel.model.isMockModel() && cgModel !in thisInstanceModels
6264
}
6365

64-
return thisInstances.groupByClassId() to dependentMockModels.groupByClassId()
66+
return thisInstanceModels.groupByClassId() to dependentMockModels.groupByClassId()
6567
}
6668

6769
private fun collectByThisInstanceModel(model: UtModel): Set<UtModelWrapper> {
@@ -71,7 +73,7 @@ class SpringTestClassModelBuilder(val context: CgContext): TestClassModelBuilder
7173
return dependentModels
7274
}
7375

74-
private fun Set<UtModelWrapper>.groupByClassId(): Map<ClassId, Set<UtModelWrapper>> {
76+
private fun Set<UtModelWrapper>.groupByClassId(): TypedModelWrappers {
7577
val classModels = mutableMapOf<ClassId, Set<UtModelWrapper>>()
7678

7779
for (modelGroup in this.groupBy { it.model.classId }) {

utbot-framework/src/main/kotlin/org/utbot/framework/codegen/CodeGenerator.kt renamed to utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/AbstractCodeGenerator.kt

Lines changed: 11 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,18 @@
1-
package org.utbot.framework.codegen
1+
package org.utbot.framework.codegen.generator
22

33
import mu.KotlinLogging
44
import org.utbot.framework.codegen.domain.ForceStaticMocking
55
import org.utbot.framework.codegen.domain.HangingTestsTimeout
66
import org.utbot.framework.codegen.domain.ParametrizedTestSource
77
import org.utbot.framework.codegen.domain.ProjectType
8-
import org.utbot.framework.codegen.domain.ProjectType.*
98
import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour
109
import org.utbot.framework.codegen.domain.StaticsMocking
1110
import org.utbot.framework.codegen.domain.TestFramework
12-
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
1311
import org.utbot.framework.codegen.domain.context.CgContext
1412
import org.utbot.framework.codegen.domain.models.CgClassFile
15-
import org.utbot.framework.codegen.domain.models.builders.SimpleTestClassModelBuilder
16-
import org.utbot.framework.codegen.domain.models.builders.SpringTestClassModelBuilder
13+
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
1714
import org.utbot.framework.codegen.renderer.CgAbstractRenderer
18-
import org.utbot.framework.codegen.reports.TestsGenerationReport
19-
import org.utbot.framework.codegen.tree.CgSimpleTestClassConstructor
20-
import org.utbot.framework.codegen.tree.ututils.UtilClassKind
2115
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
22-
import org.utbot.framework.codegen.tree.CgSpringTestClassConstructor
2316
import org.utbot.framework.plugin.api.ClassId
2417
import org.utbot.framework.plugin.api.CodegenLanguage
2518
import org.utbot.framework.plugin.api.ExecutableId
@@ -28,9 +21,9 @@ import org.utbot.framework.plugin.api.UtMethodTestSet
2821
import java.time.LocalDateTime
2922
import java.time.format.DateTimeFormatter
3023

31-
open class CodeGenerator(
32-
val classUnderTest: ClassId,
33-
val projectType: ProjectType,
24+
abstract class AbstractCodeGenerator(
25+
classUnderTest: ClassId,
26+
projectType: ProjectType,
3427
paramNames: MutableMap<ExecutableId, List<String>> = mutableMapOf(),
3528
generateUtilClassFile: Boolean = false,
3629
testFramework: TestFramework = TestFramework.defaultItem,
@@ -46,8 +39,7 @@ open class CodeGenerator(
4639
enableTestsTimeout: Boolean = true,
4740
testClassPackageName: String = classUnderTest.packageName,
4841
) {
49-
50-
private val logger = KotlinLogging.logger {}
42+
protected val logger = KotlinLogging.logger {}
5143

5244
open var context: CgContext = CgContext(
5345
classUnderTest = classUnderTest,
@@ -80,49 +72,14 @@ open class CodeGenerator(
8072
val cgTestSets = testSets.map { CgMethodTestSet(it) }.toList()
8173
return withCustomContext(testClassCustomName) {
8274
context.withTestClassFileScope {
83-
when (context.projectType) {
84-
Spring -> generateForSpringClass(cgTestSets)
85-
else -> generateForSimpleClass(cgTestSets)
86-
}
75+
generate(cgTestSets)
8776
}
8877
}
8978
}
9079

91-
private fun generateForSimpleClass(testSets: List<CgMethodTestSet>): CodeGeneratorResult {
92-
val astConstructor = CgSimpleTestClassConstructor(context)
93-
val testClassModel = SimpleTestClassModelBuilder(context).createTestClassModel(classUnderTest, testSets)
94-
95-
logger.info { "Code generation phase started at ${now()}" }
96-
val testClassFile = astConstructor.construct(testClassModel)
97-
logger.info { "Code generation phase finished at ${now()}" }
98-
99-
val generatedCode = renderToString(testClassFile)
100-
101-
return CodeGeneratorResult(
102-
generatedCode = generatedCode,
103-
utilClassKind = UtilClassKind.fromCgContextOrNull(context),
104-
testsGenerationReport = astConstructor.testsGenerationReport
105-
)
106-
}
107-
108-
private fun generateForSpringClass(testSets: List<CgMethodTestSet>): CodeGeneratorResult {
109-
val astConstructor = CgSpringTestClassConstructor(context)
110-
val testClassModel = SpringTestClassModelBuilder(context).createTestClassModel(classUnderTest, testSets)
111-
112-
logger.info { "Code generation phase started at ${now()}" }
113-
val testClassFile = astConstructor.construct(testClassModel)
114-
logger.info { "Code generation phase finished at ${now()}" }
80+
protected abstract fun generate(testSets: List<CgMethodTestSet>): CodeGeneratorResult
11581

116-
val generatedCode = renderToString(testClassFile)
117-
118-
return CodeGeneratorResult(
119-
generatedCode = generatedCode,
120-
utilClassKind = UtilClassKind.fromCgContextOrNull(context),
121-
testsGenerationReport = astConstructor.testsGenerationReport
122-
)
123-
}
124-
125-
private fun renderToString(testClassFile: CgClassFile): String {
82+
protected fun renderToString(testClassFile: CgClassFile): String {
12683
logger.info { "Rendering phase started at ${now()}" }
12784
val renderer = CgAbstractRenderer.makeRenderer(context)
12885
testClassFile.accept(renderer)
@@ -131,7 +88,7 @@ open class CodeGenerator(
13188
return renderer.toString()
13289
}
13390

134-
private fun now() = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"))
91+
protected fun now(): String = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"))
13592

13693
/**
13794
* Wrapper function that configures context as needed for utbot-online:
@@ -147,17 +104,4 @@ open class CodeGenerator(
147104
context = prevContext
148105
}
149106
}
150-
}
151-
152-
/**
153-
* @property generatedCode the source code of the test class
154-
* @property testsGenerationReport some info about test generation process
155-
* @property utilClassKind the kind of util class if it is required, otherwise - null
156-
*/
157-
data class CodeGeneratorResult(
158-
val generatedCode: String,
159-
val testsGenerationReport: TestsGenerationReport,
160-
// null if no util class needed, e.g. when we are generating utils directly into test class
161-
val utilClassKind: UtilClassKind? = null,
162-
)
163-
107+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package org.utbot.framework.codegen.generator
2+
3+
import org.utbot.framework.codegen.domain.ForceStaticMocking
4+
import org.utbot.framework.codegen.domain.HangingTestsTimeout
5+
import org.utbot.framework.codegen.domain.ParametrizedTestSource
6+
import org.utbot.framework.codegen.domain.ProjectType
7+
import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour
8+
import org.utbot.framework.codegen.domain.StaticsMocking
9+
import org.utbot.framework.codegen.domain.TestFramework
10+
import org.utbot.framework.codegen.domain.models.CgMethodTestSet
11+
import org.utbot.framework.codegen.domain.models.builders.SimpleTestClassModelBuilder
12+
import org.utbot.framework.codegen.services.language.CgLanguageAssistant
13+
import org.utbot.framework.codegen.tree.CgSimpleTestClassConstructor
14+
import org.utbot.framework.codegen.tree.ututils.UtilClassKind
15+
import org.utbot.framework.plugin.api.ClassId
16+
import org.utbot.framework.plugin.api.CodegenLanguage
17+
import org.utbot.framework.plugin.api.ExecutableId
18+
import org.utbot.framework.plugin.api.MockFramework
19+
20+
open class CodeGenerator(
21+
val classUnderTest: ClassId,
22+
val projectType: ProjectType,
23+
paramNames: MutableMap<ExecutableId, List<String>> = mutableMapOf(),
24+
generateUtilClassFile: Boolean = false,
25+
testFramework: TestFramework = TestFramework.defaultItem,
26+
mockFramework: MockFramework = MockFramework.defaultItem,
27+
staticsMocking: StaticsMocking = StaticsMocking.defaultItem,
28+
forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem,
29+
generateWarningsForStaticMocking: Boolean = true,
30+
codegenLanguage: CodegenLanguage = CodegenLanguage.defaultItem,
31+
cgLanguageAssistant: CgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(codegenLanguage),
32+
parameterizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem,
33+
runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.defaultItem,
34+
hangingTestsTimeout: HangingTestsTimeout = HangingTestsTimeout(),
35+
enableTestsTimeout: Boolean = true,
36+
testClassPackageName: String = classUnderTest.packageName,
37+
): AbstractCodeGenerator(
38+
classUnderTest,
39+
projectType,
40+
paramNames,
41+
generateUtilClassFile,
42+
testFramework,
43+
mockFramework,
44+
staticsMocking,
45+
forceStaticMocking,
46+
generateWarningsForStaticMocking,
47+
codegenLanguage,
48+
cgLanguageAssistant,
49+
parameterizedTestSource,
50+
runtimeExceptionTestsBehaviour,
51+
hangingTestsTimeout,
52+
enableTestsTimeout,
53+
testClassPackageName,
54+
) {
55+
56+
override fun generate(testSets: List<CgMethodTestSet>): CodeGeneratorResult {
57+
val testClassModel = SimpleTestClassModelBuilder(context).createTestClassModel(classUnderTest, testSets)
58+
59+
logger.info { "Code generation phase started at ${now()}" }
60+
val astConstructor = CgSimpleTestClassConstructor(context)
61+
val testClassFile = astConstructor.construct(testClassModel)
62+
logger.info { "Code generation phase finished at ${now()}" }
63+
64+
val generatedCode = renderToString(testClassFile)
65+
66+
return CodeGeneratorResult(
67+
generatedCode = generatedCode,
68+
utilClassKind = UtilClassKind.fromCgContextOrNull(context),
69+
testsGenerationReport = astConstructor.testsGenerationReport,
70+
)
71+
}
72+
}
73+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.utbot.framework.codegen.generator
2+
3+
import org.utbot.framework.codegen.reports.TestsGenerationReport
4+
import org.utbot.framework.codegen.tree.ututils.UtilClassKind
5+
6+
/**
7+
* @property generatedCode the source code of the test class
8+
* @property testsGenerationReport some info about test generation process
9+
* @property utilClassKind the kind of util class if it is required, otherwise - null
10+
*/
11+
data class CodeGeneratorResult(
12+
val generatedCode: String,
13+
val testsGenerationReport: TestsGenerationReport,
14+
// null if no util class needed, e.g. when we are generating utils directly into test class
15+
val utilClassKind: UtilClassKind? = null,
16+
)

0 commit comments

Comments
 (0)