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
Testing your web app with TDD
By Andrea Stagi
Testing your web app with TDD
- 1,485