Kensuke Kousaka's Blog

Notes for Developing Software, Service.

Unit testing Python Flask program using nose

This article describes how to implement Python unit testing using nose, which can easily implement unit testing.

Prepare sample Web app

Preparing sample app based on this article to implement codes for unit testing.

Probably file structure of sample app like following.

- HelloFlask/
  - FlaskApp/
    - app.py
    - static/
        - css/
            - sample.css
        - js/
            - sample.js
        - templates/
            - index.html
            - login.html

Unit testing using nose

Install nose

Run following command to install nose.

# pip install nose

Create unittest

Create Tests directory under HelloFlask/. Put testing program there.

- HelloFlask/
    - FlaskApp/
        - app.py
        - static/
            - css/
                - sample.css
            - js/
                - sample.js
            - templates/
                - index.html
                - login.html
        - Tests/

Create test code, file name as test_access.py, under Tests/ directory by following.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from nose.tools import eq_, ok_
from FlaskApp import app

app.testing = True
client = app.app.test_client()


def test_get_index():
    res = client.get('/')
    eq_(302, res.status_code)
    ok_('/login' in res.headers['Location'])

app.testing = True to set running mode of Flask as testing, and client = app.app.test_client() to get test client prepared in Flask. Test codes are placed inside test_get_index method. Get / by test client and receive result for requesting, and check conditions by using eq_ and ok_. This sample app implements authentication system, automatically redirect to /login page if not logged in when access to other than /login. For this reason, check status code equals to 302 (redirection) and redirect url contains /login.

After implementing test codes, move into project root directory, under HelloFlask/, and run nosetests command. After running command, you got following output.

----------------------------------------------------------------------
Ran 1 test in 0.080s

OK

When run nosetests command, recognizing file under current directory that name contains test or Test as test program, and run method that name starts with test or Test as testing. Above sample output reported that ran one test code and result was ok.

Next, write failing test code on purpose. Append following codes to test_access.py.

def test_fail_get_index():
    res = client.get('/')
    eq_(200, res.status_code)

After appending codes, move into project root directory and run nosetest again. You should got output which was different from previous output, that contains FAIL: test_access.test_fail_get_index, Traceback text, AssertionError: 200 != 302, and FAILED (failures=1). These meaning is that test_fail_get_index test method of test_access.py was failed because res.status_code not equals to 200.

Remove this method after you checked, and edit test_access.py by following.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from nose.tools import eq_, ok_
from FlaskApp import app
import json

app.testing = True
client = app.app.test_client()


def test_get_index():
    res = client.get('/')
    eq_(302, res.status_code)
    ok_('/login', in res.headers['Location'])


def test_get_login():
    res = client.get('/login')
    eq_(200, res.status_code)


def test_fail_login():
    res = client.post('/login', data={
        'username': 'testuser',
        'password': 'testpassword'
    })
    eq_(200, res.status_code)


def test_login():
    res = client.post('/login', data={
        'username': 'admin',
        'password': 'testpassword'
    })
    eq_(302, res.status_code)


def test_post_hoge():
    res = client.post(
        '/postText',
        data=json.dumps(dict(text='hoge')),
        content_type='application/json')
    eq_(200, res.status_code)
    data = json.loads(json.loads(res.data.decode('utf-8'))['ResultSet'])
    eq_('hoge', data['result'])


def test_post_HOGE():
    res = client.post(
        '/postText',
        data=json.dumps(dict(text='HOGE')),
        content_type='application/json')
    eq_(200, res.status_code)
    data = json.loads(json.loads(res.data.decode('utf-8'))['ResultSet'])
    eq_('hoge', data['result'])


def test_post_ping():
    res = client.post(
        '/postText',
        data=json.dumps(dict(text='ping')),
        content_type='application/json')
    eq_(200, res.status_code)
    data = json.loads(json.loads(res.data.decode('utf-8'))['ResultSet'])
    eq_('pong', data['result'])


def test_logout():
    res = client.get('/logout')
    eq_(302, res.status_code)

You can check functions working correctly or not with these test codes.

This sample app is published at k3nsuk3/HelloFlask