My work as a web developer has introduced me to the wonders of unit testing. No one likes writing tests and neither did I for a very long time. I relied on manual testing like many others. Until I started writing unit tests in Jasmine for my web apps. The number of scenarios that you can test makes your code way more reliable.
Another advantage of testing is that quite often there are changes you would like to make but don't want to make them right now as you might break the code that is working. And in the end these changes never happen as you get too busy with other stuff. Here, test driven development can solve the problem of this inertia. Even if you do not want to change the code itself, test suites can help to totally determine every potential lapse in a block of code that will take away the fear bit by bit of finally getting around to fixing them.
With these noble thoughts, I have finally got over my laziness and decided to write tests for the circuit simulator. As a start, Python comes with the in-built unittest module for writing tests. Django uses this and creates a class TestCase to test API end points. Besides this, Python also has the pytest module that needs to be separately installed.
So, I decided to write unit tests for the command line interface with pytest and for the web app using Django's TestCase class based on unittest. In the beginning, the code for these will be separate. However, with time, the plan will be have one set of tests and also at the same time have one set of code for both web apps.
So, the directory structure for the simulator is:
README
command_line
-- circuit_solver.py
-- circuit_elements.py
-- circuit_exceptions.py
-- LICENSE.txt
-- matrix.py
-- network_reader.py
-- solver.py
web_app
-- requirements.txt
-- simulator_interface
-- LICENSE.txt
-- manage.py
-- simulation_collection
-- media_files
-- simulator_interface
-- settings.py
-- urls.py
-- wsgi.py
-- simulations
-- circuit_solver.py
-- circuit_elements.py
-- circuit_exceptions.py
-- solver.py
-- network_reader.py
-- matrix.py
-- admin.py
-- views.py
-- models.py
-- tests.py
There may be a few more directories and files, but these are the major ones at least for testing. We will create a tests directory inside command_line directory and house the command line app tests there. We will remove the tests.py file inside the simulations directory inside web_app/simulator_interface and create a tests directory for the web app tests.
To install pytest, just do:
pip install pytest
For the command line app, I decided to get started with testing by writing a very simple test for the function csv_element_2D:
This function takes a tuple/list that represents a row, column position and converts it to a string that represents the cell position. So, [0,0] will be "1A" on a spreadsheet. So, a test for this function in the the command_line app would be:
The test itself if very simple. We import the method from the file network_reader.py that has the method. The test function has to start with test_ for pytest to execute it. The asset method in pytest merely asserts that for the test to pass, the value returned by the function for a particular argument has to be equal to a certain value.
The challenge in getting this test to run was in pytest being able to find the file/module network_reader.py. For this the first block of code above the function had to be written. I had to extract the parent method and insert it into the python path so that Python will look in that directory for the module and import it.
The test for the web app was a bit easier as I didn't have to struggle with path as much. The only challenge was I was trying to use pytest and it would not work as pytest for some reason could not find django module that is imported in the web app modules. The reason I can think of is in Django 2 onwards, the app needs to be loaded and therefore, django is not available until the app is working. For this, I had to go with the default Django TestCase class.
To run these tests. Inside command_line directory, just run the command:
pytest
There are several arguments that can be passed and I will investigate as time goes on. For the web app, inside the directory that contains manage.py, run:
python manage.py test simulations
The app name simulations is important because the tests directory with the test is inside the simulations app directory.
Another advantage of testing is that quite often there are changes you would like to make but don't want to make them right now as you might break the code that is working. And in the end these changes never happen as you get too busy with other stuff. Here, test driven development can solve the problem of this inertia. Even if you do not want to change the code itself, test suites can help to totally determine every potential lapse in a block of code that will take away the fear bit by bit of finally getting around to fixing them.
With these noble thoughts, I have finally got over my laziness and decided to write tests for the circuit simulator. As a start, Python comes with the in-built unittest module for writing tests. Django uses this and creates a class TestCase to test API end points. Besides this, Python also has the pytest module that needs to be separately installed.
So, I decided to write unit tests for the command line interface with pytest and for the web app using Django's TestCase class based on unittest. In the beginning, the code for these will be separate. However, with time, the plan will be have one set of tests and also at the same time have one set of code for both web apps.
So, the directory structure for the simulator is:
README
command_line
-- circuit_solver.py
-- circuit_elements.py
-- circuit_exceptions.py
-- LICENSE.txt
-- matrix.py
-- network_reader.py
-- solver.py
web_app
-- requirements.txt
-- simulator_interface
-- LICENSE.txt
-- manage.py
-- simulation_collection
-- media_files
-- simulator_interface
-- settings.py
-- urls.py
-- wsgi.py
-- simulations
-- circuit_solver.py
-- circuit_elements.py
-- circuit_exceptions.py
-- solver.py
-- network_reader.py
-- matrix.py
-- admin.py
-- views.py
-- models.py
-- tests.py
There may be a few more directories and files, but these are the major ones at least for testing. We will create a tests directory inside command_line directory and house the command line app tests there. We will remove the tests.py file inside the simulations directory inside web_app/simulator_interface and create a tests directory for the web app tests.
To install pytest, just do:
pip install pytest
For the command line app, I decided to get started with testing by writing a very simple test for the function csv_element_2D:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def csv_element_2D(elem): | |
""" | |
Takes the [row, column] input for a csv file | |
and given a human readable spreadsheet position. | |
""" | |
# Convert column numbers to alphabets | |
csv_col = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z" | |
csv_dict = {} | |
csv_col_list = csv_col.split(" ") | |
for c1 in range(26): | |
csv_dict[c1] = csv_col_list[c1] | |
# Because row 0 doesn't exist on a | |
# spreadsheet | |
row = elem[0]+1 | |
col = elem[1] | |
# Create a list of all the alphabets | |
# that a column will have | |
col_nos = [-1] | |
# On the run, an alphabet will | |
# have a remainder and a prefix | |
# This is essentially the first and | |
# second alphabet | |
prefix = 0 | |
remdr = col | |
# The alphabet that is to be found | |
col_count = 0 | |
while remdr-25>0: | |
# If the column>26, the first | |
# alphabet increments by 1 | |
prefix += 1 | |
remdr = remdr-26 | |
if remdr<26: | |
if prefix>25: | |
# More than 2 alphabets | |
col_nos[col_count] = remdr | |
# The remainder takes the prefix | |
remdr = prefix-1 | |
# The prefix is the third/next alphabet | |
prefix = 0 | |
# Add another element to the list | |
col_nos.append(-1) | |
col_count += 1 | |
else: | |
# 2 alphabets only | |
col_nos.append(-1) | |
col_nos[-1] = prefix-1 | |
col_nos[col_count] = remdr | |
col_letters = "" | |
# The alphabets are backwards | |
for c1 in range(len(col_nos)-1, -1, -1): | |
col_letters = col_letters + csv_dict[col_nos[c1]] | |
csv_format = str(row) + col_letters | |
return csv_format |
This function takes a tuple/list that represents a row, column position and converts it to a string that represents the cell position. So, [0,0] will be "1A" on a spreadsheet. So, a test for this function in the the command_line app would be:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os,sys,inspect | |
# Inserting the parent directory command_line | |
# into the python path. Without that imports | |
# in the tests do not work. | |
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) | |
software_dir = os.path.dirname(currentdir) | |
sys.path.insert(0,software_dir) | |
def test_csv_element_2D(): | |
""" | |
Testing csv_element_2D - takes a tuple and returns the string cell position. | |
""" | |
from network_reader import csv_element_2D | |
assert csv_element_2D([0, 0]) == "1A" |
The test itself if very simple. We import the method from the file network_reader.py that has the method. The test function has to start with test_ for pytest to execute it. The asset method in pytest merely asserts that for the test to pass, the value returned by the function for a particular argument has to be equal to a certain value.
The challenge in getting this test to run was in pytest being able to find the file/module network_reader.py. For this the first block of code above the function had to be written. I had to extract the parent method and insert it into the python path so that Python will look in that directory for the module and import it.
The test for the web app was a bit easier as I didn't have to struggle with path as much. The only challenge was I was trying to use pytest and it would not work as pytest for some reason could not find django module that is imported in the web app modules. The reason I can think of is in Django 2 onwards, the app needs to be loaded and therefore, django is not available until the app is working. For this, I had to go with the default Django TestCase class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.test import TestCase | |
class CSVElement2D(TestCase): | |
""" | |
Testing csv_element_2D - takes a tuple and returns the string cell position. | |
""" | |
def test_csv_element_2D(self): | |
from simulations.network_reader import csv_element_2D | |
self.assertEqual(csv_element_2D([0, 0]), "1A") |
To run these tests. Inside command_line directory, just run the command:
pytest
There are several arguments that can be passed and I will investigate as time goes on. For the web app, inside the directory that contains manage.py, run:
python manage.py test simulations
The app name simulations is important because the tests directory with the test is inside the simulations app directory.
No comments:
Post a Comment