How To Run Unit Test Use External CSV Test Data In Python

Python unittest library is used to run the unit tests for Python classes and functions. It is a built-in library provided by Python, so you do not need to install any third-party library to use it.

This example will demo how to use it to make your unit test more clear and easy. But the default unittest module can not create Html and XML reports of the unit test result. So this article also shows you how to use third-party libraries HtmlRunner and xmlrunner to create Html and XML reports for the unit test result.

1. Define Test Case Class In Python Unit Test.

  1. Import the unittest library.
    import unittest
  2. Create a unit test case class that extends the unittest.TestCase.
    class TestCalculator(unittest.TestCase):
        ......
        ......
  3. Implement class level or instance level setup or teardown function. The class-level setup function will be executed before all test functions only once, while the instance-level setup function will be executed before each test function.
  4. Class-level teardown function will be executed after all test functions only once and instance-level teardown function will be executed after each test function.
  5. From the below source code, we can see the class-level setup or teardown function should be annotated with @classmethod annotation.
    class TestCalculator(unittest.TestCase):
    
        # class level setup function, execute once only before any test function.
        @classmethod
        def setUpClass(cls):
            ......
            print('TestCalculator setUpClass')
    
        # class level setup function, execute once only after all test function's execution.
        @classmethod
        def tearDownClass(cls):
            ......
            print('TestCalculator tearDownClass')
    
        # execute before every test case function run.
        def setUp(self):
            ......
            print('TestCalculator setUp')
    
        # execute after every test case function run.
        def tearDown(self):
            ......
            print('TestCalculator tearDown')
  6. Define test function in the above test case class. The test function’s name should start with ‘test_’. And run the target class method which needs to be tested in the test function.
    def test_plus(self):
        ......

2. Run Unit Test Function In Test Suite.

  1. Create an instance of unittest.TestSuite class.
    test_suite = unittest.TestSuite()
  2. Add test case class object with the test function name to the test suite. You can add multiple test case instance with the different test function name.
    test_suite.addTest(TestCalculator('test_plus'))
  3. Create a unittest.TextTestRunner object.
    test_runner = unittest.TextTestRunner()
  4. Use the above test runner to run the test suite.
    test_runner.run(test_suite)
  5. If you want to run all test functions of the test case class, you can invoke unittest.main() function directly.
    unittest.main()

3. Create Python Unit Test Result Html Report.

  1. Open a terminal and install the third-party python library html-Testrunner use pip3.
    $ pip3 install html-Testrunner
  2. Create a unittest.TestSuite object and add test functions to it like above.
  3. Import the HtmlTestRunner module, create an HTMLTestRunner object with a specified output Html file folder, and use it to run the above test suite.
    import HtmlTestRunner
    
    test_runner =HtmlTestRunner.HTMLTestRunner(output='./html_report')
    
    test_runner.run(test_suite)

4. Create Python Unit Test Result XML Report.

  1. Use pip3 to install the third-party python library xmlrunner in a terminal.
    $ pip3 install xmlrunner
  2. Create a unittest.TestSuite object and add test functions to it like above.
  3. Import the xmlrunner module, create an XMLTestRunner object with a specified output XML file folder, and use it to run the above test suite.
  4. Please note the difference between Html and XML report output folders.
  5. The HTMLTestRunner generates the Html report in a subfolder /reports/ by default, so to create both Html and XML reports in the same folder, you can specify the XML report output folder like below.
    import xmlrunner
    
    test_runner = xmlrunner.XMLTestRunner(output='./reports/xml_report')
    
    test_runner.run(test_suite)

5. Python Unit Test Example.

  1. There are three source files in this example.
    D:\WORK\DEV2QA.COM-EXAMPLE-CODE\PYTHONPYCHARMPOJECT\PYTHONUNITTEST
        Calculator.py
        django_test.py
        test.py
        test_data.csv
  2. Calculator.py: The Calculator.Calculator class is defined in Calculator.py. It has four functions that implement two number’s plus, minus, multiple and divide function.
  3. test.py: The TestCalculator class is defined in test.py. It is a unittest.TestCase class’s subclass. It open and load the test data from the test_data.csv file in the class level setup function. It closes the test_data.csv file in the class level teardown function.
  4. It also has four test functions (test_plus, test_minus, test_multiple, test_divide) which test the Calculator.Calculator class’s related functions.
  5. test_data.csv: This is a CSV file that stores test data. One row of text in this file contains six columns separated by a comma.
  6. The first column is the first number x, the second column is the second number y, the third column is the value of x+y, the fourth column is the value of x-y, the fifth column is the value of x*y, the sixth column is the value of x/y.

3.1 Calculator.py

  1. Calculator.py.
    '''
    This class provide 4 function to implement number, plus, minus, multiple and divide operation.
    '''
    class Calculator:
    
        def plus(self, x, y):
            return float(x) + float(y)
    
        def minus(self, x, y):
            return float(x) - float(y)
    
        def multiple(self, x, y):
            return float(x) * float(y)
    
        def divide(self, x, y):
            if int(y) is not 0:
                return float(x) / float(y)
            else:
                return 'error, divisor y can not be zero'

3.2 test.py

  1. test.py.
    import unittest, csv
    import HtmlTestRunner
    import xmlrunner
    
    # the .py file name is Calculator and the class name is also Calculator
    from Calculator import Calculator
    
    # test data file path, the fils is a csv file.
    test_data_file_path = './test_data.csv'
    
    # test data file object
    test_data_file_object = None
    
    # test data row list.
    test_data_row_list = list()
    
    # load test data from ./test_data.csv file.
    def load_test_data():
        global test_data_file_object, test_data_row_list
        # open test data csv file.
        test_data_file_object = open(test_data_file_path, 'r')
        # read the csv file and return the text line list.
        csv_reader = csv.reader(test_data_file_object, delimiter=',')
    
        for row in csv_reader:
            test_data_row_list.append(row)
    
        print('open and load data from test_data.csv complete.')
    
    # close and release the test data file object.
    def close_test_data_file():
        global test_data_file_object
        if test_data_file_object is not None:
            test_data_file_object.close()
            test_data_file_object = None
            print('close file test_data.csv complete.')
    
    '''
    This is the TestCase class that test Calculator class functions.
    '''
    class TestCalculator(unittest.TestCase):
    
        # this is the Calculator class instance.
        calculator = None
    
        # class level setup function, execute once only before any test function.
        @classmethod
        def setUpClass(cls):
            load_test_data()
            print('')
            print('setUpClass')
    
        # class level setup function, execute once only after all test function's execution.
        @classmethod
        def tearDownClass(cls):
            close_test_data_file()
            print('')
            print('tearDownClass')
    
        # execute before every test case function run.
        def setUp(self):
            self.calculator = Calculator()
            print('')
            print('setUp')
    
        # execute after every test case function run.
        def tearDown(self):
            # release the Calculator object.
            if self.calculator is not None:
                self.calculator = None
            print('')
            print('tearDown')
    
    
        # below are function that test Calculator class's plus, minus, multiple and divide functions.
        def test_plus(self):
            print('')
            print('******test_plus******')
            # get each row text from the csv file.
            for row in test_data_row_list:
                # the first column in the text line is x value.
                x = row[0]
                # the second column in the text line is y value.
                y = row[1]
                # the third column in the text line is (x + y) value.
                expect_result = row[2]
                result = self.calculator.plus(x, y)
    
                print(str(x) + ' + ' + str(y) + ' = ' + str(result) + ', expect ' + str(expect_result))
                self.assertEqual(float(result), float(expect_result))
    
        def test_minus(self):
            print('')
            print('******test_minus******')
            for row in test_data_row_list:
                x = row[0]
                y = row[1]
                # the fourth column in the text line is (x - y) value.
                expect_result = row[3]
                result = self.calculator.minus(x, y)
    
                print(str(x) + ' - ' + str(y) + ' = ' + str(result) + ', expect ' + str(expect_result))
                self.assertEqual(float(result), float(expect_result))
    
        def test_multiple(self):
            print('')
            print('******test_multiple******')
            for row in test_data_row_list:
                x = row[0]
                y = row[1]
                # the fifth column in the text line is (x * y) value.
                expect_result = row[4]
                result = self.calculator.multiple(x, y)
    
                print(str(x) + ' * ' + str(y) + ' = ' + str(result) + ', expect ' + str(expect_result))
                self.assertEqual(float(result), float(expect_result))
    
        def test_divide(self):
            print('')
            print('******test_divide******')
            for row in test_data_row_list:
                x = row[0]
                y = row[1]
                # the sixth column in the text line is (x / y) value.
                expect_result = row[5]
                result = self.calculator.divide(x, y)
    
                print(str(x) + ' % ' + str(y) + ' = ' + str(result) + ', expect ' + str(expect_result))
                self.assertEqual(float(result), float(expect_result))
    
    
    def build_test_suite():
        # create unittest.TestSuite object.
        test_suite = unittest.TestSuite()
        # add each test function to the test suite object.
        test_suite.addTest(TestCalculator('test_plus'))
        test_suite.addTest(TestCalculator('test_minus'))
        test_suite.addTest(TestCalculator('test_multiple'))
        test_suite.addTest(TestCalculator('test_divide'))
        return test_suite
    
    def build_text_report():
        test_suite = build_test_suite()
        # create unittest.TextTestRunner() object.
        test_runner = unittest.TextTestRunner()
        # run the test suite.
        test_runner.run(test_suite)
    
        # run below code to run all test function
        # unittest.main()
    
    
    # generate html report.
    def build_html_report():
        test_suite = build_test_suite()
        test_runner = HtmlTestRunner.HTMLTestRunner(output='./html_report')
        test_runner.run(test_suite)
    
    # generate xml result.
    def build_xml_report():
        test_suite = build_test_suite()
        test_runner = xmlrunner.XMLTestRunner(output='./reports/xml_report')
        test_runner.run(test_suite)
    
    if __name__ == "__main__":
    
        build_text_report()
    
        build_html_report()
    
        build_xml_report()

3.3 test_data.csv

  1. test_data.csv.
    1,1,2,0,1,1.0
    1,2,3,-1,2,0.5
  2. Open a terminal and run python3 test.pythen you will get below execution result output in the terminal.
    open and load data from test_data.csv complete.
    
    setUpClass
    setUp
    
    ******test_divide******
    1 % 1 = 1.0, expect 1.0
    1 % 2 = 0.5, expect 0.5
    
    tearDown
    
    setUp
    
    ******test_minus******
    1 - 1 = 0.0, expect 0
    1 - 2 = -1.0, expect -1
    
    tearDown
    
    setUp
    
    ******test_multiple******
    1 * 1 = 1.0, expect 1
    1 * 2 = 2.0, expect 2
    
    tearDown
    
    setUp
    
    ******test_plus******
    1 + 1 = 2.0, expect 2
    1 + 2 = 3.0, expect 3
    
    tearDown
    close file test_data.csv complete.
    
    tearDownClass
  3. Below are generated Html and XML report files in the Pycharm project.
    reports
       |
       |---html_report
       |      |
       |      |---Test_TestCalculator_2021-02-12_20-23-02.html
       |---xml_report
       |      |
       |      |---TEST-TestCalculator-20210212202302.xml

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.