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

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

问题描述

我必须强调PyCharm 社区,它没有任何Django集成(v2016.3.2 提问时间).

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

问题很简单:在 PyCharm 中,只需单击鼠标右键(从上下文菜单),如下图所示:

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

<块引用>

回溯(最近一次调用):文件C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py",第 254 行,在 <module> 中主要的()文件C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py",第 232 行,在 main模块 = loadSource(a[0])文件C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py",第 65 行,在 loadSource 中模块 = imp.load_source(模块名,文件名)文件E:WorkDevDjangoTutorialsproj0srcpolls	ests.py",第 7 行,在 <module>从 polls.models 导入问题文件E:WorkDevDjangoTutorialsproj0srcpollsmodels.py",第 9 行,在 <module>类问题(模型.模型):文件E:WorkDevDjangoTutorialsproj0srcpollsmodels.py",第 10 行,在问题中question_text = models.CharField(max_length=200)文件E:WorkDevVEnvspy2713x64-djangolibsite-packagesdjangodbmodelsfields\__init__.py",第 1043 行,在 __init__super(CharField, self).__init__(*args, **kwargs)文件E:WorkDevVEnvspy2713x64-djangolibsite-packagesdjangodbmodelsfields\__init__.py",第 166 行,在 __init__self.db_tablespace = db_tablespace 或 settings.DEFAULT_INDEX_TABLESPACE文件E:WorkDevVEnvspy2713x64-djangolibsite-packagesdjangoconf\__init__.py",第 53 行,在 __getattr__self._setup(名称)文件E:WorkDevVEnvspy2713x64-djangolibsite-packagesdjangoconf\__init__.py",第 39 行,在 _setup% (desc, ENVIRONMENT_VARIABLE))django.core.exceptions.ImproperlyConfigured:请求设置 DEFAULT_INDEX_TABLESPACE,但未配置设置.在访问设置之前,您必须定义环境变量 DJANGO_SETTINGS_MODULE 或调用 settings.configure().

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

解决方案

1.背景资料

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

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

回到问题:我对一个包含 Django 教程([DjangoProject]:编写你的第一个 Django 应用) + Django Rest Framework Tutorial 的一些部分([DRF]:快速入门).例如,我将尝试运行 polls/tests.py:QuestionViewTests.test_index_view_with_no_questions()

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

2.创建 Python 配置

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

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

当然,必须对每个测试用例(及其方法)都这样做并不是可行的方法(这真的很烦人),因此这种方法不可扩展.

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

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

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

<块引用>

"E:WorkDevVEnvspy2713x64-djangoScriptspython.exe" "C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py" E:WorkDevDjangoTutorialsproj0srcpolls	ests.py::QuestionViewTests::test_index_view_with_no_questions true

如您所见,它正在启动C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py"(我将其称为 utrunner) 和一堆参数(第 1st 对我们很重要,因为它是测试规范).utrunner 使用一个不关心 Django 的测试运行框架(实际上有一些 Django 处理代码,但这对我们没有帮助).

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

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

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

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

  • 像之前的方法一样设置工作目录
  • 环境变量中添加一个名为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 @@除了:经过-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])+ 返回模组+ 其他:+ 返回无+++def utrunnerArgToDjangoTest(arg, basePath):+ 如果 arg.strip() 而不是 arg.startswith("--"):+ testData = arg.split("::")+ mods = fileToMod(testData[0], basePath)+ 如果模组:+ testData[0] = mods+ 返回 ".".join(testData)+ 其他:+ 返回无+ 其他:+ 返回无+++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+ 代码 = compile(codeStr, os.path.basename(argv[0]), "exec")+ codeGlobals.update({+ "__name__": "__main__",+ "__file__": argv[0]+ })+ exec(code, codeGlobals)+++def djangoMain():+ djangoTests = 列表()+ basePath = os.getcwd()+ 对于 sys.argv[1: -1] 中的 arg:+ djangoTest = utrunnerArgToDjangoTest(arg, basePath)+ 如果 djangoTest:+ djangoTests.append(djangoTest)+ 如果不是 djangoTests:+ debug("/[DJANGO MODE] 无效参数:" + sys.argv[1: -1])+ startupTestArgs = [os.getenv("DJANGO_STARTUP_TEST_ARGS", "").split(" ") if item 中的项目的项目]+ startupFullName = os.path.join(basePath, os.getenv("DJANGO_STARTUP_NAME", "manage.py"))+ 如果不是 os.path.isfile(startupFullName):+ debug("/[DJANGO MODE] 无效的启动文件:" + startupFullName)+ 返回+ djangoStartupArgs = [startupFullName, "test"]+ djangoStartupArgs.extend(startupTestArgs)+ djangoStartupArgs.extend(djangoTests)+ additionalGlobalsStr = os.getenv("DJANGO_STARTUP_ADDITIONAL_GLOBALS", "{}")+ 导入 ast+ additionalGlobals = ast.literal_eval(additionalGlobalsStr)+ 冲洗缓冲区()+ runModAsMain(djangoStartupArgs, additionalGlobals)+ 冲洗缓冲区()+++定义主():arg = sys.argv[-1]如果 arg == 真":导入单元测试@@ -186,3 +253,10 @@调试(/加载"+ str(all.countTestCases())+测试")TeamcityTestRunner().run(all, **options)+++if __name__ == "__main__":+ if os.getenv("DJANGO_TEST_MODE_GAINARIE"):+ djangoMain()+ 其他:+ 主()

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

补丁中的代码做了什么(它可能比普通的Python代码更难理解):

  • main 块下的所有内容(if __name__ == "__main__": 或当前行为)已移至名为 main(保持分开,避免误改)
  • 修改了 main 块,因此如果定义了环境变量 DJANGO_TEST_MODE_GAINARIE(并且不为空),它将遵循新的实现(djangoMain 函数),否则将正常运行.新的实现:
    • fileToModfilePath 中减去 basePath 并将差异转换为 Python 包样式.例如:fileToMod("E:WorkDevDjangoTutorialsproj0srcpolls ests.py", "E:WorkDevDjangoTutorialsproj0src"), 将返回 polls.tests
    • utrunnerArgToDjangoTest:使用前面的函数,然后添加类名(QuestionViewTests)和(可选)方法名(test_index_view_with_no_questions),所以最后它从 utrunner 格式(E:WorkDevDjangoTutorialsproj0srcpolls ests.py::QuestionViewTests::test_index_view_with_no_questions) 到 manage.py 格式(polls.tests.QuestionViewTests.test_index_view_with_no_questions)
    • flushBuffers:写入一个 eoln 字符并刷新 stdoutstderr 缓冲区(这是必需的,因为我注意到有时 PyCharmDjango 的输出是交错的,最后的结果是乱七八糟的)
    • runModAsMain:通常,所有相关的 manage.py 代码都在 if __name__ == "__main__": 下.这个函数欺骗"了 Python 让它相信 manage.py 作为它的第一个st 参数运行

修补utrunner:

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

关于这种方法的几个词:

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

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

RClick 在同一个测试上(删除之前的配置之后 :d),然后voilà:

<块引用>

E:WorkDevVEnvspy2713x64-djangoScriptspython.exe "C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py" E:WorkDevDjangoTutorialsproj0srcpolls	ests.py::QuestionViewTests::test_index_view_with_no_questions true测试于 01:38 开始...使用现有的测试数据库作为别名默认"....----------------------------------------------------------------------在 0.390 秒内运行 1 次测试好的为别名default"保留测试数据库...进程完成,退出代码 0

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

注意事项(目前我确定了其中的两个):

  • 这是良性的,它只是一个 UI 问题:utrunner(很可能)有一些 PyCharm 期望发生的初始化,这在我们的情况下显然不是.因此,即使测试成功结束,从 PyCharmPoV 来看,他们也没有成功,因此 Output 窗口将包含警告:"测试框架意外退出"
  • 这是一个令人讨厌的问题,我(还)无法深入了解它.显然,在 utrunner 中,任何 input(raw_input) 调用都没有得到很好的处理;提示文本:如果您想尝试删除测试数据库‘test_tut-proj0’,请输入‘yes’,或‘no’取消:"(如果之前的测试运行崩溃,则会出现该提示,并且它的 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
  • :
    • W10 x64
    • PyCharm 社区版 2016.3.2
    • Python 2.7.13 (VEnv)
    • Django 1.10.6

注意事项:

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

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

@EDIT0:

  • 正如我在回复@Udi 的评论时所说,对于负担不起(或不愿意)支付PyCharm 专业版许可费的人(或公司)来说,这是一个替代方案(快速浏览它看起来每个实例 ~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:

Traceback (most recent call last):
    File "C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py", line 254, in <module>
        main()
    File "C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py", line 232, in main
        module = loadSource(a[0])
    File "C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py", line 65, in loadSource
        module = imp.load_source(moduleName, fileName)
    File "E:WorkDevDjangoTutorialsproj0srcpolls	ests.py", line 7, in <module>
        from polls.models import Question
    File "E:WorkDevDjangoTutorialsproj0srcpollsmodels.py", line 9, in <module>
        class Question(models.Model):
    File "E:WorkDevDjangoTutorialsproj0srcpollsmodels.py", line 10, in Question
        question_text = models.CharField(max_length=200)
    File "E:WorkDevVEnvspy2713x64-djangolibsite-packagesdjangodbmodelsfields\__init__.py", line 1043, in __init__
        super(CharField, self).__init__(*args, **kwargs)
    File "E:WorkDevVEnvspy2713x64-djangolibsite-packagesdjangodbmodelsfields\__init__.py", line 166, in __init__
        self.db_tablespace = db_tablespace or settings.DEFAULT_INDEX_TABLESPACE
    File "E:WorkDevVEnvspy2713x64-djangolibsite-packagesdjangoconf\__init__.py", line 53, in __getattr__
        self._setup(name)
    File "E:WorkDevVEnvspy2713x64-djangolibsite-packagesdjangoconf\__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.

解决方案

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:WorkDevDjangoTutorialsproj0src"). 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:

"E:WorkDevVEnvspy2713x64-djangoScriptspython.exe" "C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py" E:WorkDevDjangoTutorialsproj0srcpolls	ests.py::QuestionViewTests::test_index_view_with_no_questions true

As you can see, it's launching "C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.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.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 3rd 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:WorkDevDjangoTutorialsproj0srcpolls ests.py", "E:WorkDevDjangoTutorialsproj0src"), 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:WorkDevDjangoTutorialsproj0srcpolls ests.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 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). [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)JetBrainsPyCharm 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 3rd-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à:

E:WorkDevVEnvspy2713x64-djangoScriptspython.exe "C:InstallPyCharm Community Edition2016.3.2helperspycharmutrunner.py" E:WorkDevDjangoTutorialsproj0srcpolls	ests.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:

  • 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 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 社区版中的鼠标右键单击上下文菜单运行/调试 Django 应用程序的 UnitTests?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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