pythonunit-testingintegration-testingpython-unittest

Using the Unittest module in python to write non unit tests. Is it Bad practice?


Suppose I design a class, called A, in its own module. Consider an abbreviation of what the code might look like.

# file: A.py
class A:
  # define some useful behaviour for A class
  pass

Then I go on to perform some unit tests for this new class. The tests would be roughly be carried out as shown below.

The reason for defining the create_test_A function outside of the TestA class will become apparent below.

# file test_A.py
import A
import unittest

def create_test_A():
  # initialize into a useful test state before returning
  return A.A()

class TestA(unittest.TestCase):
  def test(self):
    new_A = create_test_A()
    # write some clever assertions about behaviour of new_A
    pass

Now suppose I build a new class, which is designed to work directly with instantiations of the A class.

class A_bundle:
  def __init__(self):
    # define some fields that work with objects of type <class A>
    pass

When I go to write unit tests for this new class, my first instinct would be to create an instance of it, and then create a few objects of type A to interact with it. Perhaps it would go like this.

#file test_A_bundle.py

import test_A # this is the part I feel weird doing
import A_bundle
import unittest

class TestABundle(unittest.TestCase):
  def test_a_bundle(self):
    # create a couple of test objects of type A
    new_A1 = test_A.create_test_A()
    new_A2 = test_A.create_test_A()

    # make a new A_bundle to perform tests on
    new_bundle = A_bundle.A_bundle()

    # make some assertions about the way new_bundle
    # should interact with new_A1 and new_A2

Have I now gone outside the scope of unit testing and into the world of integration testing, since I'm not just testing the behaviour of the A_bundle class independently?

And if so, it seems that I could still use the unittest module in this way and run some useful tests. Is this considered a bad practice? That is, using the unittest module to write and perform tests that are not, in fact, unit tests?


Solution

  • On my opinion you're still in the realm of unit tests, but I have a suggestion for you.

    The hint

    Insert instances of the class A as arguments of the __init__() method of class bundle_A.

    Following this hint I have modified your file A_bundle.py as following:

    class A_bundle:
        def __init__(self, a1, a2):
            self.a1 = a1
            self.a2 = a2
    
        # define some fields that work with objects of type <class A>
        def sum(self):
            return self.a1.m_a() + self.a2.m_a()
    

    I have also defined the method sum() in the class A_bundle: this is an example of field that works with objects of type class A.

    Changes to class A

    In the file A.py I have define the attribute x=10 and the method m_a() as followed:

    # file: A.py
    class A:
        x = 10
        def m_a(self):
            return self.x
    

    Test methods in test_A_bundle.py

    At the end I propose you a file test_A_bundle.py which contains 2 methods:

    The code of test_A_bundle.py is the followed:

    #file test_A_bundle.py
    
    import test_A # this is the part I feel weird doing
    import A_bundle
    import unittest
    from unittest import mock
    import A
    
    class TestABundle(unittest.TestCase):
    
        #+++++ YOUR TEST METHOD +++++
        def test_a_bundle(self):
            # create a couple of test objects of type A
            new_A1 = test_A.create_test_A()
            new_A2 = test_A.create_test_A()
    
            # make a new A_bundle to perform tests on (passes 2 instances of 
            # A to the __init__ method of A_bundle)
            new_bundle = A_bundle.A_bundle(new_A1, new_A2)
            self.assertEqual(20, new_bundle.sum())
    
        def test_a_bundle_with_mock(self):
            mock_a1 = mock.Mock()
            mock_a2 = mock.Mock()
            mock_a1.m_a.return_value = 20
            mock_a2.m_a.return_value = 30
    
            new_bundle = A_bundle.A_bundle(mock_a1, mock_a2)
            self.assertEqual(50, new_bundle.sum())
    
    if __name__ == '__main__':
        unittest.main()
    

    The second method shows you how to test the interaction between objects of class A and object of class A_bundle by unit test. On my opinion this test is not an integration test, it remains a unit test with tests the cooperation between classes.