Skip to main content

Testing Django forms with pytest parameterization

Working on a largish Django project, I have to test a lot of web forms. My basic approach is to put data into the form and check if it validates. I started out using separate tests for valid and invalid input data, also thinking about for loops to handle different data sets. But you don't want to do that.

Pytest's Parametrizing offers a really neat and concise solution to this problem. Consider this simple example:

from django import forms

import pytest

class ExampleForm(forms.Form):
    name = forms.CharField(required=True)
    age = forms.IntegerField(min_value=18)

    'name, age, validity',
    [('Hugo', 18, True),
     ('Egon', 17, False),
     ('Balder', None, False),
     ('', 18, False),
     (None, 18, False),
def test_example_form(name, age, validity):
    form = ExampleForm(data={
        'name': name,
        'age': age,

    assert form.is_valid() is validity

Here, three values are parameterized: the input for the two form fields and, given this data, whether or not the form should validate or not.

When working in a TDD-style, I start with one test case, code the form logic, then add the next test case by adding just one line of parameters. And repeat.

If you think there is a better way to test Django forms, please drop me a line on the Twitter!

Update 10/10/2017: pytest parameter matrices

DreamHost is awesome!

There are many crappy hosting providers. In fact, almost all of them are crappy.


DreamHost is awesome!

In their standard shared-hosting plan they provide:

  • unlimited webspace
  • unlimited MySQL databases
  • unlimited email accounts
  • unlimited user accounts
    • with separate web spaces
    • with SSH access
  • free LetsEncrypt SSL certificates for every domain and subdomain
  • up-to-date PHP and MySQL versions
  • excellent and friendly customer support

(to be continued…)

They do offer domain registration as well, but I prefer using Hover for this. In cases where everything else is on DreamHost, I just switch the domain's name servers to the DreamHost name servers. And it just works.

By now, I recommend DreamHost to everyone who asks, especially to all my clients.

List comprehensions for physicists

A list comprehension provides a compact way of mapping a list to another list by applying a function to each of the elements of the list.

From "Dive Into Python 3"

Yeah. Well. Talk English to me! Please.

Until recently I had no clue what list comprehensions do or how to use them or what they're good for. Reading Dive Into Python 3 about two years ago, without much prior programming knowledge, maybe wasn't the best idea I ever had.

Today I know quite a bit more about programming in general and about Python in particular. Still, list comprehensions kept eluding me until I read From List Comprehensions to Generator Expressions by the inventor of Python himself, Guido van Rossum.

In this article, he starts by showing how lists are stated in mathematics. As a physicist, an expression like

$$ \{ x \in \mathbf{N}\ |\ x < 10 \} $$

is perfectly clear to me. It's the set of all natural numbers that are smaller than 10, i.e.:

$$ \{1, 2, 3, 4, 5, 6, 7, 8, 9\} $$

depending on your preference of zero being a natural number or not.

A python list of these integers would be:


Und suddenly, everything became clear to me.

[f(x) for x in S if P(x)]

is the list of the function values $f(x)$ of all $x$ in the set $\mathbf{S}$ that fulfill the condition $\mathbf{P}$.

Using a Python list comprehension, the first mathematical expression above reads:

[x for x in N if x < 10]

Of course, this list is not possible, because $\mathbf{N}$ is infinite.

The function value $f(x)$ is very handy as well. The set of the squares of all integers between 1 and 4:

$$ \{ y\ |\ y = x^{2}; x \in \mathbf{N}; 0 < x < 5 \} $$

expressed via a list comprehension is:

[x**2 for x in [1,2,3,4]]

Looking at list comprehensions in mathematical terms helped me understand them. It's a great example of one and the same idea expressed differently in different fields and thus more or less understandable by people in that field.

Run Selenium from PyCharm

In order to run the Selenium WebDriver from within PyCharm, you need to explicitly add the path of the browser driver executable.

It is in fact not necessary to add the PyCharm Selenium plugin.

My PyTest fixture for the Selenium browser now looks like this:

from selenium import webdriver
import pytest

def browser():
    browser = webdriver.PhantomJS(executable_path="/usr/local/bin/phantomjs",
                                  '': 'false',

    yield browser


I'm using PhantomJS as a headless browser because it is way faster than Firefox or Chrome. I wish I could disable all loading of CSS files.

Update 2017-01-18

Putting the explicit path to the browser driver executable into the test fixture causes problems when I run the same test on Snap CI. A better solution is to add the directory of the (PhantomJS) executable into PyCharm itself.

In my PyCharm Community Edition 2016.2 I go to Run >> Edit Configurations…, select the run configuration that runs my functional test and put


into Environment variabls. That's all. Now PyCharm finds the executable of PhantomJS in that directory and I can remove the executable_path line from the fixture.

I do not know why PyCharm doesn't just import the $PATH from my shell.

Disable CSRF validation when unit-testing a Flask app!

In my current Flask project, I wanted to test a view method that includes a form with a POST request. But it wouldn't work.

The view method looks something like this:

@tennis.route('/tournaments/add', methods=['GET', 'POST'])
def add_tournament():
    form = AddTournamentForm()

    if form.validate_on_submit():
        t = Tournament(

        flash('A tournament was added to the database.')
        return redirect(url_for('tennis.index'))

    return render_template('tennis/add_tournament.html',

Flask-Testing provides a TestCase class with several useful assert methods. So the test would look like this:

from flask_testing import TestCase

class TestTennisViews(TestCase):    

    def test_tennis_add_tournament(self):
        # WHEN submitting correct form data to the add_tournament view
        r ='/tennis/tournaments/add',
                                 name='Chicharito Open',

        # THEN it redirects to the tennis overview page
        # and it shows a message
        self.assertMessageFlashed('A tournament was added to the database.')
        # and there is a tournament in the database
        assert Tournament.query.count() > 0
        # and it has the correct tournament
        t = Tournament.query.first()
        assert == 'Chicharito Open'

assertRedirects checks if the status code is 301 or 302. In my tests, it was always 200, and the test failed. Of course, nothing was added to the database, either.

I googled a lot, without success. I tried all combinations of the following keywords, among others:

unit test
POST request

The solution hit me when taking a break (it works!). It lies in the form.validate_on_submit() part of the view function. validate_on_submit() does two things:

  1. It validates the form fields according to the validators specified in the form definition.
  2. It validates a CSRF token.

Guess what: there is no CSRF token generated when executing a POST request directly in the test.

The solution is easy, as FLASK-WTF provides a configuration option to disable CSRF:



class TestingConfig(Config):
    TESTING = True


class FunctionalTestingConfig(TestingConfig)

Of course, only disable CSRF in your test configuration! I created a separate configuration for my functional tests, so that CSRF is enabled in those. When using the browser with Selenium, the CSRF token is provided by the WTForms form on the web page.

Testing Bootstrap select dropdown field with Selenium (Python)

I am building a Flask app that uses the Bootstrap CSS framework. Forms fields are generated with WTForms via the Flask-WTF extension.

One of my forms includes a select field:

<div class="form-group ">
    <label class="control-label" for="surface">Surface</label>

    <select class="form-control" id="surface" name="surface">
        <option value="clay">Clay</option>
        <option value="hard">Hard</option>
        <option value="grass">Grass</option>

My functional tests with Selenium went fine until I changed the desired test value from the default ("Clay") to a different option ("Hard"). (duh!!) So I knew I did something wrong and looked for the correct way to choose an option from a select field.

All the tests use the Selenium WebDriver.

from selenium import webdriver

browser = webdriver.Firefox()

How it doesn't work

Via Google and Stackoverflow I found several possible solutions, none of which worked for me.
 My favorite one uses the Selenium Webdriver Select class:

from import Select

select = Select(browser.find_element_by_id('surface'))

Then there are solutions using the find_element_by_xpath method:

option = browser.find_element_by_xpath("//select[@id='surface']/option[@value='hard']")

or find_element_by_css_selector:

option = browser.find_element_by_css_selector("select#surface > option[value='hard']")

and one other looking for the option fields:

dropdown = browser.find_element_by_id('surface')
for option in dropdown.find_elements_by_tag_name('option'):
    if option.text == 'Hard':

Apparently, Bootstrap does not play with Selenium. Or whatever.

How it does works

I don't remember where I got the idea to my solution, but it's simple and obvious. It selects the dropdown menu, and then types in the text that I want to click. Then hits enter and voilá, the desired option is selected.

from selenium.webdriver.common.keys import Keys

dropdown = browser.find_element_by_id('surface')

If you have a better way to select an option in a Bootstrap select field with the Selenium WebDriver, then please tell me!

serving society

Dude. I service society by rocking! I'm out there on the front lines, liberating people with my music. Rockin' ain't no walk in the park, lady!

Jack Black in "School of Rock"

Tolle Dinge in Deutschland

Im Juli waren wir auf Deutschlandbesuch. Dort sind mir natürlich viele gute Dinge aufgefallen. Gute Dinge, die es teilweise nur dort gibt, und sicher nicht in Mexiko:

  • Sauberes und leckeres Wasser direkt aus der Leitung
  • Lecker Brötchen
  • Filterkaffee
  • Lecker Bier
  • Döner Kebap
  • Stille
  • Waldwege
  • Sofortwarme Duschen
  • Lecker Brot
  • Lecker Kuchen
  • Bier auf der Straße trinken
  • Öffentliche Mülleimer
  • Zwiebelmett

Was es nicht gibt:

  • Essensstände im Park

Too Good To Be Good

This New York Times article makes some very good observations hat are not only true for the "upscale Polanco neighboorhood":

Waiters will grab a plate from under your fork.


30 servers searched for someone to serve among the restaurant’s 15 diners.


In other words: Behind every obsequious waiter is an arrogant customer.

Some interesting information I didn't know before but, judging from my own observations, have no doubt are true:

The World Economic Forum says Mexico has the second highest level of income inequality among 35 nations in the Organization for Economic Cooperation and Development. The 10 percent of Mexico’s population with the highest income use more than one-third of the nation’s resources, while the bottom 10 percent use less than 2 percent.

That's one crazy country.