Python:使模块对TestRunner可见的正确方法是什么?

时间:2012-05-04 19:25:44

标签: python django unit-testing testing django-testing

在观看了几个关于django测试的演示之后,我想编写自己的TestRunner来跳过django测试,并为我的测试创建更好的包结构。

问题在于我们已经改变了项目结构,并且测试运行器无法找到进行测试发现的正确途径。这就是我的项目的样子:

project/
  -src/
    - project_name/
      - apps/
      - test/ # Not a good name, i know, will change it
         - some_app/

           - test_models.py
    - manage.py
    - development.db

现在,为了测试test_models.py我想这样做:

$ cd project/src/
$ python manage.py test some_app.test_models

问题是测试运行器找不到该包(some_app)和模块(test_models.py)。如果我在测试运行器中硬编码名称,它会改变,但我不喜欢这样做。这就是我做的工作。

test_labels = ["%s.%s" % ("project_name.test", l)
                           for l in test_labels
                           if not l.startswith("project_name.test")]

所以,如果你这样做

$ python manage.py test some_app.test_models

它将被重写为:

$ python manage.py test project_name.test.some_app.test_models

这很好。

我尝试过做sys.path.append("(...)/project_name/test),但两种方法都没有。

这是我的TestRunner的代码:

class DiscoveryDjangoTestSuiteRunner(DjangoTestSuiteRunner):
    """A test suite runner that uses unittest2 test discovery.
    It's better than the default django test runner, becouse it
    doesn't run Django tests and let you put your tests in different
    packages, modules and classes.

    To test everything in there:
        $ ./manage.py test

    To test a single package/module:

        $ ./manage.py test package
        $ ./manage.py test package.module

    To test a single class:

        $ ./manage.py test package.module.ClassName
    """
    def build_suite(self, test_labels, extra_tests=None, **kwargs):
        suite = None
        discovery_root = settings.TEST_DISCOVERY_ROOT
        if test_labels:
            # This is where I append the path
            suite = defaultTestLoader.loadTestsFromNames(test_labels)
            # if single named module has no tests, do discovery within it
            if not suite.countTestCases() and len(test_labels) == 1:
                suite = None
                discovery_root = import_module(test_labels[0]).__path__[0]

        if suite is None:
            suite = defaultTestLoader.discover(
                discovery_root,
                top_level_dir=settings.BASE_PATH,
                )

        if extra_tests:
            for test in extra_tests:
                suite.addTest(test)

        return reorder_suite(suite, (TestCase,))

2 个答案:

答案 0 :(得分:2)

在您继续为自定义TestRunner投入更多时间之前,我绝对建议您查看django-nose

django-nose提供的自定义测试运行器实现了nose的测试运行器,它非常灵活,并提供了很多运行测试的选项。它无缝地覆盖默认的test管理命令,并允许您在项目的settings模块中配置默认​​测试选项。

我真的推荐它有几个原因:

  • test命令的选项已完整记录(take look at the output
  • nose为test discovery
  • 提供了很多方法
  • 机会是你的同事已经是经验丰富的鼻子用户
  • 您不必自己编写TestRunner课程

答案 1 :(得分:2)

您的Python导入层次结构植根于project/src。因此,test_models模块的正确Python导入路径为project_name.test.some_app.test_models,因此我希望将其作为测试标签传递。

但是,每次要运行特定的测试模块时,您不喜欢键入project_name.test前缀,因为所有测试都将位于那里。没关系:你选择引入一些隐含的非显而易见的行为以换取一些方便。你绝对不应该向sys.path添加任何内容以实现这一目标:Python导入健全性的关键是让你的导入层次结构为一个给定的代码库,只有一个地方;重叠的sys.path条目将导致问题,例如在不同名称下对同一模块的双重导入。

你真正想要的只是一个用户界面的便利,在我看来,你所展示的测试标签代码是实现这种便利性的明显方式。你不喜欢硬编码project_name.test前缀,但它必须在某个地方进行硬编码:测试运行器无法巧妙地弄清楚你想用project_name.test预先添加测试标签。如果您希望TestRunner更通用,可以将其拉出到BASE_TEST_MODULE之类的设置或类似设置中,并将该设置的值添加到每个测试标签中。