【问题标题】:HTML report for django testsdjango 测试的 HTML 报告
【发布时间】:2019-11-07 00:15:07
【问题描述】:

我有一个 Django 项目,其中包含一个 API(如果在任何地方都很重要,则使用 rest 框架创建)。我为 API 添加了一些测试,但为了对测试有一个整体的了解,无论是通过、失败还是缺失,我都需要创建一个 HTML 报告。 测试完成后,应生成 HTML 表格报告,其中显示测试期间覆盖的端点和 HTTP 响应、测试结果以及缺少测试的组合。

不幸的是,我不明白我应该怎么做。我知道覆盖可以给我一份详细的 html 报告,但这不是我需要的,我需要这样的东西:

|端点描述 | 200 | 400 | 403 | 404 |
|获取 /endpoint1 |通过 |通过 |通过 |不适用 |
|发布 /endpoint1 |通过 |失败 |失踪|不适用 |

有人对此有任何想法吗?也许一些库可以帮助解决这个问题,或者我应该使用什么策略?

提前谢谢你

【问题讨论】:

  • 你的测试是一堆TestCase sublcasses,你通过执行./manage.py test来运行它们,对吧?
  • 是的,我就是这个意思

标签: django testing


【解决方案1】:

迟到了,但这是我为 Django 测试输出 HTML 测试报告的解决方案。 (基于HtmlTestRunner cannot be directly used with Django DiscoverRunner

如果将以下类放在 tests/html_test_reporter.py 中,则可以将其用作 DiscoverRunner,该 DiscoverRunner 已修补以使用 HTMLTestRunner。

from django.test.runner import DiscoverRunner
from HtmlTestRunner import HTMLTestRunner

class MyHTMLTestRunner(HTMLTestRunner):
    def __init__(self, **kwargs):
        # Pass any required options to HTMLTestRunner 
        super().__init__(combine_reports=True, report_name='all_tests', add_timestamp=False, **kwargs)

class HtmlTestReporter(DiscoverRunner):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # Patch over the test_runner in the super class.
        html_test_runner = MyHTMLTestRunner
        self.test_runner=html_test_runner

然后运行:

python manage.py test -v 2 --testrunner tests.html_test_reporter.HtmlTestReporter

默认情况下,Django 项目使用 django.test.runner.DiscoverRunner 搜索测试,然后使用 PyTest 运行它们。 HTMLTestRunner 可以与 PyTest 一起使用以输出 HTML 测试报告,但似乎可以将 PyTest 配置为通过 DiscoverRunner 使用 HTMLRunner。

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    由于 Django 使用 python 的标准 unittest 库,您必须调整它的一些部分。

    首先,您需要一些方法来指定哪些测试实际测试哪个端点。自定义装饰器很方便:

    from functools import wraps
    
    def endpoint(path, code):
        """
        Mark some test as one which tests specific endpoint.
        """
        def inner(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                return func(*args, **kwargs)
            return wrapper
    
        inner._endpoint_path = path
        inner._endpoint_code = code
        return inner
    
    
    class MyTestCase(TestCase):
        @endpoint(path='/path/one', code=200)
        def test_my_path_is_ok(self):
            response = self.client.get('/path/one?foo=bar')
            self.assertEqual(response.status_code, 200)
    
        @endpoint(path='/path/one', code=404)
        def test_my_path_expected_errors(self):
            response = self.client.get('/path/one?foo=qux')
            self.assertEqual(response.status_code, 404)
    
        def test_some_other_stuff(self):
            # this one will not be included in our results grid.
            pass
    

    您可以使用“神奇”的方法(例如,特殊方法的名称来猜测它们正在测试的端点),但是 explicit is better than implicit,对吗?

    那么,您需要一种方法来收集测试结果 - 特别是测试端点的结果。在这里,我们创建了一个(非常草稿)unittest.TestResult 的子类来处理它:

    class EndpointsTestResult(TestResult):
        def __init__(self):
             super(EndpointsTestResult, self).__init__()
             self.endpoint_results = {}
    
        def addError(self, test, err):
            super(EndpointsTestResult, self).addError(test, err)
            if hasattr(test, '_endpoint_path'):
                branch = self.endpoint_results.setdefault(getattr(test, '_endpoint_path'), {})
                branch[getattr(test, '_endpoint_code')] = 'MISSING'
    
        def addFailure(self, test, err):
            # similar as addError()
    
        def addSuccess(self, test):
            # similar as addError()
    

    最后是时候实际输出我们的结果了。让我们创建一个 unittest.TextTestRunner 的子类并在我们的自定义运行器中指定它:

    class EndpointsTestRunner(TextTestRunner):
        def _makeResult(self):
             self._result = EndpointsTestResult()
             return self._result
    
        def run(self, test):        
            super(EndpointsTestRunner).run(test)
            # After running a test, print out the table
            generate_a_nifty_table(self._result.endpoint_results)
    
    
    class EndpointsDjangoRunner(django.test.runner.DiscoverRunner):
        test_runner = EndpointsTestRunner
    

    现在我们有了自定义的EndpointsDjangoRunner,我们应该在settings.py 中指定它:

    TEST_RUNNER = 'path.to.the.EndpointsDjangoRunner'
    

    就是这样。如果您在代码中发现任何尴尬的错误,请告诉我。

    【讨论】:

    • 根据您的回答过一会儿开始实施!我会让你知道它是怎么回事!非常感谢!
    • 在使用装饰器并尝试测试实现时,我遇到了TypeError: endpoint() takes exactly 3 arguments (2 given) 错误。第一个参数 func 不应该是它所装饰的函数吗?
    • 也许func arg 应该是 inner 的方法 arg 而不是装饰器的
    • @koslib,试试(稍微)更新的版本。它有帮助吗?不幸的是,现在我无法检查和测试我的代码,稍后会尝试这样做。
    • 亚历克斯,它似乎有效。现在我正在将它与我们项目所需的现有 django-nose 测试套件集成方面取得进展。
    猜你喜欢
    • 2018-07-26
    • 2018-04-05
    • 2020-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-03
    • 1970-01-01
    相关资源
    最近更新 更多