简体   繁体   English

如何对该Python类进行单元测试?

[英]How to unit test this python class?

I'm having an issue with the testability of my code. 我的代码的可测试性有问题。 This is related to my class layout and my python package layout. 这与我的类布局和python包布局有关。

I hope for one of the following outcomes for this question: 我希望此问题的下列结果之一:

  1. a suggestion to change the class layout, or 改变班级布局的建议,或
  2. a suggestion to change the package layout, or 更改包装布局的建议,或
  3. a hint how to test this stuff without layout changes 提示如何在不更改布局的情况下测试这些东西

The class hierarchy 类层次结构

The base class is AuthenticationToken . 基类是AuthenticationToken The two classes HardwareToken and Keyfile inherit from it. 这两个类HardwareTokenKeyfile继承自它。

AuthenticationToken s can be serialized into a string and vice versa. AuthenticationToken可以序列化为字符串,反之亦然。 This is how I implement deserialization: 这是我实现反序列化的方式:

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

The python package layout python包布局

I have one file per class and put them into a package directory 我每个班级只有一个文件,并将它们放在包目录中

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

Now I prefer to reference a class like package.Keyfile instead of package.keyfile.Keyfile . 现在,我更喜欢引用诸如package.Keyfile类的类,而不是package.keyfile.Keyfile Also all subclass definitions of Authentication token have be seen by python before I can use the try_derialize method. 在我可以使用try_derialize方法之前,Python还会看到Authentication令牌的所有子类定义。 This is why I import all classes in __init__.py : 这就是为什么我将所有类导入__init__.py

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

The testability issue 可测试性问题

Now I would like to unit test the AuthenticationToken class without referencing its subclasses. 现在,我想对AuthenticationToken类进行单元测试,而不引用其子类。 The idea is to write a TestAutheticationToken class and use it as single subclass during the test: 这个想法是编写一个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()

This test fails because try_deserialize creates an object of type Keyfile . 该测试失败,因为try_deserialize创建了一个Keyfile类型的对象。 This is because __init__.py is evaluated. 这是因为__init__.py被求值。 This is also the case if I import AuthenticationToken directly from the module: 如果我直接从模块中导入AuthenticationToken也是如此:

from package.authentication_token import AuthenticationToken

The question 问题

So the question is: how can I prevent the classes Keyfile and HardwareToken from being imported when testing AuthenticationToken ? 所以问题是:如何在测试AuthenticationToken时防止导入KeyfileHardwareToken类?

Or otherwise how do I change the class and/or package layout so I can import all classes independtly from each other while still preserving the benefits mentioned above? 否则,如何更改类和/或程序包的布局,以便我可以彼此独立地导入所有类,同时仍保留上述好处?

The code is hard to test, because the list of token providers is implicit . 该代码很难测试,因为令牌提供者列表是隐式的 It depends on which modules have been loaded. 这取决于已加载的模块。 I suggest you at least give the option of an explicit list of token providers. 我建议您至少给出令牌提供者的明确列表选项 Either require the token providers to be registered at start up, or provide an optional parameter for the list of token providers. 要么要求在启动时注册令牌提供者,要么为令牌提供者列表提供可选参数。

Here's the simplest change I can think of: 这是我能想到的最简单的更改:

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

Now your regular code is unchanged, and your test can look like this: 现在您的常规代码保持不变,并且您的测试可以如下所示:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM