Unit testing is a software testing method by which individual units of source code are put under various tests to determine whether they are fit for use (Source). It basically determines and ascertains the quality of your code.
Tests can be Unit Tests, Integration Tests, Load Tests, Stress Tests, and so on. The tests are important to ensure the stability of the system in different scenarios and input conditions.
Unit Testing is unarguably among the most important skills that every developer should have. Any large application with thousands or millions of moving parts cannot be tested for regressions manually.
Here at LinuxAPT, as part of our Server Management Services, we regularly help our Customers perform related Python unit testing queries.
In this context, you shall learn about Unit Testing which is a fully automated way of testing the smallest piece of code also known as a unit.
The main goal here is to break the code on local machines rather than in production.
Facts about Unit Testing in Python:
Unit Testing differs from traditional manual testing in a few ways:
1. It is fully automated. The test cases written are coded and are run automatically or manually. In work, one may notice that the build or deployment pipelines run the tests before merging and/or deploying your code.
2. Tests the smallest unit of code. Often the smallest unit means a method or a function, but it can also be a single line of code.
3. It allows "test-driven development", an agile development strategy to write tests before any amount of code is done and check if the tests pass after development is done.
Advantages of Unit Testing:
1. Improves the quality of code
Writing tests forces us to create functional components which can be easily tested. This practice makes code modular and maintainable.
2. Detects software bugs in early stages
No code is without bugs and a good developer should know which condition can result in a bug. Unit tests can detect scenarios that can be considered rare while manually testing a software, however, in Live such scenarios may occur more often.
3. Edge case scenarios are tested beforehand
Conditions like Integer Overflow, IndexOutOfBound, or even NullPointers can be identified well before the code is deployed in live environments. We should also consider that the code is supposed to throw errors at certain scenarios and unit tests are a good way to test if those errors are being actually thrown.
4. Reduces both cost and development time
Writing extensive unit tests does take a considerable amount of time, however, they pay off, in the long run, considering the benefits they provide. Bugs not caught early in the development stage are skipped to testing and production to spend more time in debugging, bug fixing, and deployment.
5. Simplify the documentation
Unit tests act as documentation on their own for the developer's code. Every class or file for which unit tests are being written covers all different scenarios and reading the unit tests should give enough insight into what the component is trying to achieve.
6. Fortified Deployment and Code Submit Pipelines
Deployment pipelines can be set up to run unit tests before a system is being deployed and even well before deployment, while we submit our code to a version control system (Git, for example). This ensures that the deployments shall not fail and any new feature being added shall not break what is already working.
What is unittest Package in Python ?
Python comes with the unittest package, originally inspired by JUnit.
However, we don’t need to install any packages.
There are certain important concepts which unittest supports like in Junit.
1. test fixture
If one is coming from a Java and Junit background they must be familiar with setUp() and tearDown() used in unit tests. In python, test fixtures do the same thing. They can be used to create temporary directories, database connections, fakes, mocks etc in setUp() and destroy those objects and temp directories and connections in tearDown().
2. test case
A test case is analogous to a class we create for testing a functionality. It checks for the responses returned for a combination of inputs.
3. test suite
While running tests for a code, one may want to group multiple tests under a single umbrella, so that they run together. We can group test cases and/or test suites.
4. test runner
The test runner does the magic of orchestrating the execution of tests. Later in the article you may notice, the outcome rendered can be in textual format and/or a graphical interface.
Next, we will perform some testing:
most commonly used assert methods provided by the TestCase class to check and report for failures:
Method - Checks That
assertEqual(a, b) - a == b
assertNotEqual(a, b) - a != b
assertTrue(x) - bool(x) is True
assertFalse(x) - bool(x) is False
assertIs(a, b) - a is b
assertIsNot(a, b) - a is not b
assertIsNone(x) - x is None
assertIsNotNone(x) - x is not None
assertIn(a, b) - a in b
assertNotIn(a, b) - a not in b
assertIsInstance(a, b) - isinstance(a, b)
assertNotIsInstance(a, b) - not isinstance(a, b)
Let us consider A Basic Example
Here, try to implement a few for testing Calculator.py:
def add(self, a: int, b: int) -> int:
return a + b
def sub(self, a: int, b: int) -> int:
return a - b
def mul(self, a: int, b: int) -> int:
return a * b
def div(self, a: int, b: int) -> float:
return a / b
Calculator_test.py tests our calculator logic.
Notice we consider negative scenarios as well:
from Calculator import Calculator
def setUp(self) -> None:
self.calculator = Calculator()
# Check add(5, 6) gives 11 or not
self.assertEqual(11, self.calculator.add(5, 6),
# Check sub(5, 6) gives -1 or not
self.assertEqual(-1, self.calculator.sub(5, 6),
# This test case will fail as sub(5, 6) is -1 not 10
self.assertEqual(10, self.calculator.sub(5, 6), "Testing Calculator.sub()")
# Run single test multiple times with different parameters
# Parameter Source
params = [
For param in params:
param['a'], param['b']), "Testing Calculator.mul()")
# Check div(40, 2) gives 20 or not
40, 2), "Testing Calculator.div()")
# Check div(20, 40) gives 0.5 or not
self.assertEqual(20/40, self.calculator.div(20, 40),
# Check div(10, 3) gives 3.3333333 or not (rounded upto 7 decimal places)
self.assertAlmostEqual(3.3333333, self.calculator.div(10, 3),
# Check whether Divide by 0 throws exception ZeroDivisionError
self.assertRaises(ZeroDivisionError, self.calculator.div, 20, 0)
if __name__ == "__main__":
A test case inherits unittest.TestCase. setUp() prepares the test fixture and will run before executing each test method. The name of individual test methods needs to start with test_ to be recognized by the test runner.
subTest() context manager is used to distinguish test iterations inside the body of a test method.
But if you are using IDE like Pycharm or VSCode then there is a GUI test runner, which shows success and failures graphically.
In VSCode open the test file, press Shift + P and select Python: Run Current Test File
You should see failed and passed test cases
Best Practices while perforing Unit Testing
There are few best practices we should follow while writing unit tests:
1. Arrange, Act and Assert
i. Arrange: Every test we write, first we arrange our data for the test.
ii. Act: In this stage we act on the method or component we intend to test
iii. Assert: Here we assert the output we receive from our component.
Ensure that there is a line space after each stage of our test and code for every stage can be grouped together.
2. Before and After
Make sure the common code which is required for each and every test should be added in test fixtures. Use setUp() for running logic before every test and tearDown() for logic to be run after tests.
3. Fakes Vs Mocks
Always try to avoid mocking wherever possible and use the actual components themselves. However, if this cannot be avoided, try to use fakes, where we can fake a component, service or some I/O operation. Mocking should be the last resort taken while testing since mocking makes a lot of assumptions which may or may not hold true in real scenarios.
When test methods involve RPC (REST/GraphQL/gRPC) or database queries, we mock the wire calls. But that’s out of the scope of this article and will be covered in future articles.
4. Meaningful Names
Make sure your tests have meaningful names and the reader can guess what is happening just by reading the name of the test.