-
Notifications
You must be signed in to change notification settings - Fork 0
Simple Unit Test
Unit test method to test output of a code by providing input and output and check it against the expected input.
Normally unit test was separated by function, and each function should be specific doing one thing so it will be much easier to test the output.
Unit test is important so when there's refactoring, the function output can still be check and make sure the value output still correct as wanted.
TDD is the method to build software which taking prioritize writing test before actually writing the function itself.
We think of the system, how the system work, how many function and what each function will do and what is the expected output. Then we wrote the function and make sure all the function passing the expected output of the unit test.
There's two way of structuring unit test in python:
- Storing the unit test in same dir:
main/
|_ _ _ division.py
|_ _ _ test_division.py
- Folder for unit test only
main/
|_ _ _ division.py
|
|_ _ _ testing/
|_ _ _ test_division.py
Typically many choose the second structure since all the test are organize in separate exclusive folder so it much easier to sort the test later.
For this very simple example we going to use the same dir for the unit test and main code as the in the structure above.
#take two parameter which is int and add them
def mainfunction(x, z):
return x + zimport maincode
import unittest
#proper to use Test as the first name
class TestMaincode(unittest.TestCase):
#the function need to name test_
def test_adding(self):
#initialize the function we want to test
results = maincode.mainfunction(10,5)
#now test the value
self.assertEqual(results, 15)If we are trying to run the file test_maincode.py now without any addition setup it will not return anything.
1. Using unittest module with python
2. Running the test module itself
For the first method, we call the unittest module itself and pass the test module as the parameter for it to run:
$ python -m unittest test_allfunction.py
The second method we need to add the name == main on the bottom of the test module:
if name == 'main': unittest.main()
Within one function inside of the test function, we can add as many test as we want and if possible over every possible case for example if addition, cover the case of negative number and float.
import maincode import unittest
class TestMaincode(unittest.TestCase):
def test_adding(self):
self.assertEqual(maincode.mainfunction(10,5), 15)
self.assertEqual(maincode.mainfunction(-5,5), 0)
self.assertEqual(maincode.mainfunction(-4,-6), -10)
We can cover as many test case as possible within one function as shown above, and this will be included in the same test and testing the same function to make sure the function not broken when refactoring.
From the same code, if we add another function example test_substraction and include few test case, this will be consider as the second test. For each of the function we have inside of the main code, we want to have one function for test which include coverage for as many test case possible.
def test_subtraction(self):
self.assertEqual(maincode.subtract(10,3), 7)
self.assertEqual(maincode.subtract(-10,3), -13)
self.assertEqual(maincode.subtract(10,-10), 20)
If we caught new test case which failing the value but not cover within the test, we just need to add this test case inside of our test to make sure the same error will be caught when we run unittest next time.
Raising Error on The Unittest
There's two way of testing value with error raise, one by passing the function into the assertEqual method as argument, and another one is by calling the tested function with context manager.
Assuming we add this into the maincode:
def multiplication(x, y): if x or y == 0: raise ValueError('Number must not equal to 0') else: return x*y
First method: passing the function into method assertRaises:
def test_multiplication(self): self.assertRaises(ValueError, maincode.multiplication, 8, 0)
Second method: using context manager:
with self.assertRaises(ValueError): maincode.multiplication(8, 0)
There two builtin method inside of the class unittest.TestCase which will be called first before other unittest method being call which is setUp and tearDown method.
We can define the setUp and tearDown method and pass any initial code or value that need to be shared across the test class.
For example if we have repeated value we want to test across different method in the test class we can put something like this:
class TestProgram(unittest.TestCase):
def setUp(self):
self.test1 = ['value1', 'value2', 'value3']
self.test2 = ['valuea', 'valueb', 'valuec']
def tearDown(self):
pass
def test_functionone(self):
self.assertTrue(functionone(self.test1))
self.assertTrue(functionone(self.test2))
def test_functiontwo(self):
self.assertFalse(functionone(self.test1))
self.assertFalse(functionone(self.test2))
For each of the test test_functionone and testfunctiontwo, setUp() will run before the test method and tearDown() will run after the test. So we can set any object creation inside of the setUp() method and terminate the object inside of tearDown().
If we add the print statement to each of the method above (not added literally) the output we get is:
setup method test_functionone method teardown method
setup method test_functiontwo method teardown method
Along with the setup and teardown for each of the test method, we also have another type of setup and teardown which run once on class initialized and before end of the class itself which is setUpClass and tearDownClass, to make this both of method as class instead of instant we wrap both of this method inside decorator @classmethod
@classmethod def setUpClass(cls): pass
@classmethod tearDownClass(cls): pass
Both of this method will run once per class, if we were to add this into the print just now the order will be as this follow:
setUpClass method
setup method test_functionone method teardown method
setup method test_functiontwo method teardown method
tearDownClass method
Basic Mocking Unit Test
Given that external factor require to run unit test for example connecting to a website and download page source but if the web is down then there's no way we would be able to tell since the test will face, but it not coming from our code. The unit test should be only test our code not the external factor such as external web or api. For this purpose we can use mocking to simulate the data.
The basic idea behind this is we just set the value directly inside of the unit test. For example if we connect to a web and it return status code, then we should set the value 200 for success and others as failed.
Let said we have this module connection_tester.py which return connection status:
import requests
def connection_status(url): #the code should return 200 return requests.get(url).status_code
So the mocking unittest we should set should be:
[PLACEHOLDER] https://docs.python.org/3/library/unittest.mock-examples.html To to find info in another resources, might need details explanation, but in python documentation, mock was done using the same method as the videos which is by context managers. Find info and continue writing here.
Available method inside of python assertEqual.
Method Checks that assertEqual(a, b) a == b assertNotEqual(a, b) a != b assertTrue(x) bool(x) is True assertFalse(x) bool(x) is False assertIs(a, b) a is b assertIsNot(a, b) a is not b assertIsNone(x) x is None assertIsNotNone(x) x is not None assertIn(a, b) a in b assertNotIn(a, b) a not in b assertIsInstance(a, b) isinstance(a, b) assertNotIsInstance(a, b) not isinstance(a, b)