Test-Driven Development
Goal
Engage in test-driven development as you start on the team and individual deliverables for your command-line component of your project. Get a good start on Individual Deliverable 1.
First Though
If you haven’t finished the Git setup from the previous lab, go do that.
Overview
In this lab, you’ll practice test-driven development to get started on your first individual deliverable.
Step 1: A First Failing Test
- Accept the assignment by clicking on the
Individual Deliverable 1 Assignment
on Moodle and cloning down the repository.
In TDD, you always want to start with a failing test:
- In your Individual Deliverable 1 repository, in the
Tests
folder, make a Python file with a name that starts withtest
- Add the following boilerplate code:
import unittest if __name__ == '__main__': unittest.main()
- Verify that this runs and prints “OK”
- Try to import your production code file with the following line:
from ProductionCode.basic_cl import *
- Verify that your tests now crash
Step 2: Passing that test
Awesome, you have a failing test, great job! The reason your code is crashing is because Python is a bit odd about handling code in subdirectories, but we want to keep things nicely organized. Therefore, to run your tests for real, you will need to use some special functionality of unittest
, i.e. its ability to discover tests and handle subdirectories during that process.
- To run your tests type the following:
python3 -m unittest discover Tests/
- Verify that 0 tests have run OK
Step 3: Writing a real failing test
Okay, fine, that wasn’t very exciting and was probably taking TDD too far, but it’s a good example of how you should go about this. To get to your first real failing test case, you’ll need to make a tester class in your test file:
class TestSOMETHING(unittest.TestCase):
def test_A_GOOD_NAME(self):
""" A GOOD DOCSTRING """
# test one of the functions
(You should change the things that are all caps.)
- Write the actual test
- Verify that it fails
Step 4: Repeat
You shouldn’t write any production code for this deliverable. Instead, you should think carefully about the tests that would test the functionality of the function signatures in ProductionCode/basic_cl.py
based on the data in Data/dataset.csv
. The function signatures are also specified in the Individual Deliverable 1 details.
You should aim to complete as much of the requirements for the first individual deliverable as you can in class!
Command-line testing
Testing command-line functionality is much trickier than at the function level.
You need to use the subprocess
Python module to do so.
Here is an annotated example of a test for checking if an intro assignment has produced the correct output:
def test_caesar_normal(self):
"""Check if caesar.py works for valid command line arguments"""
#First use subprocess to make the command line call
code = subprocess.Popen(['python3', '-u', 'caesar.py', "This is the way the world ends, dontcha know?", '25'], #give it a list of the exact things you want to be "typed" on the command line, -u keeps stdout unbuffered so it goes through all at once
stdin=subprocess.PIPE, stdout=subprocess.PIPE, #you are piping stdin and stdout so subprocess can use them
encoding='utf8') #use the normal utf8 text
output, err = code.communicate() #interact with the process, which runs it, and save the things returned to output and err
self.assertEqual(output.strip(), "Sghr hr sgd vzx sgd vnqkc dmcr, cnmsbgz jmnv?") #strip extra whitespace from the output and compare it to what you think it should be
code.terminate() #stop the process to clean up after yourself