Unit testing is a testing technique in which individual modules are tested to determine if there are any issues by the developer himself.
It is concerned with functional correctness of the stand-alone modules.
The main aim is to isolate each unit of the system to identify, analyze and fix the defects.
These units are typicallly functions and methods.
Benefits of Unit Testing
Developers can work in a predictable way on developing code.
Developers can write their own unit tests.
You can get a rapid response for testing small changes
Also:
Reduces defects in the newly developed features or reduces bugs when changing the existing functionality.
Reduces cost of testing, since defects are captured in very early phase.
Improves design and allows better refactoring of code.
Testing in Python is a huge topic and can come with a lot of complexity, but it doesn’t need to be hard. You can get started creating simple tests for your application in a few easy steps and then build on it from there.
The unittest Framework
One of the popular unit testing frameworks is Unittest. It is works well and is easy to use.
The Unittest framework provides you with a bunch of assert methods, which are essentially wrappers around Python’s built-in assert function.
The basic idea is to write functions that test other functions by using these assert methods instead of peppering your code with them.
Unittest provides many assert methods – see this cheat sheet for more.
We will focus on three: * assertTrue() * assertFalse() * assertEqual()
The Basic Pattern
The Unittest framework works as follows:
Choose on a method or class that you want to test.
Create a class that is a subclass of unittest.TestCase.
In that class write methods that are designed to test the behavior of methods in the code you want to test.
These test methods focus on one behavior of one method (or function).
There can be many test methods for each target method.
Each test method name must be prefixed by test_.
Tests are executing in alphabetical order, so name them in the order you want them executed.
Each test makes use of an assert method. These methods typically compare expected with actual methods and return False if they don’t match and True if they do.
You always want tests to pass, so if you want to test if something breaks, you return True for a False condition.
Run the script and see the results.
Update the script as you create new methods or refactor existing ones.
Assert Methods
.assertTrue()
Negative Test Case
Run M08-02-script1.py
class TestStringMethods(unittest.TestCase):# test functiondef test_negative(self): testValue =False# error message in case if test case got failed message ="Test value is not true."# assertTrue() to check true of test valueself.assertTrue(testValue, message)if__name__=='__main__': unittest.main()
!python M08-02-script1.py
test_negative (__main__.TestStringMethods.test_negative) ... FAIL
======================================================================
FAIL: test_negative (__main__.TestStringMethods.test_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/rca2t1/Dropbox/Courses/DS/DS5100/DS5100-2023-07-R/repo/notebooks/M08_PythonTesting/M08-02-script1.py", line 14, in test_negative
self.assertTrue(testValue, message)
AssertionError: False is not true : Test value is not true.
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Positive Test Case
import unittestclass TestStringMethods(unittest.TestCase):# test functiondef test_positive(self): testValue =True# error message in case if test case got failed message ="Test value is not true."# assertTrue() to check true of test valueself.assertTrue( testValue, message)if__name__=='__main__': unittest.main()
!python M08-02-script2.py
test_positive (__main__.TestStringMethods.test_positive) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
.assertFalse()
Negative Test Case
import unittestclass TestStringMethods(unittest.TestCase):# test functiondef test_negative(self): testValue =True# error message in case if test case got failed message ="Test value is not false."# assetFalse() to check test value as falseself.assertFalse( testValue, message)if__name__=='__main__': unittest.main()
!python M08-02-script3.py
F
======================================================================
FAIL: test_negative (__main__.TestStringMethods.test_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/rca2t1/Dropbox/Courses/DS/DS5100/DS5100-2023-07-R/repo/notebooks/M08_PythonTesting/M08-02-script3.py", line 10, in test_negative
self.assertFalse( testValue, message)
AssertionError: True is not false : Test value is not false.
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Positive Test Case
## unit test caseimport unittestclass TestStringMethods(unittest.TestCase):# test functiondef test_positive(self): testValue =False# error message in case if test case got failed message ="Test value is not false."# assertFalse() to check test value as falseself.assertFalse( testValue, message)if__name__=='__main__': unittest.main()
!python M08-02-script4.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
.assertEqual()
Here is a case where we expect two values to be equal.
Negative Test Case
## unit test caseimport unittestclass TestStringMethods(unittest.TestCase):# test function to test equality of two valuedef test_negative(self): firstValue ="geeks" secondValue ="gfg"# error message in case if test case got failed message ="First value and second value are not equal !"# assertEqual() to check equality of first & second valueself.assertEqual(firstValue, secondValue, message)if__name__=='__main__': unittest.main()
!python M08-02-script5.py
F
======================================================================
FAIL: test_negative (__main__.TestStringMethods.test_negative)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/rca2t1/Dropbox/Courses/DS/DS5100/DS5100-2023-07-R/repo/notebooks/M08_PythonTesting/M08-02-script5.py", line 12, in test_negative
self.assertEqual(firstValue, secondValue, message)
AssertionError: 'geeks' != 'gfg'
- geeks
+ gfg
: First value and second value are not equal !
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
Positive Test Case
## unit test caseimport unittestclass TestStringMethods(unittest.TestCase):# test function to test equality of two valuedef test_positive(self): firstValue ="geeks" secondValue ="geeks"# error message in case if test case got failed message ="First value and second value are not equal !"# assertEqual() to check equality of first & second valueself.assertEqual(firstValue, secondValue, message)if__name__=='__main__': unittest.main(verbosity=2)
!python M08-02-script6.py
test_positive (__main__.TestStringMethods.test_positive) ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Example with User-defined Function
Function to test
def add_fish_to_aquarium(fish_list):iflen(fish_list) >10:raiseValueError("A maximum of 10 fish can be added to the aquarium")return {"tank_a": fish_list}import unittest
Class to test the function
class TestAddFishToAquarium(unittest.TestCase):def test_add_fish_to_aquarium_success(self): actual = add_fish_to_aquarium(fish_list=["shark", "tuna"]) expected = {"tank_a": ["shark", "tuna"]}self.assertEqual(actual, expected)def test_add_fish_to_aquarium_exception(self): too_many_fish = ["shark"] *25withself.assertRaises(ValueError) as exception_context: add_fish_to_aquarium(fish_list=too_many_fish)self.assertEqual(str(exception_context.exception),"A maximum of 10 fish can be added to the aquarium" )if__name__=='__main__': unittest.main(verbosity=2)
!python M08-02-script7.py
test_add_fish_to_aquarium_exception (__main__.TestAddFishToAquarium.test_add_fish_to_aquarium_exception) ... ok
test_add_fish_to_aquarium_success (__main__.TestAddFishToAquarium.test_add_fish_to_aquarium_success) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
Example with External Class
We create a class called Student and save it in a local file called student.py.
class Student:# constructordef__init__(self, name, courses=None):self.name = name # string typeself.courses = [] if courses isNoneelse courses # list of stringsself.num_courses =len(self.courses)# enroll in a coursedef enroll_in_course(self, course_name): self.courses.append(course_name)self.num_courses +=1# increment the number of courses
Then we create a companion test file for our class, saving it in a file called student_test.py.
from student import Studentimport unittestclass EnrollInTestCase(unittest.TestCase): def test_is_incremented_correctly(self):# test if enrollInCourse() method successfully increments the# num_courses attribute of the Student object # Create student instance, adding some courses student1 = Student('Katherine', ['DS 5100']) student1.enroll_in_course("CS 5050") student1.enroll_in_course("CS 5777")print(student1.courses)print(student1.num_courses)# Test expected =3# unittest.TestCase brings in the assertEqual() methodself.assertEqual(student1.num_courses, expected)if__name__=='__main__': unittest.main(verbosity=2)
!python student_test.py
test_incremented_correctly (__main__.EnrollInCourseTest.test_incremented_correctly)
Test if enroll_in_course() method successfully increments the ... ['DS 5100', 'CS 5050', 'CS 5777']
3
ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
The messages that unittest prints are error messages on Unix, so if we want to direct them to a file, we need to use 2>.
Notice how this command only shows the print messages contained in the program.
!python student_test.py 2> student_results.txt
['DS 5100', 'CS 5050', 'CS 5777']
3
This one, on the other hand, captures the print methods and only shows the errors.
!python student_test.py > student_results1.txt
test_incremented_correctly (__main__.EnrollInCourseTest.test_incremented_correctly)
Test if enroll_in_course() method successfully increments the ... ok
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK