-
Notifications
You must be signed in to change notification settings - Fork 303
Guide: Writing unit tests Part 1
First, read up on Unit testing for Python. See this link here. Note that this document is not a comprehensive overview of testing in Python, but is simply a walkthrough on how to begin writing tests for KA Lite with the project's bundled libraries. Check the given link for a more comprehensive overview of testing.
Unfortunately, the KA Lite project did not start with a strong testing culture. But hey, we're here to change that!
For this tutorial, let's start with a basic function in the fle_utils
package:
Here's our function, found in fle_utils/platforms.py
:
def system_script_extension(system=None):
"""
The extension for the one script that could be considered "the os script" for the given system..
"""
exts = {
"windows": ".bat",
"darwin": ".command",
"linux": ".sh",
}
system = system or platform.system()
return exts.get(system.lower(), ".sh")
Simple to understand, simple to test. Let's get to it.
To get ourselves motivated, let's first test "The Happy Path." This is when the user of this function uses it properly and gives the input our function needs to properly do its job. In this case, the happy path is pretty easy to see: our function can work with no input and should almost always return a proper response!
Let's get to writing tests for it then. First off, make sure you have a clone of the fle-utils
project on your local machine and that you are running Python 2.7. After that, cd into your fle-utils
directory and then run pip install -r requirements.txt
to install all required files into your system. Then navigate to the testing
directory and make sure you testing/platforms_tests.py
exists with these lines in it:
import os
import sys
import unittest
sys.path += [os.path.realpath('..'), os.path.realpath('.')]
if __name__ == '__main__':
unittest.main()
If it already exists and has these lines, then someone already followed this tutorial :) If so, for the sake of this tutorial, feel free to delete the contents of this file and insert those lines instead. Just remember NOT TO COMMIT IT AFTER. I REPEAT, DO NOT COMMIT IT, OR YOUR PULL REQUESTS WILL BE REJECTED TO THE HIGH HEAVENS.
One of the libraries installed in your system when you ran pip install
was the mock library. Mock allows us to reliably dictate the output of certain functions, to make tests both reproducible across systems and make them run faster. Check out the full mock documentation. It's a really awesome library.
Alright, let's start writing our tests! Let's start by writing the test class (insert between from platforms import system_script_extension
and if __name__ == '__main__'
lines)
class SystemScriptExtensionTests(unittest.TestCase):
pass
That's the beginnings of our test class, which is empty for now. Next, let's ask ourselves what do we want to test. Looking at the function, let's first test if it will return .bat
for windows machines!
class SystemScriptExtensionTests(unittest.TestCase):
def test_returns_bat_on_windows(self):
pass
The function name can be anything as long as it starts with test
. We then fill out an assert statement to test if it's returning the right extension:
class SystemScriptExtensionTests(unittest.TestCase):
def test_returns_bat_on_windows(self):
self.assertEquals(system_script_extension(), '.bat')
self.assertEquals(system_script_extension('Windows'), '.bat')
We can run it within the fle_utils
dir with the command python testing/platforms_test.py
. I get an output like this:
F
======================================================================
FAIL: test_returns_bat_on_windows (__main__.SystemScriptExtensionTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "testing/platforms_test.py", line 49, in test_returns_bat_on_windows
self.assertEquals(system_script_extension(), '.bat')
AssertionError: '.sh' != '.bat'
----------------------------------------------------------------------
Ran 1 test in 0.003s
FAILED (failures=1)
Oh no! The first assertion will only succeed if we're running the test on a Windows machine! Now, we want to have this test be portable across different OSes, and not just succeed on the Windows platform. This is where the mock
library comes in: we want to basically mock and hijack the platform.system()
function to always return Windows
for the scope of this function.
I won't go into the details of how to use the mock library (check it out here), so i'll just throw in the end result:
class SystemScriptExtensionTests(unittest.TestCase):
@patch.object(platform, 'system')
def test_returns_bat_on_windows(self, system_method):
system_method.return_value = 'Windows'
self.assertEquals(system_script_extension(), '.bat')
self.assertEquals(system_script_extension('Windows'), '.bat')
Running that, we get this output:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Nice! As you can see, we "patch" the system function to always have a return_value
of 'Windows'
. With that, our function succeeds, and all is well! Tests for the other OSes plus our default return value of .sh
should be pretty easy to formulate now based on how we did this test for Windows. Can you try doing them? After you've done so, you can compare your answers to the ones I've written way down below:
*
*
*
*
*
*
*
*
*
*
*
*
Your class should now look similar to this:
class SystemScriptExtensionTests(unittest.TestCase):
@patch.object(platform, 'system')
def test_returns_bat_on_windows(self, system_method):
system_method.return_value = 'Windows'
self.assertEquals(system_script_extension(), '.bat')
self.assertEquals(system_script_extension('Windows'), '.bat')
@patch.object(platform, 'system')
def test_returns_command_on_darwin(self, system_method):
system_method.return_value = 'Darwin' # the Mac kernel
self.assertEquals(system_script_extension(), '.command')
self.assertEquals(system_script_extension('Darwin'), '.command')
@patch.object(platform, 'system')
def test_returns_sh_on_linux(self, system_method):
system_method.return_value = 'Linux'
self.assertEquals(system_script_extension(), '.sh')
self.assertEquals(system_script_extension('Linux'), '.sh')
@patch.object(platform, 'system')
def test_returns_sh_by_default(self, system_method):
system_method.return_value = 'Random OS'
self.assertEquals(system_script_extension(), '.sh')
self.assertEquals(system_script_extension('Random OS'), '.sh')
If you run the file with that command, you should get this output:
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s
OK
And all is well in unittest-land. To make sure your tests are working perfectly, try to change the outputs of system_specific_extension
a bit. Add a few letters here and there. You'll find that our tests will fail. We are now a little confident about this function 👍
Here's a summary of the things we learned so far:
- We can mock functions and methods using the
patch.object
of themock
library - We can use
self.assertEquals
to test if two things are equal. If not, then the test fails.
Before we end this basic tutorial, here's what your testing/platforms_test.py
should look like once you've completed this:
import os
import platform
import sys
import unittest
import mock
from mock import patch
sys.path += [os.path.realpath('..'), os.path.realpath('.')]
from platforms import system_script_extension
class SystemScriptExtensionTests(unittest.TestCase):
@patch.object(platform, 'system')
def test_returns_bat_on_windows(self, system_method):
system_method.return_value = 'Windows'
self.assertEquals(system_script_extension(), '.bat')
self.assertEquals(system_script_extension('Windows'), '.bat')
@patch.object(platform, 'system')
def test_returns_command_on_darwin(self, system_method):
system_method.return_value = 'Darwin' # the Mac kernel
self.assertEquals(system_script_extension(), '.command')
self.assertEquals(system_script_extension('Darwin'), '.command')
@patch.object(platform, 'system')
def test_returns_sh_on_linux(self, system_method):
system_method.return_value = 'Linux'
self.assertEquals(system_script_extension(), '.sh')
self.assertEquals(system_script_extension('Linux'), '.sh')
@patch.object(platform, 'system')
def test_returns_sh_by_default(self, system_method):
system_method.return_value = 'Random OS'
self.assertEquals(system_script_extension(), '.sh')
self.assertEquals(system_script_extension('Random OS'), '.sh')
if __name__ == '__main__':
unittest.main()