unittest Python
Disclaimer: This post has been translated to English using a machine translation model. Please, let me know if you find any mistakes.
To create this post, we are going to create a folder called testing_python
where we will create all the code.
!mkdir testing_python
Inside that folder we are going to create the src
and tests
folders where we will put the source code and the respective tests.
!mkdir testing_python/src!mkdir testing_python/tests
Necessary Libraries
To run the tests we are going to use the unittest
library that comes by default with Python, but we will also install coverage
to see the test coverage. We install it with Conda
conda install conda-forge::coverage```
Or with pip
```bash
pip install coverage```
First tests
Let's create a first file called calculator.py
in the src
folder
!echo "def sum(a, b):" > testing_python/src/calculator.py!echo " return a + b" >> testing_python/src/calculator.py!echo "" >> testing_python/src/calculator.py!echo "def substract(a, b):" >> testing_python/src/calculator.py!echo " return a - b" >> testing_python/src/calculator.py!echo "" >> testing_python/src/calculator.py!echo "def multiply(a, b):" >> testing_python/src/calculator.py!echo " return a * b" >> testing_python/src/calculator.py!echo "" >> testing_python/src/calculator.py!echo "def divide(a, b):" >> testing_python/src/calculator.py!echo " return a / b" >> testing_python/src/calculator.py
Now we create a file called test_calculator.py
in the tests
folder
!echo "import unittest" > testing_python/tests/test_calculator.py!echo "from src.calculator import sum, substract, multiply, divide" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo "class TestCalculator(unittest.TestCase):" >> testing_python/tests/test_calculator.py!echo " def test_sum(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(sum(2, 2), 4)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_substract(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(substract(2, 1), 1)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_multiply(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(multiply(2, 3), 6)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_divide(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(divide(6, 3), 2)" >> testing_python/tests/test_calculator.py
Now to run it we do python -m unittest tests/test_calculator.py discover -s tests
!cd testing_python && python -m unittest tests/test_calculator.py
....----------------------------------------------------------------------Ran 4 tests in 0.000sOK
As we can see, four dots appear for the two tests that have been passed and were correct.
Let's modify the test file to cause an error, we're going to make it so that adding 2 and 2 gives us 5.
!echo "import unittest" > testing_python/tests/test_calculator.py!echo "from src.calculator import sum, substract, multiply, divide" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo "class TestCalculator(unittest.TestCase):" >> testing_python/tests/test_calculator.py!echo " def test_sum(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(sum(2, 2), 5)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_substract(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(substract(2, 1), 1)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_multiply(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(multiply(2, 3), 6)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_divide(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(divide(6, 3), 2)" >> testing_python/tests/test_calculator.py
Now we run the tests
!cd testing_python && python -m unittest tests/test_calculator.py
...F======================================================================FAIL: test_sum (tests.test_calculator.TestCalculator)----------------------------------------------------------------------Traceback (most recent call last):File "/home/wallabot/Documentos/web/portafolio/posts/testing_python/tests/test_calculator.py", line 6, in test_sumself.assertEqual(sum(2, 2), 5)AssertionError: 4 != 5----------------------------------------------------------------------Ran 4 tests in 0.000sFAILED (failures=1)
As we can see, now we get an F
which means that a test has failed, in addition, it provides us with the following information
FAIL: test_sum (tests.test_calculator.TestCalculator)----------------------------------------------------------------------Traceback (most recent call last):File "/home/wallabot/Documents/web/portfolio/posts/testing_python/tests/test_calculator.py", line 6, in test_sumself.assertEqual(sum(2, 2), 5)AssertionError: 4 != 5```
It's telling us that the test `test_sum` has failed on line 6, which is the one we modified, and that the expected result was 5 but the obtained result was 4.
One thing we haven't mentioned and is important is that we didn't call the methods test_sum
and test_subtract
directly; they were executed automatically. This is because methods that start with test_
are the ones that get executed automatically.
A simpler way to run the tests is to use the discover
command, which looks for all files starting with test_
in the folder passed through the -s
parameter.
First, we rewrite the tests properly.
!echo "import unittest" > testing_python/tests/test_calculator.py!echo "from src.calculator import sum, substract, multiply, divide" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo "class TestCalculator(unittest.TestCase):" >> testing_python/tests/test_calculator.py!echo " def test_sum(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(sum(2, 2), 4)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_substract(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(substract(2, 1), 1)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_multiply(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(multiply(2, 3), 6)" >> testing_python/tests/test_calculator.py!echo "" >> testing_python/tests/test_calculator.py!echo " def test_divide(self):" >> testing_python/tests/test_calculator.py!echo " self.assertEqual(divide(6, 3), 2)" >> testing_python/tests/test_calculator.py
And now we run the tests using discover
!cd testing_python && python -m unittest discover -s tests
....----------------------------------------------------------------------Ran 4 tests in 0.000sOK
He found the tests and passed them successfully
Setting up the tests
setUp

With the unittest
library we can configure the tests, but to see it, first we are going to create a new code file called bank_account.py
in the src
folder.
!echo "class BankAccount:" > testing_python/src/bank_account.py!echo " def __init__(self, balance=0):" >> testing_python/src/bank_account.py!echo " self.balance = balance" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def deposit(self, amount):" >> testing_python/src/bank_account.py!echo " if amount > 0:" >> testing_python/src/bank_account.py!echo " self.balance += amount" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def withdraw(self, amount):" >> testing_python/src/bank_account.py!echo " if amount > 0:" >> testing_python/src/bank_account.py!echo " self.balance -= amount" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def get_balance(self):" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py
Now we add tests to the new code, creating a file called test_bank_account.py
in the tests
folder.
Let's create the test for adding a deposit first, that is, the deposit
method.
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " account = BankAccount()" >> testing_python/tests/test_bank_account.py!echo " new_balace = account.deposit(1500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py
We pass the tests
!cd testing_python && python -m unittest discover -s tests
.....----------------------------------------------------------------------Ran 5 tests in 0.000sOK
We see that there are five points, but we have only written one test, so we use the flag -v
to get more information.
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 5 tests in 0.000sOK
We see that discover
has found the tests test_calculator
and test_bank_account
and both have passed.
Let's create the tests for the rest of the methods
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " account = BankAccount(balance=1000)" >> testing_python/tests/test_bank_account.py!echo " new_balace = account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " account = BankAccount(balance=1000)" >> testing_python/tests/test_bank_account.py!echo " new_balace = account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " account = BankAccount(balance=1000)" >> testing_python/tests/test_bank_account.py!echo " balance = account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py
We pass the tests
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 7 tests in 0.000sOK
We see that they have passed successfully. Now let's look at one thing: in all the tests we did account = BankAccount(balance=1000)
and then called the methods. This is because each test runs on a new object, meaning objects are not shared between tests.
So we can use the setUp
method to create an object that is shared among all tests.
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py
As we can see, we have created the account in the setUp
method and removed the account creation in the tests. Let's run the tests.
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 7 tests in 0.000sOK
tearDown

Just like with the setUp
method we configure the environment before running the tests, with the tearDown
method we can clean up the environment after running the tests. To test this, let's add to the code of bank_account.py
that operations are written to a log file.
!echo "class BankAccount:" > testing_python/src/bank_account.py!echo " def __init__(self, balance=0, log_file=None):" >> testing_python/src/bank_account.py!echo " self.balance = balance" >> testing_python/src/bank_account.py!echo " self.log_file = log_file" >> testing_python/src/bank_account.py!echo " self._log_transaction('Account created')" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def _log_transaction(self, message):" >> testing_python/src/bank_account.py!echo " if self.log_file:" >> testing_python/src/bank_account.py!echo " with open(self.log_file, 'a') as file:" >> testing_python/src/bank_account.py!echo " file.write(f'{message}\\ ')" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def deposit(self, amount):" >> testing_python/src/bank_account.py!echo " if amount > 0:" >> testing_python/src/bank_account.py!echo " self.balance += amount" >> testing_python/src/bank_account.py!echo " self._log_transaction(f'Deposit {amount}, new balance {self.balance}')" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def withdraw(self, amount):" >> testing_python/src/bank_account.py!echo " if amount > 0:" >> testing_python/src/bank_account.py!echo " self.balance -= amount" >> testing_python/src/bank_account.py!echo " self._log_transaction(f'Withdraw {amount}, new balance {self.balance}')" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def get_balance(self):" >> testing_python/src/bank_account.py!echo " self._log_transaction(f'Balance check, balance {self.balance}')" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py
Now we add a test to the new method _log_transaction
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " assert os.path.exists('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1000\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'])" >> testing_python/tests/test_bank_account.py
We pass the tests
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_transaction_log (test_bank_account.TestBankAccount) ... FAILtest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok======================================================================FAIL: test_transaction_log (test_bank_account.TestBankAccount)----------------------------------------------------------------------Traceback (most recent call last):File "/home/wallabot/Documentos/web/portafolio/posts/testing_python/tests/test_bank_account.py", line 28, in test_transaction_logself.assertEqual(content, [AssertionError: Lists differ: ['Acc[224 chars]00 ', 'Account created ', 'Withdraw 200, new[246 chars]0 '] != ['Acc[224 chars]00 ']First list contains 10 additional elements.First extra element 8:'Account created '['Account created ','Deposit 500, new balance 1500 ','Account created ','Balance check, balance 1000 ','Account created ','Deposit 500, new balance 1500 ','Withdraw 200, new balance 1300 ',- 'Balance check, balance 1300 ',- 'Account created ',- 'Withdraw 200, new balance 800 ',- 'Account created ',- 'Deposit 500, new balance 1500 ',- 'Account created ',- 'Balance check, balance 1000 ',- 'Account created ',- 'Deposit 500, new balance 1500 ',- 'Withdraw 200, new balance 1300 ','Balance check, balance 1300 ']----------------------------------------------------------------------Ran 8 tests in 0.001sFAILED (failures=1)
The tests have gone well, but we see that in the log file there are many lines with the text Account created
, this is because at the beginning of each test the setUp
method is executed which creates an account, so we need to create the tearDown
method to delete the log file after each test.
Since this is a file generated for the test, it should not exist after running the tests, so let's add the tearDown
method to delete the file.
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " assert os.path.exists('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1000\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py
We run the tests again
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_transaction_log (test_bank_account.TestBankAccount) ... FAILtest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok======================================================================FAIL: test_transaction_log (test_bank_account.TestBankAccount)----------------------------------------------------------------------Traceback (most recent call last):File "/home/wallabot/Documentos/web/portafolio/posts/testing_python/tests/test_bank_account.py", line 32, in test_transaction_logself.assertEqual(content, [AssertionError: Lists differ: ['Acc[48 chars]n', 'Withdraw 200, new balance 1300 ', 'Balan[21 chars]0 '] != ['Acc[48 chars]n', 'Account created ', 'Balance check, balan[131 chars]0 ']First differing element 2:'Withdraw 200, new balance 1300 ''Account created 'Second list contains 4 additional elements.First extra element 4:'Account created '['Account created ',+ 'Deposit 500, new balance 1500 ',+ 'Account created ',+ 'Balance check, balance 1000 ',+ 'Account created ','Deposit 500, new balance 1500 ','Withdraw 200, new balance 1300 ','Balance check, balance 1300 ']----------------------------------------------------------------------Ran 8 tests in 0.001sFAILED (failures=1)
But now we get an error, because since at the end of each test we have deleted the log file, we don't need to check that so much text has been written, but only the text from the test we are running. So let's modify the test to only check that the text from the test has been written.
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " assert os.path.exists('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py
And we pass the tests
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_transaction_log (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 8 tests in 0.001sOK
Error Documentation
If you've noticed, so far in the tests we have been using assertEqual
. This method gives us the option to write an error message when the condition is not met. Let's modify a test to make it fail and see the error message.
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 500, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " assert os.path.exists('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py
We pass the tests
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_transaction_log (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... FAILtest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok======================================================================FAIL: test_withdraw (test_bank_account.TestBankAccount)----------------------------------------------------------------------Traceback (most recent call last):File "/home/wallabot/Documentos/web/portafolio/posts/testing_python/tests/test_bank_account.py", line 19, in test_withdrawself.assertEqual(new_balace, 500, 'Balance is not correct')AssertionError: 800 != 500 : Balance is not correct----------------------------------------------------------------------Ran 8 tests in 0.001sFAILED (failures=1)
As we can see, we have the message we wrote in the test AssertionError: 800 != 500 : Balance is not correct
assert
s
So far we have used assertEqual
, but there are more, such as assertTrue
. Let's modify the line assert os.path.exists('test_log.txt')
to self.assertTrue(os.path.exists('test_log.txt'))
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(os.path.exists('test_log.txt'))" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py
And we pass the tests
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_transaction_log (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 8 tests in 0.001sOK
Some other assert
s we can use are:* assertEqual
* assertNotEqual
* assertTrue
* assertFalse
* assertRaises
skip

Just like in Python we can use pass
to do nothing, in tests we can use skip
to skip a test. This can be useful when we know we want to pass some tests, but the code to pass them is not yet ready.
I'm going to add a test that checks if the user has a loan, but since there is no code related to loans yet, I will skip the test.
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(os.path.exists('test_log.txt'))" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skip('Not implemented yet')" >> testing_python/tests/test_bank_account.py!echo " def test_has_loan(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertFalse(self.account.has_loan())" >> testing_python/tests/test_bank_account.py
Let's see what happens when we run it
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_has_loan (test_bank_account.TestBankAccount) ... skipped 'Not implemented yet'test_transaction_log (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 9 tests in 0.001sOK (skipped=1)
We can see test_has_loan (test_bank_account.TestBankAccount) ... skipped 'Not implemented yet'
that test has been skipped as we wanted.
skipIf

Another option is to use skipIf
to skip a test if a condition is met. I'm going to add a variable at the beginning called server
to know whether we are in a local environment or on the server, and the test will check this variable so that if we are on the server, it will skip the test.
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "SERVER = True" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(os.path.exists('test_log.txt'))" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skip('Not implemented yet')" >> testing_python/tests/test_bank_account.py!echo " def test_has_loan(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertFalse(self.account.has_loan())" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skipIf(SERVER, 'Only for local testing')" >> testing_python/tests/test_bank_account.py!echo " def test_server(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(SERVER)" >> testing_python/tests/test_bank_account.py
If we pass the tests
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_get_balance (test_bank_account.TestBankAccount) ... oktest_has_loan (test_bank_account.TestBankAccount) ... skipped 'Not implemented yet'test_server (test_bank_account.TestBankAccount) ... skipped 'Only for local testing'test_transaction_log (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 10 tests in 0.001sOK (skipped=2)
We see test_server (test_bank_account.TestBankAccount) ... skipped 'Only for local testing'
since, as SERVER = True
, the test has been skipped.
expectedFailure

An error should be produced when a user tries to withdraw more money than they have, so with the expectedFailure
method we can state that we expect the test to fail.
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "SERVER = True" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(os.path.exists('test_log.txt'))" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skip('Not implemented yet')" >> testing_python/tests/test_bank_account.py!echo " def test_has_loan(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertFalse(self.account.has_loan())" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skipIf(SERVER, 'Only for local testing')" >> testing_python/tests/test_bank_account.py!echo " def test_server(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(SERVER)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.expectedFailure" >> testing_python/tests/test_bank_account.py!echo " def test_fail(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(1200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Not enough money')" >> testing_python/tests/test_bank_account.py
We pass the tests
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_fail (test_bank_account.TestBankAccount) ... expected failuretest_get_balance (test_bank_account.TestBankAccount) ... oktest_has_loan (test_bank_account.TestBankAccount) ... skipped 'Not implemented yet'test_server (test_bank_account.TestBankAccount) ... skipped 'Only for local testing'test_transaction_log (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 11 tests in 0.001sOK (skipped=2, expected failures=1)
As we can see, we get test_fail (test_bank_account.TestBankAccount) ... expected failure
because it is an error we expected.
skipUnless

Just like before, there may be tests that we only want to run in the development environment, but never on the server, or in staging, or in production. For this, we can use skipUnless
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "SERVER = True" >> testing_python/tests/test_bank_account.py!echo "ENVIRONMENT = 'server'" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(os.path.exists('test_log.txt'))" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skip('Not implemented yet')" >> testing_python/tests/test_bank_account.py!echo " def test_has_loan(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertFalse(self.account.has_loan())" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skipIf(SERVER, 'Only for local testing')" >> testing_python/tests/test_bank_account.py!echo " def test_server(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(SERVER)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.expectedFailure" >> testing_python/tests/test_bank_account.py!echo " def test_fail(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(1200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Not enough money')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skipUnless(ENVIRONMENT == 'dev', 'Only for dev environment')" >> testing_python/tests/test_bank_account.py!echo " def test_environment(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(ENVIRONMENT, 'dev')" >> testing_python/tests/test_bank_account.py
We pass the tests
!cd testing_python && python -m unittest discover -s tests -v
test_deposit (test_bank_account.TestBankAccount) ... oktest_environment (test_bank_account.TestBankAccount) ... skipped 'Only for dev environment'test_fail (test_bank_account.TestBankAccount) ... expected failuretest_get_balance (test_bank_account.TestBankAccount) ... oktest_has_loan (test_bank_account.TestBankAccount) ... skipped 'Not implemented yet'test_server (test_bank_account.TestBankAccount) ... skipped 'Only for local testing'test_transaction_log (test_bank_account.TestBankAccount) ... oktest_withdraw (test_bank_account.TestBankAccount) ... oktest_divide (test_calculator.TestCalculator) ... oktest_multiply (test_calculator.TestCalculator) ... oktest_substract (test_calculator.TestCalculator) ... oktest_sum (test_calculator.TestCalculator) ... ok----------------------------------------------------------------------Ran 12 tests in 0.001sOK (skipped=3, expected failures=1)
We get test_environment (test_bank_account.TestBankAccount) ... skipped 'Only for dev environment'
because that test is only going to pass in the development environment and we are supposed to be on the server.
Organize the tests
We can organize the tests by creating what are called test suites
. To do this, we will create a file called test_suites.py
in the tests
folder.
!cd testing_python && cd tests && touch test_suites.py
And now we generate two test suites with the tests from test_calculator
and test_bank_account
!echo "import unittest" > testing_python/tests/test_suite_calculator.py!echo "" >> testing_python/tests/test_suite_calculator.py!echo "from test_calculator import TestCalculator" >> testing_python/tests/test_suite_calculator.py!echo "" >> testing_python/tests/test_suite_calculator.py!echo "def calculator_suite():" >> testing_python/tests/test_suite_calculator.py!echo " suite = unittest.TestSuite()" >> testing_python/tests/test_suite_calculator.py!echo " suite.addTest(TestCalculator('test_sum'))" >> testing_python/tests/test_suite_calculator.py!echo " suite.addTest(TestCalculator('test_substract'))" >> testing_python/tests/test_suite_calculator.py!echo " suite.addTest(TestCalculator('test_multiply'))" >> testing_python/tests/test_suite_calculator.py!echo " suite.addTest(TestCalculator('test_divide'))" >> testing_python/tests/test_suite_calculator.py!echo " return suite" >> testing_python/tests/test_suite_calculator.py!echo "" >> testing_python/tests/test_suite_calculator.py!echo "if __name__ == '__main__':" >> testing_python/tests/test_suite_calculator.py!echo " runner = unittest.TextTestRunner()" >> testing_python/tests/test_suite_calculator.py!echo " runner.run(calculator_suite())" >> testing_python/tests/test_suite_calculator.py
Now the one for test_bank_account
!echo "import unittest" > testing_python/tests/test_suite_bank_account.py!echo "" >> testing_python/tests/test_suite_bank_account.py!echo "from test_bank_account import TestBankAccount" >> testing_python/tests/test_suite_bank_account.py!echo "" >> testing_python/tests/test_suite_bank_account.py!echo "def bank_account_suite():" >> testing_python/tests/test_suite_bank_account.py!echo " suite = unittest.TestSuite()" >> testing_python/tests/test_suite_bank_account.py!echo " suite.addTest(TestBankAccount('test_deposit'))" >> testing_python/tests/test_suite_bank_account.py!echo " suite.addTest(TestBankAccount('test_withdraw'))" >> testing_python/tests/test_suite_bank_account.py!echo " suite.addTest(TestBankAccount('test_get_balance'))" >> testing_python/tests/test_suite_bank_account.py!echo " suite.addTest(TestBankAccount('test_transaction_log'))" >> testing_python/tests/test_suite_bank_account.py!echo " suite.addTest(TestBankAccount('test_has_loan'))" >> testing_python/tests/test_suite_bank_account.py!echo " suite.addTest(TestBankAccount('test_server'))" >> testing_python/tests/test_suite_bank_account.py!echo " suite.addTest(TestBankAccount('test_fail'))" >> testing_python/tests/test_suite_bank_account.py!echo " suite.addTest(TestBankAccount('test_environment'))" >> testing_python/tests/test_suite_bank_account.py!echo " return suite" >> testing_python/tests/test_suite_bank_account.py!echo "" >> testing_python/tests/test_suite_bank_account.py!echo "if __name__ == '__main__':" >> testing_python/tests/test_suite_bank_account.py!echo " runner = unittest.TextTestRunner()" >> testing_python/tests/test_suite_bank_account.py!echo " runner.run(bank_account_suite())" >> testing_python/tests/test_suite_bank_account.py
We can run only the test_calculator
tests with the command python tests/test_suite_calculator.py
!cd testing_python && python tests/test_suite_calculator.py
Traceback (most recent call last):File "/home/wallabot/Documentos/web/portafolio/posts/testing_python/tests/test_suite_calculator.py", line 3, in <module>from test_calculator import TestCalculatorFile "/home/wallabot/Documentos/web/portafolio/posts/testing_python/tests/test_calculator.py", line 2, in <module>from src.calculator import sum, substract, multiply, divideModuleNotFoundError: No module named 'src'
We see that it cannot find the src
module from test_calculator.py
, this is because it is not in the path, so let's add it.
!cd testing_python && PYTHONPATH=. python tests/test_suite_calculator.py
....----------------------------------------------------------------------Ran 4 tests in 0.000sOK
We see that only the test_calculator
tests have passed.
Let's move on to the ones in test_bank_account.py
!cd testing_python && PYTHONPATH=. python tests/test_suite_bank_account.py
....ssxs----------------------------------------------------------------------Ran 8 tests in 0.001sOK (skipped=3, expected failures=1)
And here we see that it passes the test_bank_account
tests.
We need to differentiate from what we did at the beginning, before using discover
, when we did !cd testing_python && python -m unittest tests/test_calculator.py
. Because in test_calculator.py
we can write all possible tests for calculator.py
, but with suite
s we run the tests we want. We could have several suite
s to run the tests we want from test_calculator.py
and test_bank_account.py
.
Best practices when naming tests
When naming tests, it's a good idea to follow these guidelines:* One test class for each code class* All tests must start with test_
to know that it is a code* The name of the function or method being tested should follow below* The test scenario should follow, as a method or function can have several test scenarios. For example, with all possible input values, with boundary values, with incorrect values, etc.* Finally, the expected result should be added
So a test should have the following format test_<function_name>_<scenario>_<expected_result>
API Mocking
Let's assume our code calls an external API and we want to test our code without depending on the external API. For this, we can use unittest.mock
, which comes by default in Python.
First, let's create a file called api.py
in the src
folder
!echo "import requests" > testing_python/src/api.py!echo "" >> testing_python/src/api.py!echo "def get_api():" >> testing_python/src/api.py!echo " url = 'https://jsonplaceholder.typicode.com/posts/1'" >> testing_python/src/api.py!echo " try:" >> testing_python/src/api.py!echo " response = requests.get(url)" >> testing_python/src/api.py!echo " response.raise_for_status()" >> testing_python/src/api.py!echo " data = response.json()" >> testing_python/src/api.py!echo " print(data)" >> testing_python/src/api.py!echo " return data" >> testing_python/src/api.py!echo " except requests.exceptions.RequestException as e:" >> testing_python/src/api.py!echo " print(f'error: {e}')" >> testing_python/src/api.py!echo " return None" >> testing_python/src/api.py!echo "" >> testing_python/src/api.py!echo "if __name__ == '__main__':" >> testing_python/src/api.py!echo " get_api()" >> testing_python/src/api.py
Let's run the file to see what it returns
!cd testing_python/src && python api.py
{'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto'}
Now we create the test
!echo "import unittest" > testing_python/tests/test_api.py!echo "from src.api import get_api" >> testing_python/tests/test_api.py!echo "" >> testing_python/tests/test_api.py!echo "class TestApi(unittest.TestCase):" >> testing_python/tests/test_api.py!echo " def test_get_api(self):" >> testing_python/tests/test_api.py!echo " data = get_api()" >> testing_python/tests/test_api.py!echo " self.assertIsNotNone(data)" >> testing_python/tests/test_api.py!echo " self.assertIsInstance(data, dict)" >> testing_python/tests/test_api.py!echo " self.assertIn('userId', data)" >> testing_python/tests/test_api.py!echo " self.assertIn('id', data)" >> testing_python/tests/test_api.py!echo " self.assertIn('title', data)" >> testing_python/tests/test_api.py!echo " self.assertIn('body', data)" >> testing_python/tests/test_api.py!echo " self.assertEqual(data['userId'], 1)" >> testing_python/tests/test_api.py!echo " self.assertEqual(data['id'], 1)" >> testing_python/tests/test_api.py
We pass the tests
!cd testing_python && python -m unittest tests/test_api.py
{'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto'}.----------------------------------------------------------------------Ran 1 test in 0.074sOK
Let's run the tests for bank_account
to see how long it takes
!cd testing_python && PYTHONPATH=. python tests/test_suite_bank_account.py
....ssxs----------------------------------------------------------------------Ran 8 tests in 0.001sOK (skipped=3, expected failures=1)
As we can see, 8 tests took 0.001 seconds, while a single API test took 0.074 seconds. This can make a code with many calls to an API take a long time to run the tests. Additionally, there's a risk that if the API changes and doesn't return what we expect, the tests will fail even though our code is correct. That's why APIs are mocked.
Let's see how to do it
!echo "import unittest" > testing_python/tests/test_api.py!echo "from src.api import get_api" >> testing_python/tests/test_api.py!echo "from unittest.mock import patch" >> testing_python/tests/test_api.py!echo "" >> testing_python/tests/test_api.py!echo "class TestApi(unittest.TestCase):" >> testing_python/tests/test_api.py!echo " @patch('src.api.requests.get')" >> testing_python/tests/test_api.py!echo " def test_get_api(self, mock_get):" >> testing_python/tests/test_api.py!echo " mock_get.return_value.status_code = 200" >> testing_python/tests/test_api.py!echo " mock_get.return_value.json.return_value = {" >> testing_python/tests/test_api.py!echo " 'userId': 1," >> testing_python/tests/test_api.py!echo " 'id': 1," >> testing_python/tests/test_api.py!echo " 'title': 'title'," >> testing_python/tests/test_api.py!echo " 'body': 'body'" >> testing_python/tests/test_api.py!echo " }" >> testing_python/tests/test_api.py!echo " data = get_api()" >> testing_python/tests/test_api.py!echo " self.assertIsNotNone(data)" >> testing_python/tests/test_api.py!echo " self.assertIsInstance(data, dict)" >> testing_python/tests/test_api.py!echo " self.assertIn('userId', data)" >> testing_python/tests/test_api.py!echo " self.assertIn('id', data)" >> testing_python/tests/test_api.py!echo " self.assertIn('title', data)" >> testing_python/tests/test_api.py!echo " self.assertIn('body', data)" >> testing_python/tests/test_api.py!echo " self.assertEqual(data['userId'], 1)" >> testing_python/tests/test_api.py!echo " self.assertEqual(data['id'], 1)" >> testing_python/tests/test_api.py!echo " " >> testing_python/tests/test_api.py!echo " mock_get.assert_called_once_with('https://jsonplaceholder.typicode.com/posts/1')" >> testing_python/tests/test_api.py
What we have done is* Import patch
from unittest.mock
.* We have added a decorator to the function test_get_data
and passed as a parameter to the decorator the function we want to mock.* We have created a mock_get
object that will replace the original function with the return_value
method, which is the value that the mocked function will return.* When get_api()
is called, instead of executing the original function, data
will contain the value we have mocked in return_value
* Finally, we added the check that the URL
of the API can be called.
!cd testing_python && python -m unittest tests/test_api.py
{'userId': 1, 'id': 1, 'title': 'title', 'body': 'body'}.----------------------------------------------------------------------Ran 1 test in 0.001sOK
As we can see, it now only takes 0.002 seconds.
Modify the result of objects using patch

Suppose we want to modify the method of withdrawing money so that it can only be done within a specific range of hours. First we modify the withdraw function
def withdraw(self, amount):now = datetime.now()if now.hour < 7 or now.hour > 18:print('Out of hours')return Noneif amount > 0:self.balance -= amountself._log_transaction(f'Withdraw {amount}, new balance {self.balance}')return self.balance```
!echo "from datetime import datetime" > testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo "class BankAccount:" >> testing_python/src/bank_account.py!echo " def __init__(self, balance=0, log_file=None):" >> testing_python/src/bank_account.py!echo " self.balance = balance" >> testing_python/src/bank_account.py!echo " self.log_file = log_file" >> testing_python/src/bank_account.py!echo " self._log_transaction('Account created')" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def _log_transaction(self, message):" >> testing_python/src/bank_account.py!echo " if self.log_file:" >> testing_python/src/bank_account.py!echo " with open(self.log_file, 'a') as file:" >> testing_python/src/bank_account.py!echo " file.write(f'{message}\\ ')" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def deposit(self, amount):" >> testing_python/src/bank_account.py!echo " if amount > 0:" >> testing_python/src/bank_account.py!echo " self.balance += amount" >> testing_python/src/bank_account.py!echo " self._log_transaction(f'Deposit {amount}, new balance {self.balance}')" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def withdraw(self, amount):" >> testing_python/src/bank_account.py!echo " now = datetime.now()" >> testing_python/src/bank_account.py!echo " if now.hour < 7 or now.hour > 18:" >> testing_python/src/bank_account.py!echo " print('Out of hours')" >> testing_python/src/bank_account.py!echo " return None" >> testing_python/src/bank_account.py!echo " if amount > 0:" >> testing_python/src/bank_account.py!echo " self.balance -= amount" >> testing_python/src/bank_account.py!echo " self._log_transaction(f'Withdraw {amount}, new balance {self.balance}')" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py!echo "" >> testing_python/src/bank_account.py!echo " def get_balance(self):" >> testing_python/src/bank_account.py!echo " self._log_transaction(f'Balance check, balance {self.balance}')" >> testing_python/src/bank_account.py!echo " return self.balance" >> testing_python/src/bank_account.py
Now we need to be able to test the withdrawal of money at different times, but we can't keep changing the system time, nor can we wait for it to be a specific time to run the test. For this, we can use patch
to modify the result of datetime.now()
@patch('src.bank_account.datetime.now')def test_withdraw_during_working_hours(self, mock_now):mock_now.return_value.hour = 10new_balance = self.account.withdraw(200)self.assertEqual(new_balance, 800, 'Balance is not correct')
@patch('src.bank_account.datetime.now')def test_withdraw_during_non_working_hours(self, mock_now):mock_now.return_value.hour = 20new_balance = self.account.withdraw(200)self.assertIsNone(new_balance, 'Balance is not correct')```
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "from unittest.mock import patch" >> testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "SERVER = True" >> testing_python/tests/test_bank_account.py!echo "ENVIRONMENT = 'server'" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @patch('src.bank_account.datetime.now')" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw_during_working_hours(self, mock_now):" >> testing_python/tests/test_bank_account.py!echo " mock_now.return_value.hour = 10" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @patch('src.bank_account.datetime.now')" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw_during_non_working_hours(self, mock_now):" >> testing_python/tests/test_bank_account.py!echo " mock_now.return_value.hour = 20" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertIsNone(new_balace, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(os.path.exists('test_log.txt'))" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skip('Not implemented yet')" >> testing_python/tests/test_bank_account.py!echo " def test_has_loan(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertFalse(self.account.has_loan())" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skipIf(SERVER, 'Only for local testing')" >> testing_python/tests/test_bank_account.py!echo " def test_server(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(SERVER)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.expectedFailure" >> testing_python/tests/test_bank_account.py!echo " def test_fail(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(1200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Not enough money')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skipUnless(ENVIRONMENT == 'dev', 'Only for dev environment')" >> testing_python/tests/test_bank_account.py!echo " def test_environment(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(ENVIRONMENT, 'dev')" >> testing_python/tests/test_bank_account.py
We pass the tests
!cd testing_python && PYTHONPATH=. python tests/test_suite_bank_account.py
....ssxs----------------------------------------------------------------------Ran 8 tests in 0.001sOK (skipped=3, expected failures=1)
Parameterizing tests with subTest

Suppose we want to run several tests for withdrawing money with different values. We could write a test for each value of money we want to test, but that would be repetitive code. For this, we can use subTest
, which allows us to perform multiple tests with a single test case.
What we do is create a dictionary with the different values we want to test and the expected result, and then with a for
loop and subTest
we perform the tests. This is how the test for the withdraw
method would look:
def test_withdraw(self):test_cases = {test_200: {'amount': 200,'expected': 800},test_400: {'amount': 400,'expected': 600},test_600: {'amount': 600,'expected': 400},test_800: {'amount': 800,'expected': 200},test_1000: {'amount': 1000,'expected': 0}It seems like you might have accidentally sent an incomplete request. If you have a Markdown text to translate, please provide it and I'll be happy to help!
for test_name, test_case in test_cases.items():with self.subTest(test_name):self.account = BankAccount(balance=1000, log_file='test_log.txt')new_balance = self.account.withdraw(test_case['amount'])self.assertEqual(new_balance, test_case['expected'], 'Balance is not correct')```
!echo "import unittest" > testing_python/tests/test_bank_account.py!echo "from unittest.mock import patch" >> testing_python/tests/test_bank_account.py!echo "import os" >> testing_python/tests/test_bank_account.py!echo "from src.bank_account import BankAccount" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "SERVER = True" >> testing_python/tests/test_bank_account.py!echo "ENVIRONMENT = 'server'" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo "class TestBankAccount(unittest.TestCase):" >> testing_python/tests/test_bank_account.py!echo " def setUp(self):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def tearDown(self):" >> testing_python/tests/test_bank_account.py!echo " if os.path.exists('test_log.txt'):" >> testing_python/tests/test_bank_account.py!echo " os.remove('test_log.txt')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_deposit(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 1500)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw(self):" >> testing_python/tests/test_bank_account.py!echo " test_cases = {" >> testing_python/tests/test_bank_account.py!echo " 'test_200': {" >> testing_python/tests/test_bank_account.py!echo " 'amount': 200," >> testing_python/tests/test_bank_account.py!echo " 'expected': 800" >> testing_python/tests/test_bank_account.py!echo " }," >> testing_python/tests/test_bank_account.py!echo " 'test_400': {" >> testing_python/tests/test_bank_account.py!echo " 'amount': 400," >> testing_python/tests/test_bank_account.py!echo " 'expected': 600" >> testing_python/tests/test_bank_account.py!echo " }," >> testing_python/tests/test_bank_account.py!echo " 'test_600': {" >> testing_python/tests/test_bank_account.py!echo " 'amount': 600," >> testing_python/tests/test_bank_account.py!echo " 'expected': 400" >> testing_python/tests/test_bank_account.py!echo " }," >> testing_python/tests/test_bank_account.py!echo " 'test_800': {" >> testing_python/tests/test_bank_account.py!echo " 'amount': 800," >> testing_python/tests/test_bank_account.py!echo " 'expected': 200" >> testing_python/tests/test_bank_account.py!echo " }," >> testing_python/tests/test_bank_account.py!echo " 'test_1000': {" >> testing_python/tests/test_bank_account.py!echo " 'amount': 1000," >> testing_python/tests/test_bank_account.py!echo " 'expected': 0" >> testing_python/tests/test_bank_account.py!echo " }" >> testing_python/tests/test_bank_account.py!echo " }" >> testing_python/tests/test_bank_account.py!echo " for test_case, values in test_cases.items():" >> testing_python/tests/test_bank_account.py!echo " with self.subTest(test_case=test_case):" >> testing_python/tests/test_bank_account.py!echo " self.account = BankAccount(balance=1000, log_file='test_log.txt')" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(values['amount'])" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, values['expected'], 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @patch('src.bank_account.datetime.now')" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw_during_working_hours(self, mock_now):" >> testing_python/tests/test_bank_account.py!echo " mock_now.return_value.hour = 10" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @patch('src.bank_account.datetime.now')" >> testing_python/tests/test_bank_account.py!echo " def test_withdraw_during_non_working_hours(self, mock_now):" >> testing_python/tests/test_bank_account.py!echo " mock_now.return_value.hour = 20" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.assertIsNone(new_balace, 'Balance is not correct')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_get_balance(self):" >> testing_python/tests/test_bank_account.py!echo " balance = self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(balance, 1000)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " def test_transaction_log(self):" >> testing_python/tests/test_bank_account.py!echo " self.account.deposit(500)" >> testing_python/tests/test_bank_account.py!echo " self.account.withdraw(200)" >> testing_python/tests/test_bank_account.py!echo " self.account.get_balance()" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(os.path.exists('test_log.txt'))" >> testing_python/tests/test_bank_account.py!echo " with open('test_log.txt', 'r') as file:" >> testing_python/tests/test_bank_account.py!echo " content = file.readlines()" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(content, [" >> testing_python/tests/test_bank_account.py!echo " 'Account created\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Deposit 500, new balance 1500\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Withdraw 200, new balance 1300\\n', " >> testing_python/tests/test_bank_account.py!echo " 'Balance check, balance 1300\\n'" >> testing_python/tests/test_bank_account.py!echo " ])" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skip('Not implemented yet')" >> testing_python/tests/test_bank_account.py!echo " def test_has_loan(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertFalse(self.account.has_loan())" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skipIf(SERVER, 'Only for local testing')" >> testing_python/tests/test_bank_account.py!echo " def test_server(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertTrue(SERVER)" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.expectedFailure" >> testing_python/tests/test_bank_account.py!echo " def test_fail(self):" >> testing_python/tests/test_bank_account.py!echo " new_balace = self.account.withdraw(1200)" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(new_balace, 800, 'Not enough money')" >> testing_python/tests/test_bank_account.py!echo "" >> testing_python/tests/test_bank_account.py!echo " @unittest.skipUnless(ENVIRONMENT == 'dev', 'Only for dev environment')" >> testing_python/tests/test_bank_account.py!echo " def test_environment(self):" >> testing_python/tests/test_bank_account.py!echo " self.assertEqual(ENVIRONMENT, 'dev')" >> testing_python/tests/test_bank_account.py
We pass the tests
!cd testing_python && PYTHONPATH=. python tests/test_suite_bank_account.py
....ssxs----------------------------------------------------------------------Ran 8 tests in 0.001sOK (skipped=3, expected failures=1)
Test Coverage
We can see the test coverage we have, as even though we may think we have tested everything, there might be parts of the code that we haven't tested. For this, we are going to use the coverage
library.
!cd testing_python && coverage run -m unittest tests/test_calculator.py
....----------------------------------------------------------------------Ran 4 tests in 0.000sOK
As we can see, we have only passed the test_calculator
tests, but we still don't know the coverage.
Coverage Report
After running coverage
, we can request a test coverage report with the command coverage report
!cd testing_python && coverage report
Name Stmts Miss Cover----------------------------------------------src/calculator.py 8 0 100%tests/test_calculator.py 11 0 100%----------------------------------------------TOTAL 19 0 100%
Or request it through an HTML file with the command coverage html
!cd testing_python && coverage html
Wrote HTML report to ]8;;file:///home/wallabot/Documentos/web/portafolio/posts/testing_python/htmlcov/index.htmlhtmlcov/index.html]8;;
An index.html
file is created in the htmlcov
folder that we can open in the browser to view the test coverage.