Unittest Research - bounswe/bounswe2022group7 GitHub Wiki

Unit tests are usually written before integration tests. They are usually written by the developer. Text fixture encapsulates preparations needed to perform tests and following cleanup actions.

Initial information on using in local:

  • unittest is a standard module which you don't need to install if you have python installed.
  • Here is a useful link to build and run a unittest.

Unittest

Command-Line Interface

python -m unittest test_module1 test_module2 		---> used for testing from modules

python -m unittest test_module.TestClass 		---> used for testing from classes

python -m unittest test_module.TestClass.test_method 	---> used for testing from methods

python -m unittest tests/test_something.py 		---> used for testing modules with specific paths

Flags and options

python -m unittest –h 	---> gives list of command-line options

-v flag 		---> higher verbosity for tests

-b, --buffer 		---> output for passed test is discarded from buffer and is normally echoed on test fail or error.

-c, --catch 		---> single CTRL-C during rest run waits for the current unit test case to finish and then reports all the results so far.

-f, --failfast 		---> stops test run on first error/failure

-k ---> case sensitive pattern matching for given input substring of method name

--locals ---> shows local variables in tracebacks

Test Discovery

It runs the test modules or test packages importable from the top-level directory of the project.

CMD call:

cd project_directory
python -m unittest discover

Code call:

TestLoader.discover()

Options:

-v, --verbose
-s, --start-directory <directory> 	directory to start discovery 	default: .
-p, --pattern <pattern>  		pattern to match test files   	default: test*.py
-t, --top-level-directory <directory>	top level directory of project	default: start directory

Package name can be passed as well as path as start directory.

For a globally installed package, if there is another copy of it you call, attempt to test discovery could happen from the wrong place. For this situation, test discovery will exit with a warning. As for supplying start directory as a package name rather than path, no such problem occurs and test discovery assumes it did a correct import.

Organizing Test Code

The basic building blocks of unit testing are test cases which are represented by unittest. TestCase instances in our case. To make your own test cases you must write subclasses of TestCase.

We can organize repetitive setup in test cases below a method setup() which will automatically be called before every test we run. Exception in setUp() will prevent execution of test method. Any exception from its call be considered as error, not failure.

We can organize repetitive tidy up with tearDown() after test method has been run. Executed even if the method call before it ended with exception, but an error from latest-called setup() method will prevent it from executing.

Any exception from these two method calls are considered as error, not failure.

TestSuite is for customized organization of TestCase methods in class.

image

It is advantageous to place test code in a separate module from this above regulating module.

Re-using Old Test Code

FunctionTestCase(testFunc, setUp = None, tearDown = None, description = None)

PS: Not a recommended use.

Skipping Tests and Expected Failures

Below methods are for skipping classes and methods in different ways. The 'reason' can be observed if tests are called with –v.

Terminology: 'reason' is a string describing the reason the method/class is being skipped.

An example orientation of method placement in code:

image
@unittest.skip(<reason>)			---> unconditionally skip the test

@unittest.skipIf(<condition>, <reason>)		---> skip test if condition is true

@unittest.skipUnless(<condition>, <reason>)	---> skip test unless condition is true

@unittest.expectedFailure			---> marks test as an expected failure or error. If fail/error occurs in function itself rather than in its calls to exterior conten, then it will be considered success. If test passes, it will be failure (briefly they are used to write methods that we want to see behave erroneous).

A useful example usage of skip():

Test skipped unless object passed has certain attribute.

image

Distinguishing test iterations using subtests

If we have minor parameter changing tests, we can distinguish them by subTest() context manager. Thus we can extract more verbose output and also prevent the test that is running from halting in case of the sequential calls popping up erroneous output. See here for an example usage.

Classes and Functions

  • class TestCase(methodName='runTest'):

Has three groups of methods: 1. used to run tests, 2. used to check conditions and report failures, 3. inquiry methods allowing information about the test itself to be gathered.

Methods for this class with detailed explanation can be found here. There are basically assertion methods and some other methods to customize the tests.

PS: setUp and tearDown are run once for all individual class methods while setUpClass and tearDownClass are run once for the whole class.

classmethod addClassCleanup(function, /, *args, **keyWordArgs)

Adds to a stack function to be called after tearDownClass() to be called in a LIFO fashion for resource cleanup purposes.

classmethod doClassCleanups()

Method called by default after tearDownClass method or after erroneous ending of setUpClass method. Calls functions from the stack created with addClassCleanup method. If you want to change default place the function is called, you can explicitely call the function where you want it to be called.

  • class IsolatedAsyncioTestCase(methodName='runTest')

Class is similar to the previous class except that this accepts coroutines as test functions.

For methods and coroutines, see this link.

  • class FunctionTestCase(testFunc, setUp=None, tearDown=None, description=None)

Too detailed and I think won’t be for our use. See here for explanation.

  • class TestSuite(tests=())

Represents a collection of test cases and suites. When we run a TestSuite instance, it iterates over the suite to test all tests one by one. The argument (if given) tests is an iterable of individual test cases or another test suite. They are used at the initiation of suite. There are methods to add test cases and suites to this collection later. TestSuite objects don’t implement actual tests, they only aggregate tests and organize them. For the methods of this class, see here.

There are some deprecated versions of methods, and if we are to use older versions of unittest, check here for examples of such methods and their current equivalents.

Loading and running tests

  • class TestLoader

Used to create test suites from classes and modules.

There is a default instance, unittest.defaultTestLoader but an instance of this class can be utilized for customized configurations. Check here for attributes and methods of this class.

  • class TestResult

The object stores results of a set of tests. TestCase and TestSuite classes ensure a proper recording of test results.

TestRunner.run() call returns a TestResult instance.

For attributes and methods of the class, see here.

For code examples, see here.

  • class TextTestResult(stream, descriptions, verbosity)

Concrete implementation of TestResult used by TextTestRunner

  • *class unittest.TextTestRunner(stream=None, descriptions=True, verbosity=1, failfast=False, buffer=False, resultclass=None, warnings=None, , tb_locals=False)

Class outputs results to a stream. If stream is None, sys.stderr is used as the output stream.

For attributes and methods of the class, see here.

For code examples, see here.

  • unittest.main(module='main', defaultTest=None, argv=None, testRunner=None, testLoader=unittest.defaultTestLoader, exit=True, verbosity=1, failfast=None, catchbreak=None, buffer=None, warnings=None)

CMD program loading tests from module and running them. See information on arguments passed to this method in here.

It is usually used with appending the below code to the test script. “verbosity = 2” can be omitted.

image

Load_tests protocol

Used to customize tests’ loading format during normal test runs or test discovery, it can be used by modules or packages and is called by TestLoader.loadTestsFromModule(). It should return a TestSuite.

Pattern defaults to None and passed directly from loadTestsFromModule.

Loader is the instance of TestLoader. Standart_tests are tests will be loaded by default. Pattern is used when loading packages as part of test discovery. An example and more information can be found here.

Classes and Module Fixtures

They are implemented in TestSuite. If test suite encounters a test from a new class, the function tearDownClass() is called from the previous class if there is one. After that setUpClass() is called from the new class. This process is the same in modules with tearDownModule and setUpModule.

Shared fixtures should be used carefully since they break test isolation when they are used with some features such as parallelization. They are also not intended to work with non-standard ordered suites.

If there are any exceptions raised during one of the shared fixture functions the test is reported as an error.

  • setUpClass and tearDownClass

They must be implemented as class methods:

image

If these two classes should be called on base classes, then they must be called up by the coder.

If an exception raises in setUpClass, it blocks running of tests in the class and tearDownClass is also not run. Skipped classes also will not have setUpClass or tearDownClass run. If the exception is a SkipTest exception, the class will be reported as having been skipped instead of as an error.

setUpModule and tearDownModule

These should be implemented as functions:

image

If an exception raises in setUpModule, it blocks running of tests in the module and tearDownModule is also not run. If the exception is a SkipTest exception, the class will be reported as having been skipped instead of as an error. If it is needed the code to run even in the case of an exception, use:

unittest.addModuleCleanup(function, /, * args, ** kwargs )

Add a function to be called after tearDownModule() to cleanup resources used during test. Functions will be called in reverse order (LIFO).

If setUpModule() fails, so tearDownModule() is not called, then any cleanup functions added will still be called.

unittest.doModuleCleanups()

This function is called unconditionally after tearDownModule() or after setUpModule() if setUpModule() raises an exception.

This function is responsible for calling cleanup functions added by addModuleCleanup(). If cleanup functions are needed to be called prior to tearDownModule(), this function should be called by the coder.

Signal Handling

The -c/--catch command-line option to unittest, along with catchbreak parameter to unittest.main(), provide more friendly handling of control-C during a test run. If catch break is enabled control-C will allow the currently running test to complete, and test run will report all the results so far. A second control-c will raise a KeyboardInterrupt in the usual way. For individual tests that need unittest control-c handling disabled the removeHandler() can be used.

You can check here the utility functions to enable control-c handling functionality within test frameworks.

RESOURCES:

https://docs.python.org/3/library/unittest.html

https://www.guru99.com/unit-testing-guide.html

⚠️ **GitHub.com Fallback** ⚠️