Python: Test Driven Development

Python: Test Driven Development

Introduction to TDD in Python

Testing Basics - Unit Testing

It is wonderful to say that Python, in its default carries a test library with it. But before we go testing we should answer a question?

does the tested class have any dependencies?

If not then we should do unit testing.

Tests in python are written inside a class without an __init__ method. So say if we had a class like this:

post.py

class Post:
	def __init__(self, title, content):
		self.title = tile
		self.content = content

and we want to implement some functionality with this simple class. As a basic implementation plan let’s imagine it will return a dictionary with its values and a summarize function to summarize itself.

So in the final process the class will look like that:

class Post:
	def __init__(self, title, content):
		self.title = title
		self.content = content
	
	def dictionarize(self):
		pass
	
	def summarize(self):
		pass

Now that we have our schema let’s start to write our tests. Test Driven Development basically says that:

  • imagine the functionality first and write its test with the expected behavior
  • implement the functionality with the most basic code.

Let’s write our test code

*Tests/Unit/post_test.py

from unittest import TestCase
from post import Post


class PostTest(TestCase): # inheriting from TestCase
	def test_basic_initialization(self):
		p = Post("Test","Test Content")

		self.assertEqual("Test",p.title)
		self.assertEqual("Test Content", p.content)
		
	def test_dictionarize(self):
		p = Post("Test","Test Content")
		
		self.assertDictEqual({
			"title":"Test",
			"content":"Test Content"
		}, p.dictionarize())

Now when we run this test via our IDE, we’ll see that the second test fails. Because we didn’t implement the code yet. To implement this in its most basic sense I can copy/paste the whole thing to make it pass:

post.py

def dictionarize(self):
	return {
			"title":"Test",
			"content":"Test Content"
		}

Now, when I run the test it will succeed. But as we all know this is a false-positive. To make it really real I need to change the placeholder values with the real ones:

post.py

def dictionarize(self):
	return {
			"title": self.title,
			"content": self.content
		}

Again, when I run my test it will not fail. And this will give me a solid structure to change my code and be sure that my output didn’t change - so even though the implementation details are changed, I am sure that it is giving me a correct answer. Of course it is better to check it with more than one example:

Tests/Unit/post_test.py

from unittest import TestCase
from post import Post


class PostTest(TestCase): # inheriting from TestCase
	def test_basic_initialization(self):
		p = Post("Test","Test Content")

		self.assertEqual("Test",p.title)
		self.assertEqual("Test Content", p.content)
		
	def test_dictionarize(self):
		p = Post("Test","Test Content")
		p2 = Post("Python","Topic on Testing")

		self.assertDictEqual({
			"title":"Test",
			"content":"Test Content"
		}, p.dictionarize())
		
		self.assertDictEqual(
			{
				'title': 'Python',
				'content': 'Topic on Testing',
			}, p2.dictionarize()
		)

Now let’s also do summarize functionality. First test&expected output; then actual code:

post_test.py

def test_summarize(self):
	p = Post("Test","Test Content")
	
	self.assertEqual("Test - Test Content", p.summarize())

Again running this will fail our test, see it fail. And let’s move to our code with the simplest action to make this test pass, which is giving it directly the expected value:

post.py

def summarize(self):
	return "Test - Test Content"

And finally let’s implement it properly

def summarize(self):
	return "{} - {}".format(self.title, self.content)