Testing your web app with TDD

 

by Andrea Stagi
Head of Development @ Lotrèk

 

Testing is writing code that tests your code

Why writing tests?

Painless refactoring

Continuous Integration

A new approach!

Write tests first, then write code to make them pass

TDD: Test Driven Development

Let's build a simple weather app

using a simple stack..

The Python part

Testing with py.test

https://docs.pytest.org/en/latest/

Better interface to write tests

Simple to use

 

Just launch 'pytest'

Lots of plugin ready

(Later.......)

from mylib.unicornprinter import uniformat


def test_uniformat():
    assert uniformat('Hello everyone!') == '🦄 Hello everyone!'

def test_uniformat_error():
    assert uniformat('Wrong!', 'E') == '🚨 Wrong!'

def test_uniformat_warning():
    assert uniformat('Pay attention!', 'W') == '⚠️ Pay attention!'

def test_uniformat_nonsense():
    assert uniformat('Nonsense type!', 'NNS') == '🦄 Nonsense type!'

test_unicornprinter.py

Code Coverage

How much my code is covered by tests?

pytest-cov has the answer!

Everything with a simple command

pytest --cov . --cov-report term-missing

Stop talking!
Let's code!

Mocking

We use a new plugin, pytest-mock

def test_get_temperature_by_city(mocker):

    mocked_resp = {
        "main":{
            "temp":279.61,
            "pressure":1071,
            "humidity":100,
            "temp_min":272.31,
            "temp_max":282.31
        },
    }

    mocked_requests_get = mocker.patch('mylib.weather.requests.get')
    mocked_requests_get.return_value.status_code = 200
    mocked_requests_get.return_value.json.return_value = mocked_resp
    temperature = get_temperature_by_city('Pistoia')

    assert temperature['temp'] == 279.61
    assert temperature['pressure'] == 1071
    assert temperature['humidity'] == 100
    assert temperature['temp_min'] == 272.31
    assert temperature['temp_max'] == 282.31

Mocking for better or worse

def test_get_temperature_by_city_connection_error(mocker):
    mocked_requests_get = mocker.patch('mylib.weather.requests.get')
    mocked_requests_get.side_effect = requests.exceptions.ConnectionError
    with pytest.raises(WeatherConnectionError):
        temperature = get_temperature_by_city('Pistoia')


def test_get_temperature_by_city_api_error(mocker):
    mocked_requests_get = mocker.patch('mylib.weather.requests.get')
    mocked_requests_get.return_value.status_code = 401
    with pytest.raises(WeatherAPIError):
        temperature = get_temperature_by_city('Pistoia')

Action!

Testing a Django app

Main view "/"

(weather-index)

from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import render


def weather_index(request):
    return render(request, 'weather/index.html')

API view "/api/temperature?city=CITY"

from django.http import JsonResponse
from django.shortcuts import render

from mylib.weather import get_temperature_by_city
from mylib.weather import WeatherAPIError


def get_temperature_from_city(request):
    try:
        temperature = get_temperature_by_city(
            request.GET.get('city', '')
        )
        return JsonResponse(temperature)
    except WeatherAPIError as ex:
        return JsonResponse(
            {
                'details' : 'Something \'s wrong, please try again!'
            },
            status=500
        )

A new pytest plugin? Yes, pytest-django!

import pytest
from django.urls import reverse


def test_main_view(client):
    url = reverse('weather-index')
    response = client.get(url)
    assert response.status_code == 200
    assert 'Weather Яeport .' in response.content.decode()

100% of coverage!

I can go home!

Uhhhm ... NO!

Testing the integration

Write tests that interact with your app... in the browser!

Integration tests with pytest-selenium

def test_page_api_integration(live_server, selenium, mocker):

    mocked_get_weather = mocker.patch('api.views.get_temperature_by_city')
    mocked_get_weather.return_value = {
        'temp' : 100,
        'pressure' : 40,
        'humidity' : 50,
        'temp_min' : 30,
        'temp_max' : 40,
    }

    selenium.get(live_server.url)
    selenium.maximize_window()

    cityText = selenium.find_element_by_id('inputCity')
    cityText.send_keys('Pistoia')
    startSearchButton = selenium.find_element_by_id('startSearch')
    startSearchButton.click()
    time.sleep(5)
    tempResultLabel = selenium.find_element_by_id('tempResults')
    assert tempResultLabel.text == 'In Pistoia there are 100°F'
    time.sleep(5)

Testing Javascript code

We need some new weapons

 A R M A

Browserify

http://browserify.org/

Old but.. let's keep things simple!

Use 'require' in your browser for...

... making a huge refactoring!

And build all with      !

Show me the code..

Jasmine

https://jasmine.github.io/

BDD test framework

(Behavior Driven Development)

function helloWorld() {
  return 'Hello world';
}

function helloDear(name) {
  return 'Hello ' + name;
}

describe('Greetings', function () {
  it('says hello to the world', function () {
    expect(helloWorld()).toEqual('Hello world');
  });
  it('says hello to someone', function () {
    expect(helloDear('Andrea')).toEqual(
      'Hello Andrea'
    );
  });
});

    arma

https://karma-runner.github.io/

Testing in multiple browsers

Testing framework agnostic

Just a configuration file to run

And a lot of plugins for coverage, html injection...

Karma

Jasmine

Our test files

Our source files

Run

Using

Import

The Big Picture

All is better with a demo... let's run!

Questions?

@astagi

@4stagi

stagi.andrea@gmail.com

📋 slides.com/andreastagi/web-app-tdd
💻 github.com/astagi/testing-web-app-tdd

Made with Slides.com