繁体   English   中英

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

[英]Run / Debug a Django application's UnitTests from the mouse right click context menu in PyCharm Community Edition?

我必须强调PyCharm社区,它没有任何Django集成(问题时间为v 2016.3.2 )。

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

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

在 RClick 上运行 Django 单元测试

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

 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.

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

1. 背景资料

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

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

回到问题:我对一个项目进行了测试/研究,该项目由Django 教程[DjangoProject]:编写您的第一个 Django 应用程序)+ Django Rest 框架教程[DRF]:快速入门)中的一些部分组成。 例如,我将尝试运行polls/tests.pyQuestionViewTests.test_index_view_with_no_questions()

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

2. 创建Python配置

虽然这不是问题的答案(它只是远程相关),但我还是要发布它(我相信很多人已经这样做了):

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

当然,必须为每个测试用例(及其方法)执行此操作并不是可行的方法(这确实很烦人),因此这种方法不可扩展。

3. 调整PyCharm来做我们想做的事

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

让我们先看看当我们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运行/调试配置的几句话:

  • RClick测试上时PyCharm 会自动创建一个新的运行配置(您将能够保存),就像您在运行/调试配置对话框中所做的一样。 需要注意的重要一点是配置类型是Python 测试/单元测试(它会自动触发utrunner
  • 通常在创建运行配置时, PyCharm 会将该配置类型的默认设置(可以在运行/调试配置对话框中查看)“复制”到新配置中,并用特定数据填充其他配置。 关于默认配置的一个重要的事情是它们是基于项目的:它们驻留在项目.idea文件夹( workspace.xml )中,因此修改它们不会影响其他项目(正如我最初担心的那样)

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

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

  • 像以前的方法一样设置工作目录
  • 环境变量中添加一个名为DJANGO_TEST_MODE_GAINARIE的新变量并将其设置为任何字符串(空/除外)

第二件事也是更棘手的一件事(也涉及入侵):修补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代码更难理解):

  • main块下的所有内容( if __name__ == "__main__":或当前行为)都已移至名为main的函数中(以使其保持独立并避免错误地更改它)
  • 修改了块,因此如果定义了环境变量 DJANGO_TEST_MODE_GAINARIE (并且不为空),它将遵循新的实现( djangoMain函数),否则将正常运行 新的实现:
    • fileToModfilePath 中减去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 并刷新stdoutstderr缓冲区(这是必需的,因为我注意到有时来自PyCharmDjango的输出是交错的,并且最终结果是一团糟)
    • runModAsMain :通常,所有相关的manage.py代码都在if __name__ == "__main__": 这个函数“欺骗”了Python ,让它相信manage.py是作为它的第一个参数运行的

修补utrunner

  • 我自己做了这些修改(我没有搜索具有Django集成和启发的版本)
  • utrunnerPyCharm 的一部分。 很明显为什么JetBrains人没有社区版中包含任何Django集成:让人们购买专业版 这有点踩在他们的脚趾上。 我不知道修改utrunner的法律含义,但无论如何,如果您修补它,您将自行承担责任和风险
  • 编码风格:它很糟糕(至少从命名/缩进PoV来看),但它与文件的其余部分一致(应该允许编码风格糟糕的唯一情况)。 [Python中]:PEP 8 -风格指南Python代码包含的Python编码风格指南
  • 该补丁应用于原始文件 ( utrunner.py ),具有以下属性(对v 2019.2.3仍然有效(最后检查: 20190930 )):
    • 尺寸: 5865
    • sha256sum: db98d1043125ce2af9a9c49a1f933969678470bd863f791c2460fe090c2948a0
  • 应用补丁
    • utrunner位于“ ${PYCHARM_INSTALL_DIR}/helpers/pycharm
    • 通常, ${PYCHARM_INSTALL_DIR}指向:
      • 尼克斯/usr/lib/pycharm-community
      • Win : " C:\\Program Files (x86)\\JetBrains\\PyCharm 2016.3 "(适应你的版本号)
    • 保存补丁内容(在一个名为utrunner.patch的文件中,假设它在/tmp 下
    • Nix - 事情很简单,只需( cdutrunner的文件夹并)运行patch -i /tmp/utrunner.patch [man7]:PATCH(1)是一个默认安装的实用程序( Ubtu补丁dpkg 的一部分)。 请注意,由于utrunner.pyroot 所有,因此在此步骤中您需要sudo
    • Win - 要遵循类似的步骤,但由于没有本机补丁实用程序,因此事情变得更加棘手。 但是,有一些解决方法:
      • 使用Cygwin Nix ( Lnx ) 情况下,补丁实用程序可用,但默认情况下不会安装 补丁包必须从Cygwin setup显式安装。 我试过了,它有效
      • 还有其他选择(我没有尝试过):
      • Nix的情况一样,修补文件(很可能)必须由其中一位管理员完成 另外,注意文件路径,如果它们包含空格,请确保(dbl)引用它们
    • 恢复补丁
      • 备份是无害的(除了来自可用磁盘空间的PoV ,或者当它们开始堆积时,管理它们变得很痛苦)。 在我们的案例中不需要它们。 为了恢复更改,只需在修改后的文件上运行命令: patch -Ri /tmp/utrunner.patch ,它将把它切换回原来的内容(它还会创建一个utrunner.py.orig文件修改后的内容;它实际上会切换.py.py.orig文件)。
        不过总是回到第3方修改它们(尤其是如果他们被一些工具/安装跟踪)文件前起来,所以,如果在修改他们不顺心的事,总有恢复原来状态的方法
    • 虽然这里不是这种情况,但是如果更改是另一种形式,例如应用了补丁的文件(例如在GitHub 上),您显然可以获得整个文件(如果有很多文件,跟踪所有文件可能会变成一个痛)并覆盖你的。 但同样,首先支持它(他们)

关于这种方法的几句话

  • 代码可以处理(可选)环境变量(除了DJANGO_TEST_MODE_GAINARIE - 这是强制性的):

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

R单击相同的测试(删除其先前的配置后:d),然后

 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

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

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

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

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

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

注意事项

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

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

@编辑0

  • 正如我在回复@Udi 的评论时所说,对于负担不起(或不愿意的公司)支付PyCharm 专业版许可费的人来说,这是一个替代方案(快速浏览它看起来大约是 100 美元-200 美元)每个实例的$ / 年)

请参阅https://github.com/AndreyMZ/jb_django_test_runner/blob/master/README.md

优点:

  1. 它适用于 PyCharm 2019.3.2。
  2. 输出窗口显示测试结果而不是错误“测试框架意外退出”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM