Unit Test
General Rules
  • A testing unit should focus on one tiny bit of functionality and prove it correct
  • Each test unit must be fully independent
  • Try hard to make tests that run fast
  • Learn your tools and learn how to run a single test or a test case
  • Always run the full test suite before a coding session, and run it again after
  • Runs all tests before pushing code to a shared repository
  • Use long and descriptive names for testing functions
  • Test Outcomes
  • ok, the test passes
  • FAIL, the test does not pass, and raises an AssertionError exception
  • ERROR, the test raises an exception other than AssertionError
  • Layout and pytest
  • Source Code
  • # get source code 
    git clone https://github.com/lin-chen-Langley/prime
    		

    Option 1. pytest
  • Use pytest instead of default test, the default test may ignore some exceptions
  • Implement test (2 options)
  • pytest will search for test_*.py or *_test.py files in test folder, imported by their test package name, collect test_ prefixed test functions or methods as test items
  • test does not install dependencies to system, it install the copies of dependencies to .eggs in the project folder
  • test files must have unique names, the test files will be imported as top-level modules by adding test/ to sys.path
  • developer
    python setup.py test # assure that the source code can pass all tests before is deposited to a repository
    		
    users
    git clone URL
    python setup.py test # make sure that the code can pass all the tests
    python setup.py install # install the program
    
    or
    pip install
    		
    Option 2. tox
    1. configuration, load tox.in, use Python3.9
    2. packaging, create a source distribution
    3. environment, create a virtual environment with virtualenv in .tox folder, install packages and dependencies, run commands
    4. report, print out a report of outcomes for each tox environment
    tox.ini
    # content of: tox.ini , put in same dir as setup.py
    [tox]
    envlist = py39
    
    [testenv]
    # install pytest in the virtualenv where commands will be executed
    deps = pytest
    commands =
        # NOTE: you can run any command line tool here - not just tests
        pytest
    		
    developer
    tox # implement tests
    		
    users
    git clone URL
    tox # make sure that the code can pass all the tests
    python setup.py install # install the program
    
    or
    pip install
    		
    Unit Test Option 1. Unittest
  • test fixture, the preparation needed to perform one or more tests
  • test case, a test case is the smallest unit of testing
  • test suite, a collection of test cases, test suites, or both
  • test runner, orchestrates the execution of tests and provides the outcomes to the user
  • import unittest
    
    # import the tested package
    
    class TestClassName(unittest.TestCase):
    	def setUp():
    		# define instructions that will be executed before each test method
    
    	def tearDown():
    		# define instructions that will be executed before and after each test method
    
    	def test_function():
    		# function name starts with the letters "test"
    		
    test/test_is_prime.py
    #!/usr/bin/python
    
    import unittest
    from primepackage import is_prime
    
    class Test_is_prime(unittest.TestCase):
    
        def setUp(self):
            # Set up database, parameters before each method is tested ...
            self.fixture = 10
    
        def tearDown(self):
            # Tear down database, parameters after each method is tested ...
            del self.fixture
    
        def test_numbers(self):
            self.assertEqual(is_prime(2), True)
            self.assertEqual(is_prime(8), False)
            self.assertEqual(is_prime(1), False)
            self.assertEqual(is_prime(83), True)
            self.assertEqual(is_prime(self.fixture), False)
    
        def test_raises(self):
            with self.assertRaises(ValueError):
                is_prime(0)
                is_prime(3.14)
                is_prime('Hello')
    
    if __name__ == '__main__':
        unittest.main()
    		
    test/test_suit.py
    #!/usr/bin/python
    
    import sys
    import unittest
    from primepackage import is_prime
    
    class Test_is_prime(unittest.TestCase):
    
        def setUp(self):
            # Set up database, parameters before each method is tested ...
            self.fixture = 10
    
        def tearDown(self):
            # Tear down database, parameters after each method is tested ...
            del self.fixture
    
        def test_numbers(self):
            self.assertEqual(is_prime(2), True)
            self.assertEqual(is_prime(8), False)
            self.assertEqual(is_prime(1), False)
            self.assertEqual(is_prime(83), True)
            self.assertEqual(is_prime(self.fixture), False)
    
        def test_raises(self):
            with self.assertRaises(ValueError):
                is_prime(0)
                is_prime(3.14)
                is_prime('Hello')
    
    class Test_more(unittest.TestCase):
        def test_numbers(self):
            self.assertEqual(is_prime(100), False)
    
        def test_raises(self):
            with self.assertRaises(ValueError):
                is_prime(-1)
    
    if __name__ == '__main__':
        suite_1 = unittest.TestLoader().loadTestsFromTestCase(Test_is_prime);
        suite_2 = unittest.TestLoader().loadTestsFromTestCase(Test_more);
        allTests = unittest.TestSuite([suite_1, suite_2]);
        #unittest.TextTestRunner(verbosity=2).run(allTests);
        unittest.main();
    		
    # in /Prime folder
    python setup.py test
    		
    # use test discover to implement multiple test files
    # in /src folder
    python -m unittest discover ../test "test*.py"
    		
    Unit Test Option 2. assert
    test/test_assert.py
    #!/usr/bin/python
     
    from primepackage import is_prime
     
    def test_numbers():
        response = is_prime(1)
        assert response == False
     
        response = is_prime(2)
        assert response == True
    		
    # in /Prime folder
    python setup.py test
    		
    Unit Test Option 3. Nose
    test/test_nose.py
    #!/usr/bin/python
     
    from primepackage import is_prime
    
    def test_is_prime():
        response = is_prime(5)
        assert response == False
    			
    setup.py
    import setuptools
     
    with open('requirements.txt') as f:
        requirements = f.read().splitlines()
     
    setuptools.setup(
        test_suite = 'nose.collector',
        tests_require=['nose'],
        name='linlangleyprime', # a unique name for PyPI
        version='0.7',
        author='Lin Chen, Yanhua Feng',
        author_email='lin.chen@ieee.org, yf@vims.edu',
        description='Demo for building a Python project',
        long_description=open('README.md').read(),
        long_description_content_type="text/markdown",
        url='http://lin-chen-langley.github.io',
        project_urls = {
            'PyPI': 'https://pypi.org/manage/project/linlangleyprime/releases/',
            'Conda': 'https://anaconda.org/linchenVA/linlangleyprime'
            },
        classifiers=[
          'Development Status :: 4 - Beta',
          'Environment :: X11 Applications :: GTK',
          'Intended Audience :: End Users/Desktop',
          'Intended Audience :: Developers',
          'License :: OSI Approved :: GNU General Public License (GPL)',
          'Operating System :: POSIX :: Linux',
          'Programming Language :: Python',
          'Topic :: Desktop Environment',
          'Topic :: Text Processing :: Fonts'
          ],
        install_requires=requirements,
        package_dir={'':'src'}, # location to find the packages
        packages=setuptools.find_packages(where="src"),
        #packages=['primepackage', ], # packages and subpackages containing .py files
        python_requires=">=3.9",
        scripts=['src/generator',], # the executable files will be installed for user
        license='Creative Commons Attribution-Noncommercial-Share Alike license',
    )
    			
    setup.cfg
    [nosetests]
    verbosity=1
    detailed-errors=1
    with-coverage=1
    cover-package=nose
    debug=nose.loader
    			
    # in /Prime folder
    python setup.py test
    			
  • setup_module() | teardown_module(), before and end of the module
  • with_setup(), before and end of the function
  • setup() | teardown(), before and end of each function in the class
  • setup_class() | teardown_class(), before and end of the class
  • Unit Test Option 4. Doctest
    #!/usr/bin/python
    
    def squareTest(x):
        """Return the square of x.
    
        >>> squareTest(2)
        4
        >>> squareTest(-2)
        4
        """
        return x*x;
    
    if __name__ == '__main__':
        import doctest
        doctest.testmod();
    
    			
    python testCode.py -v
    Reference
  • Testing with Nose
  • Nose
  • tox
  • pytest introduction
  • unittest documentation
  • nose introduction
  • Python unittest Fixtures
  • unittest at PYMOTW
  • Hitchhiker’s Guide