Test Driven Development with Python

A very popular methodology when programming with Python is Test Driven Development, also known as TDD. The concept of TDD is creating your tests first, before you even begin to write your program code. The belief is that by writing proper tests before writing application code, you can focus on each part of the application separately. This speeds up development by ensuring the program will act and respond as expected by the results of the test and forces the programmer to maintain a strict pattern in their code, perhaps keeping the code more concise and readable.

Harry Percival has an very long and detailed e-book that you can read online for free. In his book, Test-Driven Development with Python, he discusses the long standing tradition of a testing goat as the unofficial mascot of the TDD community.

Obey the Testing Goat. Do Nothing Until You Have a Test!

It seems that goats always take one step at a time and rarely, if ever, fall off a mountain.

In programming parlance, this is like a developer teetering on the side of a mountain of code. One wrong step and the whole project could come down in a terrible mud-slide.

First write the test. Ensure it fails like you know it will. Then write your application code to pass the test and return the correct response or result.

Using Django and Selenium for Unit Testing

Selenium is a computer generated web browser that automates tests and includes an entire host of tools you can use to test your applications.

Selenium has both an Firefox add-on IDE that will perform record and playback with the web browser.

And on the programming side, we use the Selenium web driver. We use the web driver to create our web application automation tests in Python. And then run the tests in the Python shell or terminal.

To install selenium... use PyPi.

pip install selenium  
...
...
Successfully installed selenium-2.48.0  

To begin writing tests, fire up your preferred text editor. I'm using Atom. Atom is a great text editor. You can load packages directly into your project, it's highly customizable and your code looks beautiful in the syntax highlighting. I love to read Python code in Atom. It's my favorite past-time.

Ok, let's begin by writing our first test.

We'll name this file: djangofirsttest.py

from selenium import webdriver  
brw = webdriver.ChromeDriver()  
brw.get('http://localhost:8000')  
assert 'Django' in brw.title, "Browser title is " + brw.title  

That's it. This test will open a Chrome web browser session and attempt to connect to 'http://localhost:8000'. The test will then check to see if the word Django is in the page title.

If we were to run this Python script in a terminal, we would most certainly get an AssertionError due to the fact that we have not started up our new Django project; nor is there a web server running on our localhost at the specified address.

To Write Storyboards or to Not Write Storyboards

In the book, Test Driven Development with Python, Harry mentions using a human readable story to capture what you want your application to do and what the user would expect to see. Write the story just like you were telling it to a friend. This will allow you to focus on the specific parts of the app while you develop the application.

In our project storyboard, Melissa (fictional student) is a recently graduated college student trying to find employment and burdened with a mountain of Federal Student Loan debt. She needs to find a way to solve her student loan debt problems before she gets too far behind and things get out of control. She heard about this great new tool online. It's called 'Student Loan Repayment'.

To begin, she visits the website.

from selenium import webdriver

brw = webdriver.ChromeDriver()

# Melissa visits Student Loan Repayment and goes to check out it's homepage.
brw.get('http://localhost:8000/accounts/register/')

# Melissa notices both Sign-Up and a Login links in the page header.

assert 'Sign Up' in brw.title

# Melissa clicks on the sign up links

# Melissa completes the sign up form

# She receives an email to activate her website registration

# She activates her membership

# She uploads her National Student Loan data file

# The upload is complete

# Solutions are presented to Melissa

# Melissa chooses appropriate solutions for deferment or forbearance

# Melissa saves thousands and thousands of dollars
# by using this new service.

brw.quit()  

That is our story for this project. Our tests will be written as each step of the process. From the first browser session to check the status of our web server all the way to the end of the story where our student is thrilled to have saved tons of money on her Federal Student Loans.

Standard Library - Python's UnitTest

Python's standard library helps us organize our tests into classes. These classes inherit from UnitTest.TestCase.

Next, we define our methods to be called. Any method starting with the word test is a valid unit test and will be run automatically be the test runner.

The format for using UnitTest.

from selenium import webdriver  
import unittest

class NewStudentRegistration(UnitTest.TestCase):  
    def setUp(self):
        self.browser = webdriver.ChromeDriver()
        self.browser = implicitly_wait(3)

    def tearDown(self):
        self.browser.quit()

    def test_can_sign_up_to_register(self):
        # Melissa checks out a new student loan website
        self.browser.get('localhost:8000/accounts/register/')

        # Melissa notices the page title mentions Django
        self.assertIn('Sign Up', self.browser.title)
        self.fail('Finish test and tear down')

        # She completes the sign up form
        # ... rest of the story ...  


if __name__ == '__main__':  
    unittest.main()

Now when we call unit test and the test fails, we get a much cleaner AssertionError and the web driver session cleans up the running browser window.

Failures as Expected Results

Yes, expect to fail!

Yes, you read that right. A big part of proper testing is failing. Failing and failing until you can fail no more. I know it can seem frustrating. All of those pesky AssertionError's. But if you keep up your many failures, eventually testing will leave you with nothing but

OK!

And OK is good enough...

Craig Derington

Espousing the virtues of Secular Humanism, Libertarianism, Free and Open Source Software, Linux, Ubuntu, Terminal Multiplexing, Docker, Python, Flask, Django, Go, MySQL, PostgreSQL, MongoDB and Git.

comments powered by Disqus