Creating simple unit tests

We'll create some simple unit tests of the Card class hierarchy and the card() factory function.

As the Card classes are so simple, there's no reason for overly sophisticated testing. It's always possible to err on the side of needless complication. An unthinking slog through a test-driven development process can make it seem like we need to write a fairly large number of not very interesting unit tests for a class that only has a few attributes and methods.

It's important to understand that test-driven development is advice, not a natural law like the conservation of mass. Nor is it a ritual that must be followed without thinking.

There are several schools of thought on naming test methods. We'll focus on a style of naming that includes describing a test condition and the expected results. Here are three variations on this theme:

  • StateUnderTest_should_ExpectedBehavior
  • when_StateUnderTest_should_ExpectedBehavior
  • UnitOfWork_StateUnderTest_ExpectedBehavior

For more information, refer to:

 http://osherove.com/blog/2005/4/3/naming-standards-for-unit-tests.html

The StateUnderTest portion of the name is often evident from the class that contains the test, and can be omitted from the method name. This means the individual test cases can emphasize the should_ExpectedBehavior portion of the name. In order to comply with the way the unittest.TestCase class works, each test behavior must begin with test_. This leads us to suggest test_should_ExpectedBehavior as a pattern for test case names when using individual test methods for a unittest.TestCase subclass. For pytest functions, we'll use a different pattern of naming.

It's possible to configure the unittest module to use different patterns for naming the test methods. We could change it to look for when_ instead of test_. The improvement in names doesn't seem to be worth the effort required.

This, for example, is a test of the Card class:

class TestCard(unittest.TestCase):

def setUp(self) -> None:
self.three_clubs = Card(3, Suit.CLUB)

def test_should_returnStr(self) -> None:
self.assertEqual("3♣", str(self.three_clubs))

def test_should_getAttrValues(self) -> None:
self.assertEqual(3, self.three_clubs.rank)
self.assertEqual(Suit.CLUB, self.three_clubs.suit)
self.assertEqual(3, self.three_clubs.hard)
self.assertEqual(3, self.three_clubs.soft)

We defined a test setUp() method that creates an object of the class that is being tested. We also defined two tests on this object. As there's no real interaction here, there's no state under test in the test names; they're simple universal behaviors that should always work.

This is a lot of test code for a very small class definition. This raises a question as to whether or not the volume of test code is in some way excessive. The answer is no; this is not an excessive amount of test code. There's no law that says that there should be more application code than test code. Indeed, it doesn't make sense to compare the volume of test code with the volume of application code. Most importantly, even a tiny class definition can still have bugs, and it may require complex tests to assure the bugs don't exist.

Simply testing the values of attributes doesn't seem to test the processing in this class. There are two perspectives on testing attribute values:

  • The black-box perspective means that we disregard the implementation. In this case, we need to test all of the attributes. The attributes could, for example, be properties, and they must be tested.
  • The white-box perspective means that we can examine the implementation details. When performing this style of testing, we can be a little more circumspect in which attributes we test. The suit attribute, for example, doesn't deserve much testing. The hard and soft attributes, however, do require testing.

For more information, refer to:

 http://en.wikipedia.org/wiki/White-box_testing and http://en.wikipedia.org/wiki/Black-box_testing

Of course, we need to test the rest of the Card class hierarchy. We'll just show you the AceCard test case. The FaceCard test case should be clear after this example:

class TestAceCard(unittest.TestCase):

def setUp(self) -> None:
self.ace_spades = AceCard(1, Suit.SPADE)

@unittest.expectedFailure
def test_should_returnStr(self) -> None:
self.assertEqual("A♠", str(self.ace_spades))

def test_should_getAttrValues(self) -> None:
self.assertEqual(1, self.ace_spades.rank)
self.assertEqual(Suit.SPADE, self.ace_spades.suit)
self.assertEqual(1, self.ace_spades.hard)
self.assertEqual(11, self.ace_spades.soft)

This test case also sets up a particular Card instance so that we can test the string output. It checks the various attributes of this fixed card.

Note that the test_should_returnStr() test will fail. The definition of the AceCard class does not display the value as shown in this test definition. Either the test is incorrect or the class definition is incorrect. The unit test uncovered this fault in the class design.

A similar test is required for the FaceCard class. It will be similar to the test shown for the AceCard class. We won't present it here, but will leave it as an exercise for you.

When we have a number of tests, it can be helpful to combine them into a suite of tests. We'll turn to this next.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset