简体   繁体   中英

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.

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 . The two classes HardwareToken and Keyfile inherit from it.

AuthenticationToken s can be serialized into a string and vice versa. 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

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 . Also all subclass definitions of Authentication token have be seen by python before I can use the try_derialize method. This is why I import all classes in __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. The idea is to write a TestAutheticationToken class and use it as single subclass during the test:

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 . This is because __init__.py is evaluated. This is also the case if I import AuthenticationToken directly from the module:

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 ?

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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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