Hello, readers! I have been pretty heads down over the last few months focusing on learning everything there is to know about the technology and philosophy behind CI/CD and how it can be leveraged to reduce lead times and increase the quality/reliability of services that we are delivering for our customers. I have started a continuous series called “Tales of a Salesforce DevOps Engineer” that goes in depth into the various things I have been and continue to learn about.
For this post, we are going to focus on writing a unit test in Python.
As you may or may not know, CI/CD often requires some level of scripting to achieve any file parsing or evaluation such as differential deployments or evaluating exported command outputs from yaml and customizing the way we are handling those outputs. Typically, the most common method of scripting is written in either Bash or Python. In this case, we will assume that we are using Python for our scripting needs. Side note, this is also just applicable to Python in general and its always good practice to write unit tests to execute against any functionality you write to assert that your logic is working as intended. Let’s dive in.
First, I want to point out that we are using the unittest python testing framework which happens to be my favorite (I’ll explain why in a bit). We will use this framework to implement a great testing pattern for our python application.
So let’s build the python application that we want to test. For the sake of time, we’re going to build something relatively simple so its very clear on what each piece is doing and we will walk through all of the steps. Below, our python file will create a simple directory.
#currently in python file called main.py # miscellaneous operating system interfaces import os # function that will create a sample directory called 'sample dir' def create_sample_directory(): if not os.path.exists('sample_dir'): os.mkdir('sample_dir') if __name__ == '__main__': create_sample_directory()
To summarize in pseudo, in this file above we are invoking a function called create_sample_directory() and within this function we are first checking to see if the directory exists. This is useful if there is a chance that another iteration through the python file could encounter this line again and we can be defensive so that it doesn’t throw an error. If the path doesn’t exist, then we simply generate a new folder called ‘sample_dir’. Easy enough. The first thing I would do here is check to see if the directory gets created. The easiest way to do that is navigating to the terminal within your working copy and running this command:
Here, you can confirm that the directory does actually get created.
This is great! Our application is working as expected. However, what if we could take this a step further and actually assert that this file exists upon running the python application? Let’s take a look at this code. There is quite a bit to unpack here.
# miscellaneous operating system interfaces import os # import the unittest python framework import unittest # high level file operations import shutil # import the function from the python main file from main import create_sample_directory class TestCreateSampDir(unittest.TestCase): def test_create_sample_directory(self): create_sample_directory() self.assertTrue(os.path.exists('sample_dir', 'the function above should have generated the sample directory') # once we are finished with our test, be sure to purge the directory your test class is generating try: shutil.rmtree('sample_dir') except: 'Deleting the directory was unsuccessful' if __name__ == '__main__': unittest.main()
First point, why are we importing all of these frameworks and what are they doing for us. os is being used very similarly to its usage in the main.py file in that its just confirming for us if the folder actually exists after the function is run. unittest is the python testing framework of choice. It’s important to note that this is not the only option for testing. shutil is a great library for folder/file manipulation that we will be using for removing the directory after its been created by the test. from main import create_sample_directory is our way of importing the functionality in question into our test file so that it can be evaluated and asserted against. There is a lot here regarding where your files live within the directory and how to successfully import them in various situations where the main python file and the test file do not live within the same directory. You can read more about Absolute and Relative Imports here.
Now that we have our imported modules and frameworks, we can start declaring the patterns needed by unittest to evaluate the functionality. Next, we declare a class called TestCreateSampleDir and pass it the unittest.TestCase parameter and create our function that we want the test class to evaluate. First important note is that when using the unittest framework, all functions should begin with the keyword test_ in order to be evaluated.
Next, we call our imported function which will generate the new directory. Now we utilize the self parameter that is defined in the function signature for the test method. What this allows us to do is use various types of asserts against what we are trying to test against. Take a look at this:
self gives us an assortment of options on how we actually want to assert against our functionality living in main. In this case, we are using the self.assertTrue() to determine if the file was actually generated by our function.
Finally, our only other procedure is to delete the directory that our test just generated using the shutil module.
Now that we have successfully written our assert, we will be notified of its success/failure in the output result of the test. You should see something like this after running your test file:
Congratulations! You have successfully created your first python tests. The benefits of unit testing go longer than simply confirming/validating your functionality after its written. This is a really good opportunity to use a test driven development approach and I know it has saved me a ton of time vetting out some of my ideas and how I picture my functionality working.
As always, please let me know if you have any questions. Happy Coding!