2

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?

Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50

2 Answers2

2

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.

Evgeny
  • 4,173
  • 2
  • 19
  • 39
0

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.

Andrew Morozko
  • 2,576
  • 16
  • 16
  • Considering that unit-testing is applicable to all languages, I would suspect that there would be a suggested approach that isn't dependent on the specific implementation of Python. The fact that Python is dynamic, has much metadata and that everything is public, it's possible to use this to solve problem like these, but I'm more looking for the preferred design choice when constructing classes and/or unit-tests in general. – Ted Klein Bergman Jun 17 '18 at 17:29
  • I am not aware of some general method, but considering that even in C# private method(and, I suspect, property) testing [requires introspection](https://msdn.microsoft.com/en-us/library/ms184807(v=vs.80).aspx) there is no clean way to do this. – Andrew Morozko Jun 17 '18 at 17:38
  • I'm a bit skeptical considering [this answer](https://stackoverflow.com/a/2811167/6486738) that suggest that testing using introspection for my own code is considered flaws in my design. Which is why I feel like introspection probably isn't the right choice for me, but rather that it's something I should change by my design. As one choice is to make the mask available as a read-only property, but I'm still hesitant if it's good to practice to make internal variables readable. – Ted Klein Bergman Jun 17 '18 at 17:56
  • This idea is expressed in the phrase [we are all responsible users](http://docs.python-guide.org/en/latest/writing/style/#we-are-all-responsible-users). If your property is not a part of your interface - mark it private. But if for testing purposes you need to access `._mask` - it's your code, do whatever you need. Also I'd like to remind you of mocking - literally monkey patching libraries - this is standard tool of unit testing and a huge no-no in regular programming. And finally, look for `._` in unit tests of big python projects (requests, django, ...), you'll see plenty. – Andrew Morozko Jun 17 '18 at 18:36
  • 1
    After reading up on this it seems like reading the "private" attribute is more or less the way to do it – Ted Klein Bergman Jun 21 '18 at 00:35