【问题标题】:How to unit test this python class?如何对这个 python 类进行单元测试?
【发布时间】:2018-03-30 21:08:18
【问题描述】:

我的代码的可测试性存在问题。这与我的班级布局和我的python包布局有关。

我希望这个问题有以下结果之一:

  1. 建议更改课程布局,或
  2. 建议更改包布局,或
  3. 提示如何在不更改布局的情况下测试这些东西

类层次结构

基类是AuthenticationTokenHardwareTokenKeyfile 这两个类都继承自它。

AuthenticationTokens 可以序列化为字符串,反之亦然。这就是我实现反序列化的方式:

class AuthenticationToken(object):

    @classmethod
    def try_deserialize(cls, spec: str):
        for subclass in cls.__subclasses__():
            token = subclass.try_deserialize(spec)
            if token:
                return token
        return None

python 包布局

我每个类都有一个文件并将它们放入包目录中

package
+-- __init__.py
+-- authentication_token.py
+-- hardware_token.py
+-- keyfile.py

现在我更喜欢引用 package.Keyfile 这样的类,而不是 package.keyfile.Keyfile。在我可以使用try_derialize 方法之前,python 已经看到了 Authentication token 的所有子类定义。这就是我在__init__.py 中导入所有类的原因:

from .authentication_token import AuthenticationToken
from .hardware_token import HardwareToken
from .keyfile import Keyfile

可测试性问题

现在我想在不引用其子类的情况下对 AuthenticationToken 类进行单元测试。这个想法是编写一个TestAutheticationToken 类并在测试期间将其用作单个子类:

import unittest
from package import AuthenticationToken

class TestSubclass(AuthenticationToken):
    pass

class TestAuthenticationToken(unittest.TestCase):

    # This test fails
    def test_bad_case(self):
        should_be_none = AuthenticationToken.try_deserialize("Keyfile")
        self.assertIsNone(should_be_none)

if __name__ == '__main__':
    unittest.main()

此测试失败,因为try_deserialize 创建了一个Keyfile 类型的对象。这是因为 __init__.py 被评估。如果我直接从模块中导入AuthenticationToken,也是这种情况:

from package.authentication_token import AuthenticationToken

问题

所以问题是:在测试AuthenticationToken 时,如何防止KeyfileHardwareToken 类被导入?

否则我如何更改类和/或包布局,以便我可以相互独立地导入所有类,同时仍然保留上述好处?

【问题讨论】:

  • 为什么你写你的try_deserialize 的方式取决于 Python 加载了哪些模块?听起来这就是这里的根本问题。

标签: python unit-testing serialization package factory-pattern


【解决方案1】:

代码很难测试,因为令牌提供者列表是隐式。这取决于已加载哪些模块。我建议您至少给出一个明确的令牌提供者列表的选项。要么要求在启动时注册令牌提供者,要么为令牌提供者列表提供可选参数。

这是我能想到的最简单的改变:

class AuthenticationToken(object):

    @classmethod
    def try_deserialize(cls, spec: str, token_providers=None):
        if token_providers is None:
            token_providers = cls.__subclasses__()
        for subclass in token_providers:
            token = subclass.try_deserialize(spec)
            if token:
                return token
        return None

现在你的常规代码没有改变,你的测试可以是这样的:

    def test_bad_case(self):
        should_be_none = AuthenticationToken.try_deserialize(
            "Keyfile",
            token_providers=[TestSubclass])
        self.assertIsNone(should_be_none)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-12
    • 2018-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-17
    • 1970-01-01
    相关资源
    最近更新 更多