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

问题描述

我必须强调 PyCharm 社区版本,该版本没有任何 Django 集成 v 2016.3.2 )。



我已经 Google 解决了我的问题,并且(令人惊讶的是,)我没有得到任何答案((当然,我不排除可能会有一些答案,但是我只是想念他们而已)。)



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





不幸的是,这会产生异常:

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

解决方案

1。背景信息




  • 我只在 Django 上工作了大约3个月

  • 关于 PyCharm ,我使用了几年,但仅作为一个IDE(例如 PyCharm for dummies )使用,所以我没有深入研究它的高级内容



考虑到上述情况,对于某些高级用户而言,解决方案的某些(或全部)部分似乎很繁琐/愚蠢,请耐心等待。我将结合任何可能在解决方案中增加价值的评论。



回到问题:我对包含 Django Tutorial的项目进行了测试/研究()+ Django Rest Framework Tutorial ()。例如,我将尝试运行 polls / tests.py QuestionViewTests.test_index_view_with_no_questions()



请注意, DJANGO_SETTINGS_MODULE 设置为例外情况,触发另一个,依此类推...



2。创建一个 Python 配置



尽管这不是问题的答案(仅与远程相关),但无论如何我都会发布它(我'肯定已经有很多人这样做了)




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

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


    • 添加新配置类型为: Python

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

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

    • Script参数设置为测试参数( test QuestionViewTests.test_index_view_with_no_questions

    • 为您的配置命名(可选),然后单击 OK 。现在,您将可以运行此测试




当然,对于每个测试用例(及其方法)而言,这样做都不是可行的方法(这确实很烦人),因此这种方法不可扩展。



3。调整 PyCharm 以执行我们想要的



请注意,我认为这不是真正的解决方案,它更像是( me脚)解决方法( gainarie ),而且它也很麻烦。



让我们先看看 RClick test 上(我将通常使用此术语-除非另有说明,否则可能表示测试用例或方法或整个测试文件)。对我来说,它正在运行以下命令:

如您所见,它正在启动 C:\Install\PyCharm Community Edition\2016.3.2\helpers\pycharm\ \utrunner.py (我将其称为 utrunner )带有一堆参数(第一个对我们很重要,因为它测试规范)。 utrunner 使用的测试运行框架不关心 Django (实际上有一些 Django 处理代码,但这对我们没有帮助)。 / p>

关于 PyCharm 运行/调试配置的几句话:




  • test RClick -ing时, PyCharm 自动创建一个新的 Run配置(您将可以保存),就像在运行/调试配置对话框中一样。 重要要注意的是配置类型,它是 Python测试/单元测试(会自动触发 utrunner

  • 通常在创建 Run配置时, PyCharm 从该配置类型默认值中复制设置(可以在运行/调试配置 对话框),然后将新数据填充到其他配置中。关于默认配置的重要一件事是它们基于基于项目的:它们位于 .idea 文件夹( workspace.xml ),因此对其进行修改不会影响其他项目(正如我最初担心的那样)



让我们继续:



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




  • 设置工作目录,就像在以前的方法中一样

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



第二件事和棘手的事情(还涉及入侵):修补 utrunner



utrunner。补丁

  --- 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 @@
除外:
通过

-如果__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 modList中的项目,如果item])
+返回mods
+ else:
+返回None
+
+
+ def utrunnerArgToDjangoTest(arg,basePath):
+如果是arg.strip()而不是arg.startswith(-):
+ testData = arg.split( ::)
+ mods = fileToMod(testData [0], basePath)
+如果mods:
+ testData [0] = mods
+返回。。join(testData)
+否则:
+返回None
+ else:
+返回None
+
+
+ def flushBuffers():
+ sys.stdout.write(os.line sep)
+ sys.stdout.flush()
+ sys.stderr.write(os.linesep)
+ sys.stderr.flush()
+
+
+ def runModAsMain(argv,codeGlobals):
+以open(argv [0])作为f:
+ codeStr = f.read()
+ sys.argv = argv
+代码= 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()
+ sys.argv中的arg [1:-1]:
+ 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, ma nage.py))
+如果不是os.path.isfile(startupFullName):
+ debug( / [DJANGO MODE]无效的启动文件: + startupFullName)
+返回
+ djangoStartupArgs = [startupFullName,测试]
+ djangoStartupArgs.extend(startupTestArgs)
+ djangoStartupArgs.extend(djangoTests)
+ AdditionalGlobalsStr = os.getenv( DJANGO_STARTUP_AD {})
+导入ast
+ AdditionalGlobals = ast.literal_eval(additionalGlobalsStr)
+ flushBuffers()
+ runModAsMain(djangoStartupArgs,AdditionalGlobals)
+ flushBuffers ()
+
+
+ def main():
arg = sys.argv [-1]
如果arg == true:
导入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()
+否则:
+ main()

上面是 diff ()(或 patch -名称可以结合使用-我喜欢(和(将使用) patch ):它显示了 utrunner.py.orig (原始文件-我在开始修改之前保存的文件)之间的区别,您无需这样做)和 utrunner.py (包含更改的当前版本)。我使用的命令是 diff --binary -uN utrunner.py.orig utrunner.py (显然,在 utrunner 文件夹中)。作为个人评论, patch 是更改3 参与者源代码(以使更改受到控制并分开)的首选形式。



补丁中的代码的作用(可能比普通的 Python 代码更难遵循):




  • main 块下的所有内容(如果__name__ == __main __:或当前行为,则 )已移至名为 main 的函数中(以使其分开并避免错误更改)

  • main 块为修改,以便如果定义了环境变量 DJANGO_TEST_MODE_GAINARIE (并且不为空),它将遵循新的实现( djangoMain 函数),否则它将正常执行 。新的实现:


    • fileToMod filePath 中减去 basePath 并进行转换 Python 封装样式的区别。例如: fileToMod( E:\Work\Dev\Django\Tutorials\proj0\src\polls\tests.py, E:\Work\Dev \Django\Tutorials\proj0\src),将返回 polls.tests

    • utrunnerArgToDjangoTest :使用上一个函数,然后添加类名称( 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 )转换为 manage.py 格式( polls.tests.QuestionViewTests.test_index_view_with_no_questions

    • flushBuffers :编写一个 eoln 字符并刷新 stdout stderr 缓冲区(这是必需的,因为我注意到有时 PyCharm Django 的输出是交错的,最终结果是混乱的)

    • runModAsMain :通常,如果__name__ == __main __:,所有相关的 manage.py 代码都在下。此函数欺骗 Python ,使其认为 manage.py 是作为其1 参数




修补 utrunner




  • 我自己进行了这些修改(我没有搜索集成了 Django 的版本并从中得到启发)
  • b $ b
  • utrunner PyCharm 的一部分。很明显,为什么 JetBrains 伙计们没有在 Community Edition 中包括任何 Django 集成:让人们购买 Professional版本 。这有点踩他们的脚趾。我不知道修改 utrunner 的法律含义,但是无论如何,只要修补它,您就要自己承担责任和风险

  • 编码样式:很烂(至少从命名/缩进 PoV 起),但是与文件的其余部分保持一致(唯一的情况是应该允许编码样式烂掉) 。 包含了编码 Python
  • 的样式指南
  • 该修补程序应用于原始文件( utrunner.py ),具有以下属性(仍然有效)对于 v 2019.2.3 (最近检查: 20190930 )):


    • 大小: 5865

    • sha256sum: db98d1043125ce2af9a9c9a1f933969678470bd863f791c2460fe090c2948a0


  • 应用补丁


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

    • 通常, $ {PYCHARM_INSTALL_DIR} 指向:


      • Nix / usr / lib / pycharm-community

      • Win C:\Program Files(x86)\JetBrains\PyCharm 2016.3 (适应您的版本号)


    • 保存 patch 内容(保存在名为例如 utrunner.patch ,我们假设它在 / tmp 下)

    • Nix -事情很简单, ( cd utrunner 的文件夹,然后)运行 patch -i /tmp/utrunner.patch。 是一种实用工具,默认情况下已安装( Ubtu patch dpkg的一部分)。请注意,由于 utrunner.py root 拥有,因此,您需要 sudo

    • Win -遵循类似的步骤,但是由于没有本地的 patch 实用程序,因此操作起来更加棘手。但是,有一些解决方法:


      • 使用

      • 理论上,(任何客户端)都应该能够应用 patch ,但是我不确定文件是否应为工作副本的一部分>。

      • 手动应用补丁(一个不太理想的选择:))


    • Nix 的情况一样,(很可能)必须由 Administrators 之一来修补文件。另外,请注意文件路径,如果它们包含空格,请确保对它们进行(dbl)quote


  • 还原补丁


    • 备份无害(可用磁盘空间的 PoV 除外,或者当它们开始堆积时,管理它们变得很痛苦。在我们的情况下,不需要它们。为了恢复更改,只需在修改后的文件上运行命令: patch -Ri /tmp/utrunner.patch ,它将切换回其原始内容(还将创建具有已修改内容的 utrunner.py.orig 文件;它将实际切换 .py .py.orig 文件)。
      尽管如此,在修改它们之前总是先备份3个方文件(特别是如果某些工具/安装程序正在跟踪它们的情况下),以便在出现问题时修改它们时,总有一种恢复原始状态的方法


  • 虽然这里不是这种情况,但是如果更改是另一种形式,例如应用了 patch 的文件(例如,在上),您显然可以获取整个文件(如果有很多文件,将它们全部跟踪可能会很麻烦)并覆盖您的文件。但是再次,先将其备份




有关此方法的单词




  • 该代码可以处理(可选)env vars(除了 DJANGO_TEST_MODE_GAINARIE -这是必填项):




    • DJANGO_STARTUP_NAME :如果 manage.py 具有其他名称(出于某种原因?),或者位于工作目录之外的其他文件夹中。 重要的地方:指定文件路径时,请使用平台特定的路径分隔符:斜杠 / Win
    • $ b的 Nix bkslash \ ) $ b
    • DJANGO_STARTUP_TEST_ARGS manage.py test 接受的其他参数(运行 manage.py test --help 获取整个列表)。在这里,我必须坚持使用 -k / -keepdb 来保存测试数据库(两次运行之间,默认情况下为test _ $ {REGULAR_DB_NAME} 或在 TEST 词典的设置中进行设置)。当运行单个测试时,创建 DB (并应用所有迁移)并销毁它可能会很耗时(并且也很烦人)。此标志确保 DB 不会在最后删除,并且将在下一次测试运行时重新使用

    • DJANGO_STARTUP_ADDITIONAL_GLOBALS :这必须具有 Python dict 的字符串表示形式。 manage.py 出于某些原因要求将任何值包含在 globals()词典中的值,应放在此处


  • 修改默认配置时,以前创建的所有继承该配置的配置,更新,因此必须手动删除(并由新的 RClick 在其测试中自动重新创建)




RClick (在删除其先前的配置后:d),并 voilà

调试也可以(断点等)。



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




  • 这是良性的,它只是一个 UI utrunner (最有可能)进行了一些 PyCharm 预期的初始化,显然在我们的情况下没有。因此,即使测试成功结束,从 PyCharm PoV 也不会,因此 Output 窗口将包含警告: 测试框架意外退出

  • 这是一个令人讨厌的框架,但我还无法深入了解它。显然,在 utrunner 中,任何 input raw_input )调用都不能很好地处理;提示文本: 如果要尝试删除测试数据库'test_tut-proj0',请输入'yes',否则请取消:'(如果先前的测试运行崩溃,则会出现,并且其 DB 最终没有被销毁)不会显示,并且程序会冻结(这不会在 utrunner 之外发生),而无需让用户输入文本(也许混合中有线程?)。恢复的唯一方法是停止测试运​​行,删除 DB 并再次运行测试。同样,我必须推广 manage.py test -k 标志,该标志可以解决此问题



我已经在以下环境上进行了测试:




  • Nix Lnx ):


    • Ubtu 16.04 x64

    • PyCharm社区版2016.3.3

    • Python 3.4.4 VEnv

    • Django 1.9.5


  • Win


    • W10 x64

    • PyCharm社区版2016.3.2

    • Python 2.7.13 VEnv

    • Django 1.10.6




注释 >:




  • 我将继续调查当前问题(至少是第二个个问题)

  • 一个干净解决方案将以某种方式覆盖 PyCharm 单元测试运行的默认设置s(我从代码中做了什么),但是我找不到任何配置文件(可能是在 PyCharm jars中?)

  • 我注意到很多 helpers utrunner 的父文件夹)文件夹中特定于 Django 的文件/文件夹,也许也可以使用,检查



正如我在开始时所说,任何建议都非常受欢迎!



@ edit0




  • 当我回复@Udi的评论时,对于无法负担(或不愿支付公司费用) PyCharm Professional Edition 许可费用的人(在快速浏览中,它看起来像是〜100 $ -200 $ /每年(每个实例)


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:

Note: I only added the question to provide an answer that might be useful to someone.

解决方案

1. Background info

  • I am only working with Django for ~3 months
  • 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 ([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 ...

2. 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.

3. 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:

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 1 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.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 3 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):

  • 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 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: 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: 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 1 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). [Python]: PEP 8 -- Style Guide for Python Code contains the coding style guidelines for Python
  • The patch is applied on the original file (utrunner.py), with the following properties (still valid for v2019.2.3 (last checked: 20190930)):
    • size: 5865
    • sha256sum: db98d1043125ce2af9a9c49a1f933969678470bd863f791c2460fe090c2948a0
  • Applying the patch:
    • utrunner is located in "${PYCHARM_INSTALL_DIR}/helpers/pycharm"
    • Typically, ${PYCHARM_INSTALL_DIR} points to:
      • Nix: /usr/lib/pycharm-community
      • Win: "C:\Program Files (x86)\JetBrains\PyCharm 2016.3" (adapt to your version number)
    • Save the patch content (in a file called e.g. utrunner.patch, let's assume it's under /tmp)
    • Nix - things are easy, just (cd to utrunner's folder and) run 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
    • 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 Nix (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):
      • As in Nix'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.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).
        Nevertheless always back 3-party files up before modifying them (especially if they're being tracked by some tools / installers), so that if something goes wrong while modifying them, there's always a way to restore the original state
    • Although not the case here, but if the changes are in another form, like the file with the patch applied (e.g. on GitHub), you can obviously get the entire file (if there are many files, tracking all of them down could become a pain) and overwrite yours. But again, back it (them) up first!

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 Nix, 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 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 run
    • DJANGO_STARTUP_ADDITIONAL_GLOBALS: this must have the string representation of a Python dict. 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 RClicks on their tests)

RClick on the same test (after deleting its previous configuration :d), and voilà:

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:

  • Nix (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 2 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?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-24 19:35