1

I have a class with the following property clusters:

import numpy as np

class ClustererKmeans(object):

    def __init__(self):
        self.clustering = np.array([0, 0, 1, 1, 3, 3, 3, 4, 5, 5])

    @property
    def clusters(self):
        assert self.clustering is not None, 'A clustering shall be set before obtaining clusters'
        return np.unique(self.clustering)

I now want to write a unittest for this simple property. I start off with:

from unittest import TestCase, main
from unittest.mock import Mock

class Test_clusters(TestCase):

    def test_gw_01(self):
        sut = Mock()
        sut.clustering = np.array([0, 0, 1, 1, 3, 3, 3, 4, 5, 5])
        r = ClustererKmeans.clusters(sut)
        e = np.array([0, 1, 3, 4, 5])
        # The following line checks to see if the two numpy arrays r and e are equal,
        # and gives a detailed error message if they are not. 
        TestUtils.equal_np_matrix(self, r, e, 'clusters')

if __name__ == "__main__":
    main()

However, this does not run.

TypeError: 'property' object is not callable

I next change the line r = ClustererKmeans.clusters(sut) to the following:

r = sut.clusters

But again, I get an unexpected error.

AssertionError: False is not true : r shall be a <class 'numpy.ndarray'> (is now a <class 'unittest.mock.Mock'>)

Is there an easy way to test the implementation of a property in Python using the unittest framework?

physicalattraction
  • 6,485
  • 10
  • 63
  • 122
  • 2
    Should you not be doing `r = sut.clusters` ? The `self` argument is sent by default. I see another issue `sut.clustering` is not the right way of initializing the class variable. You should be sending it as an argument while initializing the class – karthikr Jan 05 '15 at 16:54
  • I did try `r = sut.clusters`, but in the code above, it returns a Mock object, not a numpy array. – physicalattraction Jan 06 '15 at 06:08

1 Answers1

3

To call property directly you can replace in your original code ClustererKmeans.clusters(sut) by ClustererKmeans.clusters.__get__(sut).

Even if I'm a mocking enthusiastic IMHO this case is not a good example to apply it. Mocking are useful to remove dependencies from class and resources. In your case ClustererKmeans have a empty constructor and there isn't any dependency to break. You can do it by:

class Test_clusters(TestCase):
    def test_gw_01(self):
        sut = ClustererKmeans()
        sut.clustering = np.array([0, 0, 1, 1, 3, 3, 3, 4, 5, 5])
        np.testing.assert_array_equal(np.array([0, 1, 2, 3, 4, 5]),sut.clusters)

If you would use mocking you can patch ClustererKmeans() object by using unittest.mock.patch.object:

def test_gw_01(self):
    sut = ClustererKmeans()
    with patch.object(sut,"clustering",new=np.array([0, 0, 1, 1, 3, 3, 3, 4, 5, 5])):
        e = np.array([0, 1, 3, 4, 5])
        np.testing.assert_array_equal(np.array([0, 1, 2, 3, 4, 5]),sut.clusters)

...but why use patch when python give to you a simple and direct way to do it?

Another way to use mock framework should be trust numpy.unique and check if the property do the right work:

@patch("numpy.unique")
def test_gw_01(self, mock_unique):
    sut = ClustererKmeans()
    sut.clustering = Mock()
    v = sut.clusters
    #Check is called ....
    mock_unique.assert_called_with(sut.clustering)
    #.... and return
    self.assertIs(v, mock_unique.return_value)

    #Moreover we can test the exception
    sut.clustering = None
    self.assertRaises(Exception, lambda s:s.clusters, sut)

I apologize for some errors but I don't test the code. I you notify to me I will fix all as soon as possible.

Michele d'Amico
  • 22,111
  • 8
  • 69
  • 76
  • I fixed the test for `np.array` after read http://stackoverflow.com/a/14921351/4101725 and http://stackoverflow.com/a/14249723/4101725 – Michele d'Amico Jan 05 '15 at 22:37
  • The line `r = ClustererKmeans.clusters(__get__(sut))` gives me an error: `NameError: name '__get__' is not defined`. The constructor is empty here, since I left out all irrelevant details in my question. In practice, the code is more elaborate. – physicalattraction Jan 06 '15 at 06:07
  • The line `r = ClustererKmeans.clusters.__get__(sut)` does work as expected. Thanks for pointing this out! – physicalattraction Jan 06 '15 at 06:11