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

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

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

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

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

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

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),所以我没有深入研究它的高级知识

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

回到问题:我对由Django教程([DjangoProject]: Writing your first Django app)+ Django Rest Framework教程([DRF]: Quickstart)的某些部分组成的项目进行了测试/研究。例如,我将尝试运行polls / tests.py:QuestionViewTests.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
为您的配置命名(可选),然后单击“确定”。现在,您将可以运行此测试

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

3.调整PyCharm以执行我们想要的操作

请注意,我不认为这是一个真正的解决方案,它更像是(lam)解决方法(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的运行/调试配置的几句话:

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

考虑到以上几点,让我们继续:

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

像以前的方法一样设置工作目录
在环境变量中,添加一个名为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代码更难遵循):

主块下的所有内容(if __name__ == "__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格式()
flushBuffers:编写一个eoln char并刷新stdout和stderr缓冲区(这是必需的,因为我注意到有时PyCharm和Django的输出是交错的,最终结果被弄乱了)
runModAsMain:通常,所有相关的manage.py代码都在polls.tests.QuestionViewTests.test_index_view_with_no_questions下。该函数“欺骗” Python,使其认为manage.py作为其第一个参数运行

修补utrunner:

我自己进行了这些修改(我没有搜索具有Django集成的版本并从中得到启发)
utrunner是PyCharm的一部分。很明显,为什么JetBrains的人没有在Community Edition中包含任何Django集成:让人们购买Professional Edition。这有点踩他们的脚趾。我不知道修改utrunner的法律含义,但是无论如何,如果您对其进行修补,则是您自己承担责任和风险
编码样式:很烂(至少是从命名/缩进PoV中),但是与文件的其余部分一致(唯一的情况是应该允许使用编码样式。) [Python]: PEP 8 -- Style Guide for Python Code包含Python的编码风格指南
该修补程序将应用于具有以下属性的原始文件(utrunner.py)(对于v2019.2.3仍有效(最近检查:20190930)):

大小: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的文件夹,然后)运行if __name__ == "__main__":。 [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 -i /tmp/utrunner.patch,它将切换回其原始内容(它还将创建带有修改后内容的utrunner.py.orig文件;实际上切换.py和.py.orig文件)。但是,始终在修改之前备份第三方文件(特别是如果某些工具/安装程序正在跟踪它们),因此,如果在修改它们时出现问题,总有一种方法可以还原原始状态

尽管这里不是这种情况,但是如果更改是以另一种形式进行的,例如应用了补丁的文件(例如GitHub上的文件),则显然可以获取整个文件(如果有很多文件,则可以跟踪所有文件)变得痛苦)并覆盖您的痛苦。但是,再次将其备份!

关于此方法的几句话:

该代码可以处理(可选)env var(DJANGO_TEST_MODE_GAINARIE除外-这是强制性的):

DJANGO_STARTUP_NAME:如果manage.py具有其他名称(出于某种原因?),或者位于Working目录之外的其他文件夹中。这里重要的一点:指定文件路径时,请使用平台特定的路径分隔符:对于Nix,斜杠(/),对于Win,bkslash(\)
DJANGO_STARTUP_TEST_ARGS:patch -Ri /tmp/utrunner.patch接受的其他参数(运行manage.py test以获取整个列表)。在这里,我必须坚持使用-k / --keepdb来在两次运行之间保留测试数据库(默认情况下为test _ $ {REGULAR_DB_NAME}或在TEST字典下的设置中进行设置)。在运行单个测试时,创建数据库(并应用所有迁移)并销毁它可能很耗时(并且也很烦人)。该标志确保数据库不会在最后删除,并在下一次测试运行时被重用
DJANGO_STARTUP_ADDITIONAL_GLOBALS:必须具有Python字典的字符串表示形式。出于某些原因,manage.py要求在manage.py test --help词典中出现的任何值都应放在此处

修改默认配置时,所有先前创建的继承该配置的配置都不会更新,因此必须手动将其删除(新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

调试也可以(断点等)。

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

这是良性的,这只是一个UI问题:utrunner(最有可能)具有PyCharm希望进行的一些初始化,在我们的情况下显然不存在。因此,即使测试成功结束,但从PyCharm的PoV中未成功结束,因此“输出”窗口将包含警告:“测试框架意外退出”
这是一个令人讨厌的应用程序,但我还无法深入了解它。显然,在utrunner中,任何globals()input)调用都不能很好地处理。提示文本:“如果要尝试删除测试数据库'test_tut-proj0',请输入'yes',否则请取消'no':”(如果先前的测试运行崩溃,并且数据库未销毁时显示,结束)不会被显示,并且程序会冻结(这在utrunner之外不会发生),而不会让用户输入文本(也许混合中有线程?)。恢复的唯一方法是停止测试运​​行,删除数据库,然后再次运行测试。同样,我必须提升raw_input标志,它将解决此问题。

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

尼克斯(Lnx):

乌布图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 jar中?)
我注意到在helpers(utrunner的父文件夹)文件夹中有很多特定于Django的文件/文件夹,也许那些也可以使用,因此必须检查

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

@ EDIT0:

正如我回复@Udi的评论一样,对于那些负担不起(或不愿意支付)PyCharm Professional Edition许可费的人(在快速浏览中,它的价格约为100 $ -200)每个实例$ /年)

Python uuid4,如何限制唯一字符的长度 - python

在Python中,我正在使用uuid4()方法创建唯一的字符集。但是我找不到将其限制为10或8个字符的方法。有什么办法吗?uuid4()ffc69c1b-9d87-4c19-8dac-c09ca857e3fc谢谢。 参考方案 尝试:x = uuid4() str(x)[:8] 输出:"ffc69c1b" Is there a way to…

Python:无法识别Pip命令 - python

这是我拍摄的屏幕截图。当我尝试在命令提示符下使用pip时,出现以下错误消息:pip无法识别为内部或外部命令,可操作程序或批处理文件。我已经检查了这个线程:How do I install pip on Windows?我所能找到的就是我必须将"C:\PythonX\Scripts"添加到我的类路径中,其中X代表python版本。如您在我的…

Python:如何将有效的uuid从String转换为UUID? - python

我收到的数据是 { "name": "Unknown", "parent": "Uncategorized", "uuid": "06335e84-2872-4914-8c5d-3ed07d2a2f16" }, 我需要将uuid从Strin…

Python 3会流行吗? - python

我已经学习了一些Python 2和Python 3,似乎Python 2总体上比Python 3更好。这就是我的问题所在。是否有充分的理由真正切换到python 3? 参考方案 总体上,甚至在大多数细节上,Python3都比Python2更好。关于第三方库, Python 3落后于的唯一区域是。使Python变得如此出色的原因不仅在于它作为一种语言的内在特性…

python 。向字典中的键添加多个项目 - python

我正在尝试根据一组唯一值构建一个字典,以用作键和提供项目的元组压缩列表。set = ("a","b","c") lst 1 =("a","a","b","b","c","d",&#…