With "units" respectively "components" in OOP refer to methods and/or functions. What does this mean?

Every method/function has to be written and tested by the developer and they did it for a long time manually. In the meantime, testing can be automated: For this purpose unit tests are written for each method. The number of unit tests depends on the number of code pathes in the method. Each code path should be tested.

Oops! That sounds like a lot of work. Why should we do this?


Save time

Of course, additional code has to be written for the tests first. But these are quickly created.

First we will write some unit tests for the Solution: Inheritance of "Student" and integration into the program

Zipped project directory (testStudent.py was renamed in printStudent.py because of reserved name fragment "test".)

Remember, we manually tested the functionality by running the following programme:

testStudent.py renamed in printStudent.py

 1  
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from School import *                        # Import classes
from User import *
        
schoolClass1 = SchoolClass("c1",[])         
schoolClass2 = SchoolClass("c2",[])

student1 = Student("Miller","Sam","22.11.2004",schoolClass1)  # additional argument of type SchoolClass
student2 = Student("Smith","Jake","13.02.2003",schoolClass1)
student3 = Student("Hampton","Oliver","11.01.2004",schoolClass2)

school = School([schoolClass1, schoolClass2]) 

schoolClasses = school.getSchoolClasses()  
for sc in schoolClasses:
    print(f"School class: {sc.getName()}") 
    Students = sc.getStudents()
    for student in Students:    # student now knows school class
        print(f"\t{student.getSurname()}, {student.getFirstname()} in school class {student.getSchoolClass().getName()}")

This code has to be replaced by automatically executed unit tests.


All you have to do in essence is calling the methods with the appropriate parameters and check for the correct return values:

 1  
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# filename: testStudent.py 
# It makes sense that the name contains 'test' in order to be recognised by the framework as a test file.

import unittest                                     # import unit tests

from School import *                                # Import classes
from User import *
        

class TestStudent(unittest.TestCase):
    @classmethod                                    # Annotation marks setUpClass() as a test method 
    def setUpClass(self):                           # is executed once for all tests    
        self.schoolClass1 = SchoolClass("c1",[])         
        self.schoolClass2 = SchoolClass("c2",[])

        self.student1 = Student("Miller","Sam","22.11.2004",self.schoolClass1)  
        self.student2 = Student("Smith","Jake","13.02.2003",self.schoolClass1)
        self.student3 = Student("Hampton","Oliver","11.01.2004",self.schoolClass2)

        self.school = School([self.schoolClass1, self.schoolClass2]) 

    def test_School(self):                          # test names must start with 'test_' otherwise will not executed
        schoolClasses = self.school.getSchoolClasses()
        self.assertEqual(len(schoolClasses), 2, 'Number of school classes wrong.')
        self.assertEqual(schoolClasses[0].getName(), "c1", '1st school class wrong.')
        self.assertEqual(schoolClasses[1].getName(), "c2", '2nd school class wrong.')

    def test_Schoolclass(self):
        students = self.schoolClass1.getStudents()
        self.assertEqual(len(students), 2, 'Number of students wrong.')
        self.assertEqual(students[0].getUsername(),'Miller', 'Students username wrong.')
        # ... 

if __name__ == '__main__':
    unittest.main()

Testclass with 2 unit tests - assertEqual() compares return value with expected value


But now the tests run automatically in a short time and even with small projects you don't have to constantly manually operate any buttons in the program interface and laboriously interpret the result.

Test results in IntelliJ IDEA

Test results in Visual Studio Code


Side effects can also easily occur unnoticed during refectorings or maintenance work. This means that an error occurs at a different point in the code, since normally not the whole program is tried out manually every time, but only the changed functionality.
The error is noticed late, maybe even at the customer. Now the connection between the change and the new error has to be determined laboriously in order to eliminate it.
In contrast, all unit tests run completely after each refactoring, the whole program is tested automatically, side effects are noticed immediately and can easily be associated with the change.


High quality of code

Unit testing improves the quality of the code. It identifies every defect that may have come up before code is sent further for integration testing. Writing tests makes developers think harder about the problem and makes them write better code.


Code documentation

Unit testing provides documentation of the system. Developers inspect unit tests for getting an overview about the functionality of classes and how to use it.