From b4e6e537b611ba7ddaac1eb99352c8854aa72d92 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Thu, 31 Oct 2019 04:15:13 +0300 Subject: [PATCH] PY-38775: Generate tests even for empty classes. When user wants to generate test for class ``SpamEggs`` that has no methods: * For pytest generate "test_spam_eggs" function * for UniTest "class TestSpamEggs" ``PyTestCreationModel`` also rewritten a little to make it slightly more readable. GitOrigin-RevId: cc5d774ad684844f5fd9c86634dbcd0d2343437f --- .../testIntegration/PyTestCreationModel.kt | 57 +++++++---- python/testData/create_tests/create_tst.py | 3 + .../PyTestCreationModelTest.kt | 97 +++++++++++-------- 3 files changed, 97 insertions(+), 60 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/testIntegration/PyTestCreationModel.kt b/python/src/com/jetbrains/python/codeInsight/testIntegration/PyTestCreationModel.kt index 7ba559fde3a1..028c7deb61b4 100644 --- a/python/src/com/jetbrains/python/codeInsight/testIntegration/PyTestCreationModel.kt +++ b/python/src/com/jetbrains/python/codeInsight/testIntegration/PyTestCreationModel.kt @@ -22,37 +22,56 @@ class PyTestCreationModel(var fileName: String, var methods: List) { init { - assert(methods.isNotEmpty()) { "Provide at least one method" } + assert(className.isNotEmpty() || methods.isNotEmpty()) { "Either class or at least one method must be provided" } } companion object { + private val String.asFunName + get():String { + return replace(Regex("([a-z])([A-Z])"), "$1_$2").toLowerCase() + } + /** * @return model of null if no test could be created for this element */ fun createByElement(element: PsiElement): PyTestCreationModel? { if (PythonUnitTestUtil.isTestElement(element, null)) return null //Can't create tests for tests - val file = element.containingFile as? PyFile ?: return null - val pyClass = PsiTreeUtil.getParentOfType(element, PyClass::class.java, false) - val function = PsiTreeUtil.getParentOfType(element, PyFunction::class.java, false) - val elementsToTest: Sequence = when { - function != null -> listOf(function) - pyClass != null -> pyClass.methods.asList() - else -> (file.topLevelFunctions + file.topLevelClasses) as List - }.asSequence().filterNot { PythonUnitTestUtil.isTestElement(it, null) } + val fileUnderTest = element.containingFile as? PyFile ?: return null + val classUnderTest = PsiTreeUtil.getParentOfType(element, PyClass::class.java, false) + val functionUnderTest = PsiTreeUtil.getParentOfType(element, PyFunction::class.java, false) + val elementsUnderTest: Sequence = when { + functionUnderTest != null -> listOf(functionUnderTest) + classUnderTest != null -> classUnderTest.methods.asList() + else -> (fileUnderTest.topLevelFunctions + fileUnderTest.topLevelClasses) + }.asSequence().filterIsInstance().filterNot { PythonUnitTestUtil.isTestElement(it, null) } - val functionNames = elementsToTest - .filterNot { it.name?.startsWith("__") == true } + + /** + * [PyTestCreationModel] has optional field "class" and list of methods. + * For unitTest we need "class" field to filled by test name. + * For pytest we may leave it empty, but we need at least one method. + */ + + val testFunctionNames = elementsUnderTest .mapNotNull { it.name } - .map { "test_${it.toLowerCase()}" }.toList() + .filterNot { it.startsWith("__") } + .map { "test_${it.asFunName}" }.toMutableList() - return if (functionNames.isEmpty()) null - else { - val className = if (PythonUnitTestUtil.isTestCaseClassRequired(file)) "Test${pyClass?.name ?: ""}" else "" - PyTestCreationModel(fileName = "test_${file.name}", - targetDir = getTestFolder(element).path, - className = className, - methods = functionNames) + val nameOfClassUnderTest = classUnderTest?.name + // True for unitTest + val testCaseClassRequired = PythonUnitTestUtil.isTestCaseClassRequired(fileUnderTest) + if (testFunctionNames.isEmpty()) { + when { + // No class, no function, what to generate? + classUnderTest == null -> return null + // For UnitTest we can generate test class. For pytest we need at least one function + !testCaseClassRequired -> testFunctionNames.add("test_" + (nameOfClassUnderTest?.asFunName ?: "fun")) + } } + return PyTestCreationModel(fileName = "test_${fileUnderTest.name}", + targetDir = getTestFolder(element).path, + className = if (testCaseClassRequired) "Test${nameOfClassUnderTest ?: ""}" else "", + methods = testFunctionNames) } diff --git a/python/testData/create_tests/create_tst.py b/python/testData/create_tests/create_tst.py index 09b05f02b12d..0f2e385251e5 100644 --- a/python/testData/create_tests/create_tst.py +++ b/python/testData/create_tests/create_tst.py @@ -9,3 +9,6 @@ class Spam: def test_foo(): pass + +class SpamSpamSpamBakedBeans: + pass diff --git a/python/testSrc/com/jetbrains/python/codeInsight/testIntegration/PyTestCreationModelTest.kt b/python/testSrc/com/jetbrains/python/codeInsight/testIntegration/PyTestCreationModelTest.kt index 08942ca35763..015e193f0677 100644 --- a/python/testSrc/com/jetbrains/python/codeInsight/testIntegration/PyTestCreationModelTest.kt +++ b/python/testSrc/com/jetbrains/python/codeInsight/testIntegration/PyTestCreationModelTest.kt @@ -3,15 +3,14 @@ package com.jetbrains.python.codeInsight.testIntegration import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.WriteAction -import com.intellij.openapi.vfs.VfsUtil import com.intellij.psi.PsiElement +import com.intellij.testFramework.VfsTestUtil import com.jetbrains.python.PyNames import com.jetbrains.python.fixtures.PyTestCase import com.jetbrains.python.psi.PyFile import com.jetbrains.python.testing.PyTestFrameworkService import com.jetbrains.python.testing.PythonTestConfigurationsModel import com.jetbrains.python.testing.TestRunnerService -import org.junit.Assert class PyTestCreationModelTest : PyTestCase() { private val dir get() = myFixture.file.containingDirectory.virtualFile @@ -19,59 +18,75 @@ class PyTestCreationModelTest : PyTestCase() { private val service: TestRunnerService get() = TestRunnerService.getInstance(myFixture.module) private val testsFolderName = "tests" - fun testWithUnitTest() { - service.projectConfiguration = PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME - val modelToTestClass = getModel(true)!! - Assert.assertEquals("test_create_tst.py", modelToTestClass.fileName) - Assert.assertEquals("TestSpam", modelToTestClass.className) - Assert.assertEquals(dirPath, modelToTestClass.targetDir) - Assert.assertEquals(modelToTestClass.methods, listOf("test_eggs", "test_eggs_and_ham")) - - val modelToTestFunction = getModel(false)!! - Assert.assertEquals("test_create_tst.py", modelToTestFunction.fileName) - Assert.assertEquals("Test", modelToTestFunction.className) - Assert.assertEquals(dirPath, modelToTestClass.targetDir) - Assert.assertEquals(modelToTestFunction.methods, listOf("test_test_foo")) - } - - fun testWithPyTest() { - service.projectConfiguration = PyTestFrameworkService.getSdkReadableNameByFramework(PyNames.PY_TEST) - val modelToTestClass = getModel(true)!! - Assert.assertEquals("test_create_tst.py", modelToTestClass.fileName) - Assert.assertEquals("", modelToTestClass.className) - Assert.assertEquals(dirPath, modelToTestClass.targetDir) - Assert.assertEquals(modelToTestClass.methods, listOf("test_eggs", "test_eggs_and_ham")) - - Assert.assertNull("test_foo is test from pytest point of view, can't test it", getModel(false)) - } - - fun testTestFolderDetected() { - ApplicationManager.getApplication().invokeAndWait { - WriteAction.runAndWait { - VfsUtil.createDirectoryIfMissing(dir, testsFolderName) - } - } - val modelToTestClass = getModel(true)!! - Assert.assertEquals(dir.findChild(testsFolderName)!!.path, modelToTestClass.targetDir) - } - override fun setUp() { super.setUp() myFixture.configureByFile("/create_tests/create_tst.py") } + fun testWithUnitTest() { + service.projectConfiguration = PythonTestConfigurationsModel.PYTHONS_UNITTEST_NAME + val modelToTestClass = getModel()!! + assertEquals("test_create_tst.py", modelToTestClass.fileName) + assertEquals("TestSpam", modelToTestClass.className) + assertEquals(dirPath, modelToTestClass.targetDir) + assertEquals(modelToTestClass.methods, listOf("test_eggs", "test_eggs_and_ham")) + + val modelToTestFunction = getModelForFunc()!! + assertEquals("test_create_tst.py", modelToTestFunction.fileName) + assertEquals("Test", modelToTestFunction.className) + assertEquals(dirPath, modelToTestClass.targetDir) + assertEquals(modelToTestFunction.methods, listOf("test_test_foo")) + + val modelToTestEmptyClass = getModel("SpamSpamSpamBakedBeans")!! + assertEquals("test_create_tst.py", modelToTestEmptyClass.fileName) + assertEquals("TestSpamSpamSpamBakedBeans", modelToTestEmptyClass.className) + assertEquals(dirPath, modelToTestEmptyClass.targetDir) + assertEquals(modelToTestEmptyClass.methods, emptyList()) + } + + fun testWithPyTest() { + service.projectConfiguration = PyTestFrameworkService.getSdkReadableNameByFramework(PyNames.PY_TEST) + val modelToTestClass = getModel()!! + assertEquals("test_create_tst.py", modelToTestClass.fileName) + assertEquals("", modelToTestClass.className) + assertEquals(dirPath, modelToTestClass.targetDir) + assertEquals(modelToTestClass.methods, listOf("test_eggs", "test_eggs_and_ham")) + + assertNull("test_foo is test from pytest point of view, can't test it", getModelForFunc()) + + val modelToTestEmptyClass = getModel("SpamSpamSpamBakedBeans")!! + assertEquals("test_create_tst.py", modelToTestEmptyClass.fileName) + assertEquals("", modelToTestEmptyClass.className) + assertEquals(dirPath, modelToTestEmptyClass.targetDir) + assertEquals(modelToTestEmptyClass.methods, listOf("test_spam_spam_spam_baked_beans")) + } + + fun testTestFolderDetected() { + ApplicationManager.getApplication().invokeAndWait { + WriteAction.runAndWait { + VfsTestUtil.createDir(dir, testsFolderName) + } + } + val modelToTestClass = getModel()!! + assertEquals(dir.findChild(testsFolderName)!!.path, modelToTestClass.targetDir) + } + override fun tearDown() { ApplicationManager.getApplication().invokeAndWait { WriteAction.runAndWait { - dir.findChild(testsFolderName)?.delete(this) + dir.findChild(testsFolderName)?.let { VfsTestUtil.deleteFile(it) } } } super.tearDown() } - private fun getModel(forClass: Boolean): PyTestCreationModel? { + private fun getModelForFunc() = getModel(null) + private fun getModel(forClass: String? = "Spam"): PyTestCreationModel? { val pyFile = myFixture.file as PyFile - val element: PsiElement = if (forClass) pyFile.topLevelClasses[0] else pyFile.findTopLevelFunction("test_foo")!! + val element: PsiElement = when { + forClass != null -> pyFile.findTopLevelClass(forClass)!! + else -> pyFile.findTopLevelFunction("test_foo")!! + } return PyTestCreationModel.createByElement(element) } }