Hello community. I've been in the IT field for decade or so and am now dipping my toes into the 'deep-end' of the field. Perhaps I should use 'deeper', as depth may somewhat be a matter of perspective. I expect many of you have a group you can confidently point to and declare them as the real nuts of the bunch.
I came across a thorough guide on testing code using unittest
and pytest
. I've condensed it into concise take-ways. In this article I focus on how to use unittest
and how to understand the sometimes confusing results.
Software Testing is helpful to identify bugs and inconsistencies in code that can cause errors. The ISTQB breaks software testing into 4 levels:
- Unit Testing - tests specific lines of code
- Integration Testing - tests integration between many units
- Systems Testing - tests entire system
- Acceptance Testing - checks compliance with acceptance criteria
It's critical to note that just because a test passes, doesn't mean that the software is entirely bug-free. (If only it were that easy!)
Unit Testing verifies correct behavior of a 'unit' which may be a class, function, or method depending on the language. Unit testing is a low-level process which can identify bugs at local level before they propagate to other levels of the software system. Testing many units individually has several benefits: It eases identifying and locating integration problems. Bugs resulting from future modifications can be easier to location (provided good test history and documentation). Repeatedly testing the relationship of unit input and output provides documentation (results) prior to deployment.
Additional Qualities of a Unit Test:
- Fast (in most cases)
- Isolated - testing of individual units doesn't rely on external processes (like network resources, files, etc)
- Repeatable - Many tests can confirm consistency of results
- Naming - The name of a unit test should clarify the tests purpose
A common Organization Strategy for writing unit tests is the Arrange, Act, and Assert (AAA) pattern.
- Arrange - set objects and variables
- Act - call the unit being tested (function/class/method)
- Assert - declare expected outcome
Having a uniform strategy like AAA will ensure your tests are clean and easy to read.
Unit Testing in Python is primarily performed with unittest
and pytest
.
unittest
is included in the standard Python library and contains:
- test case - tests a single unit
- test suite - group of tests that run together
- test runner - component that handles the execution and results of all test cases
unittest
is written as methods of a class that subclasses unittest.TestCase
and can use special assertion methods.
The guide uses the Example of a bank account that is designed to not allow withdraws greater than the current balance:
import unittest
class BankAccount:
def __init__(self, id):
self.id = id
self.balance = 0
def withdraw(self, amount):
if self.balance >= amount:
self.balance -= amount
return True
return False
def deposit(self, amount):
self.balance += amount
return True
A new test case is made in the same python file by creating class TestBankOperations
that's a subclass of unittest.TestCase
.
class TestBankOperations(unittest.TestCase):
def test_insufficient_deposit(self):
# Arrange
a = BankAccount(1)
a.deposit(100)
# Act
outcome = a.withdraw(200)
# Assert
self.assertFalse(outcome)
Notice how the AAA method is used to Arrange (establish variables), Act (call unit to be tested), and Assert (verify outcome). Also note how the test method test_insufficient_deposit
begins with test
. This is important because every test method must start with test
.
Ideally this test returns the result false
. This is because we asked the test to deposit 100 and try to withdraw 200. To Assert the result, the example uses the special assertion method assertFalse()
. Put more simply - we expect that depositing 100 and withdrawing 200 will fail. By using the special assertion method, we are declaring that the test will have passed if test_insufficient_deposit
fails.
We Run The Test by opening the command line and running
python -m unittest example.py
(example.py
being the file containing the source code we are testing.)
Results should look like:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
This indicates that our test passed and the attempt to withdraw in excess of balance failed.
We can generate a Failed Test for this example by creating another unit test that tries to deposit a negative amount to our account:
def test_negative_deposit(self):
# Arrange
a = BankAccount(1)
# Act
outcome = a.deposit(-100)
# Assert
self.assertFalse(outcome)
To get more thorough results from the failed test we add -v
(verbose) to unittest
in the command line. We run unit test, which will now run both the insufficient deposit test and the negative deposit test.
python -m unittest -v example.py
And our result:
test_insufficient_deposit (example.TestBankOperations) ... ok
test_negative_deposit (example.TestBankOperations) ... FAIL
======================================================================
FAIL: test_negative_deposit (example.TestBankOperations)
----------------------------------------------------------------------
Traceback (most recent call last):
File "example.py", line 35, in test_negative_deposit
self.assertFalse(outcome)
AssertionError: True is not false
----------------------------------------------------------------------
Ran 2 tests in 0.002s
FAILED (failures=1)
We see that our second test failed in line 35. In self.assertFalse(outcome)
we asserted that our test should return false, as we don't want our program accepting negative deposits. However the outcome was true (successful) and therefore our test failed.
We can use this information to determine that we may need to modify the code if refusing negative deposits is one of the design requirements for our program.
unittest
has Different Assertion Methods based on your needs
-
assertEqual(x,y)
- testsx==y
-
assertRaises(exception_type)
- checks if exceptions are raised -
assertIsNone(x)
- tests ifx is None
-
assertIn(x,y)
- tests ifx in y
To Expand Functionality of unit testing in Python, it is recommended to use the pytest
library (non-native), which allows for more complex testing using less code, supports the native unittest
suites and offers more than 800 external plugins.
If you have made it this far, thanks for reading my post. I hope I was able to concisely explain and demonstrate the value of unittest
and help some beginners make more sense of somewhat confusing results (like assertfalse
failed true is not false) indicate. Credit for code examples goes to Lorenzo Bonannella in his very thorough guide of testing using unittest
and pytest
.
Top comments (0)