从PyCharm Community Edition中鼠标右键单击上下文菜单运行/调试Django应用程序的UnitTests?

问题描述 投票:3回答:1

我必须强调没有任何Django集成的PyCharm Community Edition(在提问时v2016.3.2)。

我用谷歌搜索了我的问题(令人惊讶的是)我没有得到任何答案,(当然我不排除可能存在某些问题的可能性,但我只是错过了它们)。

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

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

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.

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

Run Django Unit Test at RClick

python django unit-testing pycharm
1个回答
7
投票

1.背景信息

  • 我只与Django合作约3个月
  • 关于PyCharm,我使用它已经有好几年了,但只是作为一个IDE(比如PyCharm for dummies),所以我没有进入它的高级东西

考虑到上述情况,对于某些高级用户来说,解决方案的某些(或所有)部分可能看起来很麻烦/愚蠢,所以请耐心等待。我将纳入任何可能为解决方案增加价值的评论。

回到问题:我对一个由Django Tutorial([DjangoProject]: Writing your first Django app)+ Django Rest Framework Tutorial([DRF]: Quickstart)中的一些部分组成的项目进行了测试/研究。举个例子,我将尝试运行polls / tests.py:QuestionViewTests.test_index_view_with_no_questions()

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

2.创建Python配置

虽然这不是问题的答案(它只是远程相关),但无论如何我都会发布它(我确信很多人已经这样做了):

  • 单击菜单Run - > Edit Configurations ...
  • 在“运行/调试配置”对话框中: 添加具有以下类型的新配置:Python 将Working目录设置为项目的根路径(对我来说,它是“E:\ Work \ Dev \ Django \ Tutorials \ proj0 \ src”)。默认情况下,这也将在Python的模块搜索路径中添加路径 将脚本设置为Django项目启动脚本(manage.py) 将脚本参数设置为测试参数(test QuestionViewTests.test_index_view_with_no_questions) 为您的配置命名(可选),然后单击“确定”。现在,您将能够运行此测试

当然,必须为每个测试用例(及其方法)执行此操作不是要走的路(这真的很烦人),所以这种方法不可扩展。

3.调整PyCharm以做我们想要的

需要注意的是,我不认为这是一个真正的解决方案,它更像是一个(跛脚)解决方法(获得),而且它也是侵入性的。

让我们首先看看当我们对测试进行RClick时会发生什么(我将使用这个术语 - 它可能意味着测试用例或方法或整个测试文件,除非另有说明)。对我来说,它运行以下命令:

"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

正如你所看到的,它正在推出“C:\ Install \ PyCharm Community Edition \ 2016.3.2 \ helpers \ pycharm \ utrunner.py”(我将把它称为utrunner)带有一堆参数(第一个问题)对我们来说,因为它是测试规范)。 utrunner使用一个不关心Django的测试运行框架(实际上有一些Django处理代码,但这对我们没有帮助)。

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

  • 当RClicking测试时,PyCharm会自动创建一个新的Run配置(您可以保存),就像在Run / Debug Configurations对话框中一样。需要注意的一件重要事情是配置类型,即Python测试/ Unittests(自动触发utrunner)
  • 通常,在创建运行配置时,PyCharm会将设置从“配置类型默认值”(可以在“运行/调试配置”对话框中查看)“复制”到新配置中,并使用特定数据填充其他配置。关于默认配置的一个重要的事情是它们是基于项目的:它们位于项目的.idea文件夹(workspace.xml)中,因此修改它们不会影响其他项目(我最初担心)

考虑到上述情况,让我们继续:

您需要做的第一件事是:从Run / Debug Configurations对话框(菜单:Run - > Edit Configurations ...),编辑Defaults / Python tests / Unittests设置:

  • 像上一种方法一样设置工作目录
  • 在Environment变量中添加一个名为DJANGO_TEST_MODE_GAINARIE的新变量,并将其设置为任何字符串(除空/ null)

第二件事和棘手的事情(也涉及入侵):修补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()

上面是一个diff([man7]: DIFF(1))(或一个补丁 - 名字可以合用使用 - 我喜欢(并将使用)补丁):它显示utrunner.py.orig之间的差异(原始文件 - 我在开始之前保存的修改,你不需要这样做)和utrunner.py(包含更改的当前版本)。我使用的命令是diff --binary -uN utrunner.py.orig utrunner.py(显然,在utrunner的文件夹中)。作为个人评论,补丁是改变第三方源代码的首选形式(以保持变化的控制和分离)。

补丁中的代码是什么(它可能比普通的Python代码更难遵循):

  • 主要块(if __name__ == "__main__":或当前行为)下的所有内容都已移动到一个名为main的函数中(以保持它独立并避免错误地更改它)
  • 主块被修改,因此如果定义了env var 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 char并刷新stdout和stderr缓冲区(这是必需的,因为我注意到有时来自PyCharm和Django的输出是交错的,最后的结果搞砸了) runModAsMain:通常,所有相关的manage.py代码都在if __name__ == "__main__":下。这个函数“欺骗”Python使它相信manage.py作为第一个参数运行

修补utrunner:

  • 我自己做了这些修改(我没有搜索具有Django集成的版本并从那里启发)
  • utrunner是PyCharm的一部分。很明显为什么JetBrains的家伙没有在社区版中包含任何Django集成:让人们购买专业版。这有点踩到他们的脚趾。我不知道修改utrunner的法律含义,但无论如何,如果你修补它,你自己负责并承担风险
  • 编码风格:它很糟糕(至少从命名/缩进PoV),但它与文件的其余部分一致(唯一的情况是应该允许编码风格)。 [Python]: PEP 8 -- Style Guide for Python Code包含Python的编码风格指南
  • 该补丁应用于原始文件(utrunner.py),具有以下属性(仍然对v2019.1(上次检查:20190716)有效): 大小:5865 sha256sum:db98d1043125ce2af9a9c49a1f933969678470bd863f791c2460fe090c2948a0
  • 应用补丁: utrunner位于“$ {PYCHARM_INSTALL_DIR} / helpers / pycharm” 通常,$ {PYCHARM_INSTALL_DIR}指向: Nix:/ usr / lib / pycharm-community 赢:“C:\ Program Files(x86)\ JetBrains \ PyCharm 2016.3”(适应您的版本号) 保存补丁内容(在一个名为utrunner.patch的文件中,让我们假设它在/ tmp下) 尼克斯 - 事情很简单,只是(cd到utrunner的文件夹)运行patch -i /tmp/utrunner.patch[man7]: PATCH(1)是默认安装的实用程序(Ubtu中补丁dpkg的一部分)。请注意,由于utrunner.py由root拥有,因此对于此步骤,您需要sudo Win - 要遵循的类似步骤,但事情比较棘手,因为没有本机补丁实用程序。但是,有一些解决方法: 使用Cygwin。与Nix(Lnx)一样,补丁实用程序可用,但默认情况下不会安装。必须从Cygwin安装程序中显式安装补丁程序pkg。我试过这个并且它有效 还有其他选择(我没试过): [SourceForge.GnuWin32]: Patch for Windows 理论上,[RedBean]: svn patch(任何客户端)应该能够应用补丁,但我不确定该文件是否应该是工作副本的一部分。 手动应用补丁(不太理想的选项:)) 与Nix的情况一样,修补文件(很可能)必须由其中一个管理员完成。另外,请注意文件路径,如果它们包含空格,请确保(dbl)引用它们 恢复补丁: 备份没有害处(除了可用磁盘空间的PoV,或者当它们开始堆积时,管理它们会变得很麻烦)。在我们的案例中没有必要。为了恢复更改,只需在修改后的文件:patch -Ri /tmp/utrunner.patch上运行命令,它就会将其切换回原始内容(它还会创建一个带有修改内容的utrunner.py.orig文件;它实际上会切换.py和.py.orig文件)。

关于这种方法的几个词:

  • 代码可以处理(可选)env变量(除了DJANGO_TEST_MODE_GAINARIE - 这是必需的): DJANGO_STARTUP_NAME:如果manage.py有其他名称(无论出于何种原因?),或者位于工作目录以外的其他文件夹中。这里一个重要的事情是:在指定文件路径时,使用特定于平台的路径分隔符:Nix的斜杠(/),Win的bkslash(\) DJANGO_STARTUP_TEST_ARGS:manage.py test接受的其他参数(运行manage.py test --help以获取整个列表)。在这里,我必须坚持-k / --keepdb,它在运行之间保留测试数据库(默认为test _ $ {REGULAR_DB_NAME}或在TEST字典下的设置中设置)。运行单个测试时,创建数据库(并应用所有迁移)并销毁它可能非常耗时(并且非常烦人)。此标志确保最后不删除数据库,并在下次测试运行时重复使用 DJANGO_STARTUP_ADDITIONAL_GLOBALS:这必须具有Python dict的字符串表示形式。由于某种原因,manage.py要求出现在globals()字典中的任何值都应该放在这里
  • 修改默认配置时,所有先前创建的继承配置都不会更新,因此必须手动删除它们(并且将在其测试中通过新的RClicks自动重新创建)

RC单击相同的测试(删除其先前的配置后:d),并且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

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

警告(到目前为止,我确定了其中两个):

  • 这是一个良性的,它只是一个UI问题:utrunner(最有可能)有一些PyCharm预期发生的初始化,这显然不是我们的情况。因此,即使测试成功结束,从PyCharm的PoV他们也没有,因此输出窗口将包含警告:“测试框架意外退出”
  • 这是一个令人讨厌的问题,但我还没有达到它的底部。显然,在utrunner中,任何inputraw_input)的呼叫处理都不是很好;提示文本:“如果你想尝试删除测试数据库'test_tut-proj0',则输入'yes',或者取消'no'取消:”(如果先前的测试运行崩溃,并且其数据库未在没有显示并且程序冻结(这不会发生在utrunner之外),而不让用户输入文本(可能混合中有线程?)。恢复的唯一方法是停止测试运​​行,删除数据库并再次运行测试。再次,我必须推广manage.py test -k标志,这将解决这个问题

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

  • 尼克斯(Lnx): Ubtu 16.04 x64 PyCharm社区版2016.3.3 Python 3.4.4(VEnv) Django 1.9.5
  • 赢得: W10 x64 PyCharm社区版2016.3.2 Python 2.7.13(VEnv) Django 1.10.6

笔记:

  • 我将继续调查当前的问题(至少是第二个问题)
  • 一个干净的解决方案是在PyCharm中以某种方式覆盖单元测试运行默认设置(我从代码中做了什么),但我找不到任何配置文件(可能它在PyCharm罐子里?)
  • 我注意到在帮助器(utrunner的父级)文件夹中有很多特定于Django的文件/文件夹,也许那些也可以使用,必须检查

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

@ EDIT0:

  • 当我回复@ Udi的评论时,对于那些负担不起(或不愿意的公司)支付PyCharm专业版许可证费用的人来说,这是另一种选择(快速浏览它看起来像是~100 $ -200每个实例的$ /年)
© www.soinside.com 2019 - 2024. All rights reserved.