从PyCharm Community Edition中的鼠标右键上下文菜单运行/调试Django应用程序的UnitTests? [英] Run/Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition?

查看:441
本文介绍了从PyCharm Community Edition中的鼠标右键上下文菜单运行/调试Django应用程序的UnitTests?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我必须强调

问题很简单,在PyCharm 中,可以运行(调试)单元测试( TestCase 或其中的一种方法),只需点击鼠标右键(从上下文菜单中),如下图所示:





不幸的是,这会产生一个例外:


 追溯(最近的最后一次呼叫):
文件C:\Install\PyCharm Community Edition \2016.3。 2\helpers\pycharm\utr unner.py,第254行,在< module>
main()
文件C:\Install\PyCharm Community Edition \2016.3.2\helpers\pycharm\utrunner.py,第232行,主要
module = loadSource(a [0])
文件C:\Install\PyCharm Community Edition \2016.3.2\helpers\pycharm\utrunner.py,第65行,loadSource
module = imp.load_source(moduleName,fileName)
文件E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py,第7行,< module>
来自polls.models import问题
文件E:\Work\Dev\Django\Tutorials\proj0\src\polls\models.py,第9行, <模块>
class问题(models.Model):
文件E:\Work\Dev\Django\Tutorials\proj0\src\polls\models.py,第10行,在问题
question_text = models.CharField(max_length = 200)
文件E:\Work\Dev\VEnvs\py2713x64-django\lib\site-packages\django \db\models\fields\__init __。py,第1043行,__init__
super(CharField,self).__ init __(* args,** kwargs)
文件E:\\ \\ Work \Dev\VEnvs\py2713x64-django\lib\site-packages\django\db\models\fields\__init __。py,第166行,__init__
自我.db_tablespace = db_tablespace或settings.DEFAULT_INDEX_TABLESPACE
文件E:\Work\Dev\VEnvs\py2713x64-django\lib\site-packages\django\conf\__init __。py ,第53行,__getattr__
self._setup(name)
文件E:\Work\Dev\VEnvs\p y2713x64-django\lib\site-packages\django\conf\__init __。py,第39行,在_setup
%(desc,ENVIRONMENT_VARIABLE))
django.core.exceptions。未正确配置:请求的设置DEFAULT_INDEX_TABLESPACE,但设置未配置。在访问设置之前,您必须定义环境变量DJANGO_SETTINGS_MODULE或调用settings.configure()。


注意:我只添加问题是提供可能对某人有用的答案。

解决方案

背景信息




  • 我只在〜3个月内与Django 一起工作。

  • 关于> PyCharm ,我曾经工作了好几年,但只是作为一个IDE(像Pycharm for modmies那样),所以我没有进入高级的东西。



考虑到上述情况,某些(或全部)部分解决方案对于某些高级用户来说似乎很麻烦/愚蠢,因此请与我一起承担。我将纳入任何可能的评论,为解决方案增加价值。



回到这个问题:我做了一个项目的测试/研究,由 [Django]:教程(+某些部分来自 [Django]:Rest Framework教程)。例如,我将尝试运行 polls / tests.py QuestionViewTests.test_index_view_with_no_questions()



作为注释,设置 DJANGO_SETTINGS_MODULE 作为异常指示,触发另一个,依此类推...



1 - 创建Python配置



尽管这不是问题的答案(这只是远程相关),我还是发布它我确信很多人都已经这样做了):




  • 点击菜单运行 - >编辑配置...

  • 运行/调试配置对话框中:


    • 添加具有以下类型的新配置: Python

    • 工作目录设置为项目的根路径(对我来说, EM> E:\Work\Dev\Django\Tutorials\proj0\src )。默认情况下,这也将添加 Python 的模块搜索路径中的路径。

    • 脚本设置为您的 > Django 项目启动脚本( manage.py )。

    • 脚本参数设置为测试参数(测试QuestionViewTests.test_index_view_with_no_questions

    • 给您的配置一个名称(可选),然后点击确定。现在,您将可以运行此测试。




当然,对于每个测试用例(和他们的方法)来说,这样做是不行的(这真的很讨厌),所以这种方法是不可扩展的。



2 - 调整PyCharm做我们想要的事情



只是要注意,我不认为这是一个真正的解决方案,它更像是一个(跛脚)的解决方法(我们首先来看看当我们在测试上点击时会发生什么事情 (我将使用这个术语 - 这可能意味着测试用例或方法或整个测试文件,除非另有说明)。对我来说,它正在运行以下命令:


E:\Work\Dev\VEnvs\\ \\py2713x64-django\Scripts\python.exeC:\Install\PyCharm Community Edition \2016.3.2\helpers\pycharm\utrunner.pyE:\Work\Dev\ Django\Tutorials\proj0\src\polls\tests.py :: QuestionViewTests :: test_index_view_with_no_questions true


正如你所看到的,它正在启动C:\Install\PyCharm Community Edition \2016.3.2\helpers\pycharm\utrunner.py(我'我们将把一些参数(1 st 称为 utrunner )引用到我们,因为它是测试规范)。 utrunner 使用一个不关心Django的测试运行框架(实际上还有一些处理代码的Django ,但这并没有帮助我们) / p>

PyCharm上的几个字`s 运行/调试配置




  • 当您在测试上点击时,PyCharm 自动创建一个新的运行配置(您将能够保存),就像您从运行/调试配置对话框。要注意的一个重要的事情是配置类型是 Python测试/单元测试(它会自动触发 utrunner )。

  • 创建一个 一般来说,运行配置,PyCharm 从配置类型默认值复制设置(可以在运行/调试配置对话框),进入新配置,并填写其他具体数据。 默认配置的一个重要事情是它们是基于基于项目的:它们位于 .idea 文件夹( workspace.xml ),所以修改它们不会影响其他项目(就像我第一次担心的那样)。



让我们继续下去:



您需要做的第一件事是:从运行/调试配置对话框(菜单:运行 - >编辑配置... ),编辑默认值/ Python测试/单元测试设置:




  • 像以前的方法一样设置工作目录

  • 在环境变量中添加新的一个名为 DJANGO_TEST_MODE_GAINARIE 并将其设置为任何字符串(空/空除外)。



第二件事情是一件棘手的事情(也涉及入侵):修补 utrunner



utrunner.diff

  --- 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 @@
除了:
pass

-if __name__ = =__main__:
+
+ def fileToMod(filePath,basePath):
+如果os.path.exists(filePath)和filePath.startswith(basePath):
+ modList = filePath [len(basePath):]。split(os.path.sep)
+ mods =。。join([os.path.splitext(item)[0] for item in modList if item ])
+返回mods
+ else:
+返回无
+
+
+ def utrunnerArgToDjangoTest(arg,basePath):
+ if arg.strip()而不是arg.startswith( - ):
+ testData = arg.split(::)
+ mods = fileToMod(testData [0],basePath )
+如果mods:
+ testData [0] = mods
+ return。。join(testData)
+ else:
+ return无
+ else:
+返回无
+
+
+ def flushBuffers():
+ sys.stdout.write(os.lines ep)
+ 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 sys.argv [1:-1]中的arg:
+ djangoTest = utrunnerArgToDjangoTest(arg,basePath)
+如果djangoTest:
+ djangoTests.append(djangoTest)
+如果不是djangoTests:
+ debug(/ [DJANGO MODE]无效的参数: + sys.argv [1:-1])$ ​​b $ b + startupTestArgs = [os.getenv中的项目项目(DJANGO_STARTUP_TEST_ARGS,).split()如果项目]
+ startupFullName = os.path.join(basePath,os.getenv(DJANGO_STARTUP_NAME,man ($)
+ if not os.path.isfile(startupFullName)
+ debug(/ [DJANGO MODE]启动文件无效:+ 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]
如果arg ==true:
import unittest
@@ -186,3 +253,10 @@

debug(/ Loaded+ str(all.countTestCases())+tests)
TeamcityTestRunner()。run(all,** options)
+
+
+如果__name__ ==__main__:
+如果os.getenv(DJANGO_TEST_MODE_GAINARIE) :
+ djangoMain()
+ else:
+ main()

以上是 diff :它显示了 utrunner.py.orig (原始文件 - 在开始修改之前保存的区别,你不需要这样做)和 utrunner.py (包含更改的当前版本)。我使用的命令是 diff --binary -uN utrunner.py.orig utrunner.py (显然,在 utrunner 的文件夹中)。作为个人评论, diff 是更改3 rd 方源代码的首选方式(以保持更改的控制和分离)。



diff 中的代码(可能比简单的Python 代码更难遵循):




  • 主要块( if __name__ ==__main __:或当前行为)已被移动到一个名为 main 的函数中(保持分开,避免错误地更改)

  • 主要块被修改,所以如果env var DJANGO_TEST_MODE_GAINARIE 被定义(而不是空),它将遵循新的实现( djangoMain 函数),否则将正常执行 。新实现:


    • fileToMod(filePath,basePath) subtracts basePath filePath ,并将差异转换为 Python 包样式。例如: fileToMod(E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py,E:\Work\Dev \Django\Tutorials\proj0\src),将返回 polls.tests

    • utrunnerArgToDjangoTest(arg,basePath):使用上一个函数,然后添加类名( QuestionViewTests )和(可选)方法名称( test_index_view_with_no_questions ),所以最后它将测试规范从 utrunner 格式转换( E: \Work\Dev\Django\Tutorials\proj0\src\polls\tests.py :: QuestionViewTests :: test_index_view_with_no_questions )to manage.py 格式( polls.tests.QuestionViewTests.test_index_view_with_no_questions )。

    • flushBuffers()写一个 eoln char并刷新stdout 和 stderr 缓冲区(这是必需的,因为我注意到有些使得 PyCharm 和 Django 的输出被交错,最后的结果被弄乱了)

    • runModAsMain(argv,codeGlobals):通常,如果__name_ ==__main __: manage.py 代码位于下$ C>。这个功能诡计Python 使得它相信 manage.py 是作为其1 st 参数运行的。




修补 utrunner




  • 我自己做了这些修改(我没有搜索具有Django 集成的版本,并从那里启发)。

  • utrunner 是PyCharm 的一部分。很明显,为什么JetBrains 不会在 社区版本中包含任何的Django 集成:让人们购买 专业版版本。这样他们的脚趾步骤。我不知道修改 utrunner 的法律含义,但无论如何,如果您修补它,您正在做自己的责任和风险

  • 编码风格:它吸吮(至少从命名/缩进 PoV ),但它与文件的其余部分一致(唯一的情况是编码风格应该被允许吸)

  • 该修补程序应用于原始文件( utrunner.py ),具有以下属性:


    • size: 5865

    • sha256sum: db98d1043125ce2af9a9c49a1f933969678470bd863f791c2460fe090c2948a0


  • 应用补丁:


    • utrunner 位于 $ {PYCHARM_INSTALL_DIR} / helpers / pycharm

    • 通常, $ {PYCHARM_INSTALL_DIR} 指向:


      • Ux / usr / lib / pycharm-community

      • C:\程序文件(x86)\JetBra ins\PyCharm 2016.3


    • 保存diff内容(在一个名为 utrunner.diff ,我们假定它是在 / tmp 下)。

    • Ux 只需( cd utrunner 的文件夹,然后运行 patch -i /tmp/utrunner.diff补丁是一个默认安装的实用程序(补丁 dpkg in Ubuntu )。请注意,由于 utrunner.py 拥有,因此您需要 sudo

    • Win - 要遵循的类似步骤,但由于没有本机的补丁实用程序,事情变得非常棘手。但是,有解决方法:


    • 恢复补丁:备份无害(除了可用磁盘空间的PoV 之外,或者当它们开始堆积时,管理它们变得很痛苦)。在我们的案件中没有必要。为了恢复更改,只需在修改后的文件中运行命令: patch -Ri /tmp/utrunner.diff ,它将切换回原始内容(它还将创建一个具有修改内容的 utrunner.py.orig 文件;它将实际切换 .py 和_.py.orig_files。




关于这种方法的几句话




  • 代码可以处理(可选)env vars(除了$ code> DJANGO_TEST_MODE_GAINARIE - 这是强制性的) :




    • DJANGO_STARTUP_NAME :如果 manage.py 有其他名称(无论什么原因?),或位于另一个文件夹中,而不是工作目录。一个重要的事件:在指定文件路径时,使用特定于平台的路径分隔符: / slash )for < b
    • DJANGO_STARTUP_TEST_ARGS :附加参数 manage.py test 接受(运行管理。 py test --help 获取整个列表)。在这里我必须坚持 -k / - keepdb 保留测试数据库( / code> c $ c> c c c c c dict )之间。运行单个测试时,创建 DB (并应用所有迁移)并销毁它可能会耗费时间(也非常烦人)。这个标志确保最终不会删除 DB ,并在下一次测试运行时重复使用。

    • DJANGO_STARTUP_ADDITIONAL_GLOBALS :它必须具有 Python 字典的字符串表示形式。应该将 c> c> globals()字典中的 manage.py 所要求的任何值放在这里。


  • 修改默认配置时,所有先前创建的继承它的配置不会更新,因此必须手动删除(并将在其测试上由新的 重新创建)在相同的测试中(在删除其以前的配置之后):d)中的




voilà


  E:\Work\Dev \\ \\VEnvs\py2713x64-django\Scripts\python.exeC:\Install\PyCharm Community Edition \2016.3.2\helpers\pycharm\utrunner.pyE:\Work\ Dev \Django\Tutorials\proj0\src\polls\tests.py :: QuestionViewTests :: test_index_view_with_no_questions true 
测试从01:38开始...


使用现有的测试数据库进行别名default...

---------------------------------------------- ------------------------
Ran 1 test in 0.390s

OK

保存别名default的测试数据库...


进程退出代码0


调试也有效(断点等)...



注意事项(到目前为止,我确定了其中2个):




  • 这是良性的,这只是一个UI问题: utrunner (很可能)有一些初始化,希望PyCharm 可以发生,这显然不是我们的例子。因此,即使测试成功结束,从Pyjarm 的 PoV 中也没有,所以输出窗口将包含一个警告: em>测试框架意外退出。

  • 这是一个讨厌的一个,我不能到底(还)。显然,在 utrunner 中,任何输入 raw_input )调用处理得不好;如果您想尝试删除测试数据库'test_tut-proj0',或'否'取消:(如果以前的测试运行崩溃,则显示它的 DB 未被破坏)未显示并且程序冻结(这不会发生在 utrunner 之外),而不让用户输入文本(也许有线程在混合?)。唯一的恢复方法是停止测试运​​行,删除 DB 并再次运行测试。再次,我必须推广 manage.py test -k 标志,以解决这个问题。



我在以下环境中工作/测试




  • Ux Lnx ):


    • Ubtu 16.04 x64

    • PyCharm社区版 2016.3.3

    • Python 3.4.4( VEnv

    • Django 1.9.5


  • / em>:


    • W10 x64

    • PyCharm社区版 2016.3.2

    • Python 2.7.13( VEnv

    • > Django 1.10.6




strong>:




  • 我将继续调查当前的问题(至少2个 nd 一个) li>
  • 清洁解决方案将是在PyCharm 中以某种方式覆盖单元测试运行默认setti ngs(我从代码中做了什么),但是我找不到任何配置文件(可能是在PyCharm jars中)。

  • 我注意到很多在帮助者 utrunner 的父文件夹)文件夹中特定于的Django 的文件/文件夹,也许这些文件/文件夹也可以使用,检查。



正如我刚才所说,任何建议都是欢迎!



@ EDIT0 :当我回复Udi 的评论时,这是一个替代买不起的人(或不愿意的公司) )支付PyCharm专业版许可证费用(快速浏览,每个实例看起来都是〜100 $ -200 $ /年)。


I must emphasize on PyCharm Community Edition which does not have any Django integration.

I've Google'ed 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.

解决方案

Background info:

  • I am only working with Django for ~3months.
  • Regarding PyCharm, I worked with it for some years, but only as an IDE (like Pycharm for dummies), so I didn't get into its advanced stuff.

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 (+ some parts from [Django]: Rest Framework Tutorial). 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...

1 - Creating a Python configuration

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):

  • Click on the menu Run -> Edit Configurations....
  • On the Run/Debug Configurations dialog:
    • Add a new configuration having the type: Python.
    • Set the Working directory to the root path of your project (for me it is E:\Work\Dev\Django\Tutorials\proj0\src). By default, this will also add the path in the Python's modules search paths.
    • Set the Script to your Django project startup script (manage.py).
    • Set the Script parameters to the test parameters (test QuestionViewTests.test_index_view_with_no_questions)
    • Give your configuration a name (optional) and click OK. Now, you will be able to run this test.

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.

2 - Adjusting PyCharm to do what we want

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:

  • When RClick-ing on a test, PyCharm automatically creates a new Run configuration (that you will be able to save), just like you would from the Run/Debug Configurations dialog. An important thing to note is the configuration type which is Python tests/Unittests (which automatically fires utrunner).
  • When creating a Run configuration in general, PyCharm "copies" the settings from that configuration type Defaults (can be viewed in the Run/Debug Configurations dialog), into the new configuration, and fills the others with specific data. One important thing about Default configurations is that they are project based: they reside in the .idea folder (workspace.xml) of the project, so modifying them would not impact other projects (as I first feared).

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:

  • Set the Working directory just like in the previous approach.
  • In the Environment variables add a new one named DJANGO_TEST_MODE_GAINARIE and set it to any string (other than empty/null).

Second thing and the trickier one (also involving intrusion): patching utrunner.

utrunner.diff:

--- 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: 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, diff is the preferred form of altering 3rd party source code (to keep changes under control, and separate).

What the code in the diff does (it's probably harder to follow than plain Python code):

  • Everything under the main block (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)
  • the main block was modified, so that if the env var DJANGO_TEST_MODE_GAINARIE is defined (and not empty), it will follow the new implementation (djangoMain function), otherwise it will act normally. The new implementation:
    • fileToMod(filePath, basePath) subtracts basePath from filePath and converts the difference into Python package style. Ex: fileToMod("E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py", "E:\Work\Dev\Django\Tutorials\proj0\src"), will return polls.tests.
    • utrunnerArgToDjangoTest(arg, basePath): uses the previous function and then adds the class name (QuestionViewTests) and (optionally) the method name (test_index_view_with_no_questions), so at the end it converts the test specification from utrunner format (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).
    • flushBuffers() writes an eoln char and flushes the stdout and stderr buffers (this is needed because I noticed that sometimes the outputs from PyCharm and Django are interleaved, and the final result is messed up).
    • runModAsMain(argv, codeGlobals): typically, all the relevant manage.py code is under if __name__ == "__main__":. This function "tricks" Python making it believe that manage.py was run as its 1st argument.

Patching utrunner:

  • I did these modifications on my own (I didn't search for versions having Django integration and inspire from there).
  • utrunner is part of PyCharm. It's obvious why JetBrains guys didn't include any Django integration in the Community Edition: to make people buy the Professional Edition. This kinda steps on their toes. I'm not aware of the legal implications of modifying utrunner, but anyway if you patch it, you're doing it on your own responsibility and risk.
  • Coding style: it sucks (at least from naming/indenting PoV), but it's consistent with the rest of the file (the only case when coding style should be allowed to suck).
  • The patch is applied on the original file (utrunner.py), with the following properties:
    • size: 5865
    • sha256sum: db98d1043125ce2af9a9c49a1f933969678470bd863f791c2460fe090c2948a0
  • Applying the patch:
    • utrunner is located in ${PYCHARM_INSTALL_DIR}/helpers/pycharm.
    • Typically, ${PYCHARM_INSTALL_DIR} points to:
      • Ux: /usr/lib/pycharm-community.
      • Win: C:\Program Files (x86)\JetBrains\PyCharm 2016.3.
    • Save the diff content (in a file called e.g. utrunner.diff, let's assume it's under /tmp).
    • Ux - things are easy, just (cd to utrunner's folder and) run patch -i /tmp/utrunner.diff. patch 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.
    • Win - similar steps to be followed, but things are trickier since there's no native patch utility. However, there are workarounds:
      • Use Cygwin. As in Ux(Lnx) case, patch utility is available but it doesn't get installed by default. The patch pkg must be explicitly installed from Cygwin setup. I tried this and it works.
      • There are alternatives (I didn't try them):
        • Patch for Windows.
        • In theory, svn diff(any client) should be able to apply a diff, but I'm not sure if the file should be part of a working copy.
        • Applying the diff manually (a less desired option :) ).
      • As in Ux's case, patching the file would (most likely) have to be done by one of the Administrators. Also watch out for file paths, make sure to (dbl)quote them if they contain spaces.
    • Reverting the patch: backups are not harmful (except from the free disk space's PoV, or when they start to pile up, managing them becomes a pain). There's no need for them in our case. In order to revert the changes, just run the command on the modified file: patch -Ri /tmp/utrunner.diff, 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):

    • DJANGO_STARTUP_NAME: in case that manage.py has other name (for whatever reason?), or is located in another folder than the Working directory. An important thing here: when specifying file paths, use the platform specific path separator: / (slash) for Ux, \ (bkslash) for Win.
    • DJANGO_STARTUP_TEST_ARGS: additional arguments that 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 dict) 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 run.
    • DJANGO_STARTUP_ADDITIONAL_GLOBALS: this must have the string representation of a Python dictionary. Any values that for some reason are required by manage.py to be present in the globals() dictionary, should be placed here.
  • When 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 RClick's 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):

  • This is benign, it's only an UI issue: utrunner (most likely) has some initialization that PyCharm expects to take place, which obviously doesn't in our case. So, even if the test ended successfully, from PyCharm's PoV they didn't and therefore the Output window will contain a warning: Test framework quit unexpectedly.
  • This is a nasty one, and I wasn't able to get to the bottom of it (yet). Apparently, in utrunner any 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 problem.

I've worked/tested on the following environments:

  • Ux(Lnx):
    • Ubtu 16.04 x64
    • PyCharm Community Edition 2016.3.3
    • Python 3.4.4 (VEnv)
    • Django 1.9.5
  • Win:
    • W10 x64
    • PyCharm Community Edition 2016.3.2
    • Python 2.7.13 (VEnv)
    • Django 1.10.6

Notes:

  • I will continue investigating the current issues (at least the 2nd one).
  • A clean solution would be to override somehow in PyCharm the Unit Test running default settings (what I did from code), but I couldn't find any config files (probably it's in the PyCharm jars?).
  • I noticed a lot of files/folders that are specific to Django in the helpers (utrunner's parent) folder, maybe those can be used too, will have to check.

As I stated at the beginning, any suggestion is more than welcome!

@EDIT0: As I replied to Udi's comment, this is an alternative for people who can't afford (or companies that aren't willing) to pay the PyCharm Professional Edition license fee (on a quick browse it looks like it's ~100$-200$/year for each instance).

这篇关于从PyCharm Community Edition中的鼠标右键上下文菜单运行/调试Django应用程序的UnitTests?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆