Skip to content

Simple Unit Test

Fitri edited this page Sep 5, 2021 · 11 revisions

Resouces and References

Python Wiki

Youtube Vidoes

Unit Testing Description

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.

Test Driven Development

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.

Structuring Unit Test Folders

There's two way of structuring unit test in python:

  1. Storing the unit test in same dir:
	main/
	|_ _ _ division.py
	|_ _ _ test_division.py
  1. 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.

Using python internal unit test module.

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.

code inside of maincode

#take two parameter which is int and add them

def mainfunction(x, z):
        return x + z

code inside of test_maincode

import 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)

Running the unittest code

If we are trying to run the file test_maincode.py now without any addition setup it will not return anything.

Typically there two way of running the unit test

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()

Test Full Coverage

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)

Setup and Teardown Method

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)