I must emphasize on PyCharm Community Edition which does not have any Django integration (v2016.3.2 at question time).
I've Googled my problem and (surprisingly,) I did not get any answers, (of course I don't exclude the possibility that there might be some, be but I just missed them).
The question is simple: in PyCharm, one can Run (Debug) an Unit Test (TestCase or one of its methods) with a simple mouse right click (from the context menu) just as in the image below:
Unfortunately, that yields an exception:
Traceback (most recent call last): File "C:\Install\PyCharm Community Edition\2016.3.2\helpers\pycharm\utrunner.py", line 254, in <module> main() File "C:\Install\PyCharm Community Edition\2016.3.2\helpers\pycharm\utrunner.py", line 232, in main module = loadSource(a[0]) File "C:\Install\PyCharm Community Edition\2016.3.2\helpers\pycharm\utrunner.py", line 65, in loadSource module = imp.load_source(moduleName, fileName) File "E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py", line 7, in <module> from polls.models import Question File "E:\Work\Dev\Django\Tutorials\proj0\src\polls\models.py", line 9, in <module> class Question(models.Model): File "E:\Work\Dev\Django\Tutorials\proj0\src\polls\models.py", line 10, in Question question_text = models.CharField(max_length=200) File "E:\Work\Dev\VEnvs\py2713x64-django\lib\site-packages\django\db\models\fields\__init__.py", line 1043, in __init__ super(CharField, self).__init__(*args, **kwargs) File "E:\Work\Dev\VEnvs\py2713x64-django\lib\site-packages\django\db\models\fields\__init__.py", line 166, in __init__ self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE File "E:\Work\Dev\VEnvs\py2713x64-django\lib\site-packages\django\conf\__init__.py", line 53, in __getattr__ self._setup(name) File "E:\Work\Dev\VEnvs\py2713x64-django\lib\site-packages\django\conf\__init__.py", line 39, in _setup % (desc, ENVIRONMENT_VARIABLE)) django.core.exceptions.ImproperlyConfigured: Requested setting DEFAULT_INDEX_TABLESPACE, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configure() before accessing settings.
Note: I only added the question to provide an answer that might be useful to someone.
Considering the above, some (or all) parts of the solution might seem cumbersome / stupid for some advanced users, so please bear with me. I will incorporate any possible comment that adds value into the solution.
Back to the question: I did my tests / research on a project that consists of Django Tutorial ([DjangoProject]: Writing your first Django app) + some parts from Django Rest Framework Tutorial ([DRF]: Quickstart). As an example, I'm going to attempt running polls/tests.py: QuestionViewTests.test_index_view_with_no_questions()
As a note, setting DJANGO_SETTINGS_MODULE as the exception instructs, triggers another one, and so on ...
Although this is not an answer to the question (it's only remotely related), I'm posting it anyway (I'm sure that many people already did it):
test QuestionViewTests.test_index_view_with_no_questions
)Of course, having to do this for every test case (and their methods) is not the way to go (it is truly annoying), so this approach is not scalable.
Just to be noted that I don't see this as a true solution, it's more like a (lame) workaround (gainarie), and it's also intrusive.
Let's start by looking what happens when we RClick on a test (I'm going to use this term in general - it might mean Test Case or method or whole test file, unless specified otherwise). For me, it is running the following command:
"E:\Work\Dev\VEnvs\py2713x64-django\Scripts\python.exe" "C:\Install\PyCharm Community Edition\2016.3.2\helpers\pycharm\utrunner.py" E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py::QuestionViewTests::test_index_view_with_no_questions true
As you can see, it's launching "C:\Install\PyCharm Community Edition\2016.3.2\helpers\pycharm\utrunner.py" (I'm going to refer to it as utrunner) with a bunch of arguments (the 1st matters to us, since it's the test specification). utrunner uses a test run framework which does not care about Django (actually there is some Django handling code, but that's not helping us).
A few words on PyCharm`s Run/Debug configurations:
With the above in mind, let's proceed:
First thing you need to do is: from the Run/Debug Configurations dialog (menu: Run -> Edit Configurations...), edit the Defaults/Python tests/Unittests settings:
Second thing and the trickier one (also involving intrusion): patching utrunner.
utrunner.patch:
--- utrunner.py.orig 2016-12-28 19:06:22.000000000 +0200
+++ utrunner.py 2017-03-23 15:20:13.643084400 +0200
@@ -113,7 +113,74 @@
except:
pass
-if __name__ == "__main__":
+
+def fileToMod(filePath, basePath):
+ if os.path.exists(filePath) and filePath.startswith(basePath):
+ modList = filePath[len(basePath):].split(os.path.sep)
+ mods = ".".join([os.path.splitext(item)[0] for item in modList if item])
+ return mods
+ else:
+ return None
+
+
+def utrunnerArgToDjangoTest(arg, basePath):
+ if arg.strip() and not arg.startswith("--"):
+ testData = arg.split("::")
+ mods = fileToMod(testData[0], basePath)
+ if mods:
+ testData[0] = mods
+ return ".".join(testData)
+ else:
+ return None
+ else:
+ return None
+
+
+def flushBuffers():
+ sys.stdout.write(os.linesep)
+ sys.stdout.flush()
+ sys.stderr.write(os.linesep)
+ sys.stderr.flush()
+
+
+def runModAsMain(argv, codeGlobals):
+ with open(argv[0]) as f:
+ codeStr = f.read()
+ sys.argv = argv
+ code = compile(codeStr, os.path.basename(argv[0]), "exec")
+ codeGlobals.update({
+ "__name__": "__main__",
+ "__file__": argv[0]
+ })
+ exec(code, codeGlobals)
+
+
+def djangoMain():
+ djangoTests = list()
+ basePath = os.getcwd()
+ for arg in sys.argv[1: -1]:
+ djangoTest = utrunnerArgToDjangoTest(arg, basePath)
+ if djangoTest:
+ djangoTests.append(djangoTest)
+ if not djangoTests:
+ debug("/ [DJANGO MODE] Invalid arguments: " + sys.argv[1: -1])
+ startupTestArgs = [item for item in os.getenv("DJANGO_STARTUP_TEST_ARGS", "").split(" ") if item]
+ startupFullName = os.path.join(basePath, os.getenv("DJANGO_STARTUP_NAME", "manage.py"))
+ if not os.path.isfile(startupFullName):
+ debug("/ [DJANGO MODE] Invalid startup file: " + startupFullName)
+ return
+ djangoStartupArgs = [startupFullName, "test"]
+ djangoStartupArgs.extend(startupTestArgs)
+ djangoStartupArgs.extend(djangoTests)
+ additionalGlobalsStr = os.getenv("DJANGO_STARTUP_ADDITIONAL_GLOBALS", "{}")
+ import ast
+ additionalGlobals = ast.literal_eval(additionalGlobalsStr)
+ flushBuffers()
+ runModAsMain(djangoStartupArgs, additionalGlobals)
+ flushBuffers()
+
+
+def main():
arg = sys.argv[-1]
if arg == "true":
import unittest
@@ -186,3 +253,10 @@
debug("/ Loaded " + str(all.countTestCases()) + " tests")
TeamcityTestRunner().run(all, **options)
+
+
+if __name__ == "__main__":
+ if os.getenv("DJANGO_TEST_MODE_GAINARIE"):
+ djangoMain()
+ else:
+ main()
The above is a diff ([man7]: DIFF(1)) (or a patch - the names can be used conjunctively - I preffer (and will use) patch): it shows the differences between utrunner.py.orig (the original file - that I saved before starting modifying, you don't need to do it) and utrunner.py (the current version containing the changes). The command that I used is diff --binary -uN utrunner.py.orig utrunner.py
(obviously, in utrunner's folder). As a personal remark, patch is the preferred form of altering 3rd party source code (to keep changes under control, and separate).
What the code in the patch does (it's probably harder to follow than plain Python code):
if __name__ == "__main__":
or the current behavior) has been moved into a function called main (to keep it separate and avoid altering it by mistake)fileToMod("E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py", "E:\Work\Dev\Django\Tutorials\proj0\src")
, will return polls.tests
E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py::QuestionViewTests::test_index_view_with_no_questions
) to manage.py format (polls.tests.QuestionViewTests.test_index_view_with_no_questions
)if __name__ == "__main__":
. This function "tricks" Python making it believe that manage.py was run as its 1st argumentPatching utrunner:
patch -i /tmp/utrunner.patch
. [man7]: PATCH(1) is an utility that is installed by default (part of patch dpkg in Ubtu). Note that since utrunner.py is owned by root, for this step you would need sudo
patch -Ri /tmp/utrunner.patch
, and it will switch it back to its original content (it will also create an utrunner.py.orig file with the modified content; it will actually switch the .py and .py.orig files). Couple of words about this approach:
The code can handle (optional) env vars (other than DJANGO_TEST_MODE_GAINARIE - which is mandatory):
manage.py test
accepts (run manage.py test --help
to get the whole list). Here, I have to insist on -k / --keepdb which preserves the test database (test_${REGULAR_DB_NAME} by default or set in settings under the TEST dictionary) between runs. When running a single test, creating the DB (and applying all the migrations) and destroying it can be be time consuming (and very annoying as well). This flag ensures that the DB is not deleted at the end and will be reused at the next test runglobals()
dictionary, should be placed hereWhen modifying a Default configuration, all previously created configurations that inherit it, won't be updated, so they have to be manually removed (and will be automatically recreated by new RClicks on their tests)
RClick on the same test (after deleting its previous configuration :d), and voilà:
E:\Work\Dev\VEnvs\py2713x64-django\Scripts\python.exe "C:\Install\PyCharm Community Edition\2016.3.2\helpers\pycharm\utrunner.py" E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py::QuestionViewTests::test_index_view_with_no_questions true Testing started at 01:38 ... Using existing test database for alias 'default'... . ---------------------------------------------------------------------- Ran 1 test in 0.390s OK Preserving test database for alias 'default'... Process finished with exit code 0
Debugging also works (breakpoints, and so on ...).
Caveats (so far I identified 2 of them):
input
(raw_input
) call is not handled very well; the prompt text: "Type 'yes' if you would like to try deleting the test database 'test_tut-proj0', or 'no' to cancel:" (which appears if the previous test run crashed, and its DB was not destroyed at the end) is not being displayed and the program freezes (this doesn't happen outside utrunner), without letting the user to input text (maybe there are threads in the mix?). The only way to recover is stopping the test run, deleting the DB and running the test again. Again, I have to promote the manage.py test -k
flag which will get around this problemI've worked/tested on the following environments:
Notes:
As I stated at the beginning, any suggestion is more than welcome!
@EDIT0:
See https://github.com/AndreyMZ/jb_django_test_runner/blob/master/README.md.
Pros:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With