Ted Klein Bergman
Ted Klein Bergman

Reputation: 9766

How to test a method where the set up and checking depends on untested methods?

I've created an example class (a bitmask class) which has 4 really simple functions. I've also created a unit-test for this class.

import unittest

class BitMask:

    def __init__(self):
        self.__mask = 0

    def set(self, slot):
        self.__mask |= (1 << slot)

    def remove(self, slot):
        self.__mask &= ~(1 << slot)

    def has(self, slot):
        return (self.__mask >> slot) & 1

    def clear(self):
        self.__mask = 0


class TestBitmask(unittest.TestCase):

    def setUp(self):
        self.bitmask = BitMask()

    def test_set_on_valid_input(self):
        self.bitmask.set(5)
        self.assertEqual(self.bitmask.has(5), True)

    def test_has_on_valid_input(self):
        self.bitmask.set(5)
        self.assertEqual(self.bitmask.has(5), True)

    def test_remove_on_valid_input(self):
        self.bitmask.set(5)
        self.bitmask.remove(5)
        self.assertEqual(self.bitmask.has(5), False)

    def test_clear(self):
        for i in range(16):
            self.bitmask.set(i)
        self.bitmask.clear()
        for j in range(16):
            with self.subTest(j=j):
                self.assertEqual(self.bitmask.has(j), False)

The problem I'm facing is that all these tests requires both the set and has methods for setting and checking values in the bitmask, but these methods are untested. I cannot confirm that one is correct without knowing that the other one is.

This example class isn't the first time I've experienced this issue. It usually occurs when I need to set up and check values/states within a class in order to test a method.

I've tried to find resources that explain this, but unfortunately their examples only use pure functions or where the changed attribute can be read directly. I could solve the problem by extracting the methods to be pure functions, or using a read-only property that returns the attribute __mask.

But is this the preferred approach? If not, how do I test a method that needs to be set up and/or checked using untested methods?

Upvotes: 2

Views: 184

Answers (2)

Evgeny
Evgeny

Reputation: 4571

Not sure this answers your question, as it deals with changing of initial class design, but here it goes.

You make a lazy class with no constructor or property , which hides the state of your object. It is not the set or has methods that are untested, it is the issue of state of the object being unknown. Have you had a .value property to reveal self.__mask, this would solve a question of testing .set() and has().

Also I would strongly consider a default value in constructor, which makes it a better-looking instantination and allows easier testing (some advice on avoiding setters in python is here).

def __init__(self, mask=0):
    self.__mask = mask

If there any design considerations that prevent you from having a .value property, perhaps an `__eq__ method can be used, if __init__ accepts a value.

 a = BitMask(0)     
 b = BitMask(5)     
 a.set(5)
 assert a == b     

Of course, you can challenge that on how is __eq__tested itself.

Finally, perhaps you are failiar with patching or monkey-patching - a technique to block something inside a object under test or make it work differently (eg imitate web response without actual call). With any of the libraries for pathcing I think you would still endup-performing a kind of x.__mask = value assignment, which is not too reasonable for a small, nice, and locally-defined class like one here.

Hope it helps in line of what you are exploring.

Upvotes: 2

Andrew Morozko
Andrew Morozko

Reputation: 2816

I would’ve used single underscore instead of double, and just looked directly at the _mask in unit test.

Python doesn’t really have private attributes or methods, even double underscore attributes are accessible on your instance like this: obj._BitMask__mask. Double underscore is used when you want subclasses to not overwrite the attribute of superclass. To indicate “private” you should use single underscore.

Allowing access to private fields is a part of python's design, so using this ability responsibly is not considered wrong, doubly so if you are accessing your own class.

The rationale behind "Do not touch the private fields" is that you as the developer can mess something up with the internals of the class, also private interface of s library can change at any point and break your code. When you are writing unit tests you are not afraid of messing with your own class, and is accepting that you have to change unit test if you change your class, so this programming idiom is not useful for you to apply.

Upvotes: 0

Related Questions