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.31Mocking 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
Testing your web app with TDD
By Andrea Stagi
Testing your web app with TDD
- 1,661