This repository has been archived by the owner on Aug 7, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathPythonFlaskTesting.txt
444 lines (355 loc) · 16.4 KB
/
PythonFlaskTesting.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
How to Test and Deploya Python Web App
An Opinionated Tour of Testing Tools
2
Bear (Mike Taylor)
@codebear
Site Reliability Engineer at CircleCI
Using Python to build, test and deploy applications at:
&Yet
Mozilla
Seesmic
Open Source Applications Foundation
hello, i'm bear
my job at CircleCI is the operations side of the Site Reliability Engineering team
I've been using python to develop applications and ops tools for many decades and when I joined CircleCI I naturally started to use circleci in my python projects.
One of my goals is to make Circle CI a best-in-class tool for Python Continuous Integration and to do that I decided to create a simple web application and then apply the current best practices for testing and deploys to that project.
This talk is the result of that research
This talk will not be
a deep dive into any one area of testing
a debate on which style, framework or methodology is better
This talk will be
Opinionated
A list of what tools are available
A collection of resources available
Useful (hopefully)
3
This talk will not be a deep dive into any one area, nor will it be a battle-of-the-frameworks.
What it will be is an opinionated list of what tools and practices I consider useful for developing and deploying a modern python web application.
At the end of the talk my goal is to have given you some ideas and thoughts about python testing, even if you don't agree with them.
4
Tenki - A Weather Service
Flask Web site & API that returns the weather
https://github.com/bear/tenki
OpenWeatherMaphttp://openweathermap.org/api
PyOWM - Python wrapper for the OpenWeatherMap APIhttps://github.com/csparpa/pyowm
In order for this to be in any way useful, you have to be able to recreate what i'm talking about, not just be shown a deck of slides and some notes.
To that end I created a simple Flask app that uses OpenWeatherMap and the python module PyOWM to return the weather for a given zip code.
This will require a Flask code framework, an API and also will require calling out to an external service for data - all of which need to be tested.
Note that I could have created this with Django or Bottle for the app, but I found Flask to be very minimal in it's requirements.
5
Developer Testing
Component Testing
Integration Testing
Acceptance Testing
System Testing
Python Web App Testing Path
Fast Tests
Slow Tests
Developer
CI
This is the path that any app travels while being tested.
The flow is from the Developer, thru components (aka UI), then on to Integration testing and finally into more formal acceptance and system testing.
The area of responsibility is divided between the Developer and your Continuous Integration solution.
This slide also points out that testing must be very fast in order for it to be useful.
The slower tests are reserved for the parts of the path that do not see a lot of change.
6
Developer Testing
Must be very fast
Started via Makefile
pytest
Must be fast
pre-commit hooks
PyEnv -- NO System Python
Able to run specific test(s)
Did I mention fast
Let's take a look at Developer Testing, which is probably the most interesting part for everyone here.
To be honest this part is the least interesting part for me as an Ops person, except when it fails and the CI starts failing and every developer starts posting their favorite version of "works for me"
(switch to slide 7)
7
Developer Testing
Must be very fast
Started via Makefile
pytest
Must be fast
pre-commit hooks
PyEnv -- NO System Python
Able to run specific test(s)
Did I mention fast
and over in ops we usually start responding with (switch to slide 8)
8
Developer Testing
Must be very fast
Started via Makefile
pytest
Must be fast
pre-commit hooks
PyEnv -- NO System Python
Able to run specific test(s)
Did I mention fast
9
Developer Testing
Must be very fast
all commands run from a Makefile
PyEnv -- NO System Python
pytest
pre-commit hooks
Did I mention fast
If even a few of the items I show you today are followed, then we can start to bring together the dev and ops teams ...
The first step towards a sparkly devops princess future is the Makefile
10
Makefile
clean:
python manage.py clean
lint: clean
pycodestyle --config={toxinidir}/setup.cfg
test: clean
python manage.py test
integration:
python manage.py integration
coverage: lint
@coverage run --source=tenki manage.py test
@coverage html
@coverage report
info:
@python --version
@pip --version
@virtualenv --version
ci: info test integration coverage
@CODECOV_TOKEN=`cat .codecov-token` codecov
all: clean integration coverage
install-hook:
git-pre-commit-hook install --force --plugins json --plugins yaml \
--plugins flake8 --flake8_ignore E111,E124,E126,E201,E202,E221,E241,E302,E501,N802,N803
The makefile is where we document what steps are taken to setup an environment, what steps are needed for each test type and also what steps are needed for deploys.
Here is where we discover what is required to get a clean test environment
What is required to run lint and the different test types
here is also where we document the steps required to setup the development environment.
All which are essential for current developers and also to enable new developers to come up to speed.
(show the clean, lint, test and info targets)
The info target is useful to help document the environment currently being run which is useful when you are looking back in history to find out what has changed.
11
manage.py (a Flask-Script)
import sys
from flask.ext.script import Manager, Server
from flask.ext.script.commands import Command, ShowUrls, Clean
from tenki import create_app
def test(marker="not web and not integration"):
test_args = ['--strict', '--verbose', '--tb=long', 'tests', '-m', marker]
import pytest
errno = pytest.main(test_args)
sys.exit(errno)
class Test(Command):
def run(self):
self.test_suite = True
test()
class Integration(Command):
def run(self):
self.test_suite = True
test(marker=”integration”)
class WebTests(Command):
def run(self):
self.test_suite = True
test(marker="web")
app = create_app('tenki.settings.%sConfig' % env.capitalize())
manager = Manager(app)
manager.add_command("test", Test())
manager.add_command("integration", Integration())
manager.add_command("webtest", WebTests())
both Flask and Django make use of an management script to run the app.
The role of manage.py is to put into a single location what checks are needed before starting the app and what options are available to someone wanting to run the app.
We are going to make use of this to define some custom commands that will be used to start the unit, web and integration tests.
(Show the Test, Integration and WebTests class def’s and talk about test_suite = True)
12
pyenv -- https://pypi.python.org/pypi/pyenv
bear@opus ~/circleci/tenki
$ pyenv virtualenv 2.7.10 tenki27
New python executable in /usr/local/var/pyenv/versions/2.7/envs/tenki27/bin/python2.7
...
bear@opus ~/circleci/tenki
$ pyenv local tenki27
pyenv-virtualenv: deactivate
pyenv-virtualenv: activate tenki
(tenki27)
bear@opus ~/circleci/tenki
$ git clone [email protected]:bear/tenki.git .
(tenki27)
bear@opus ~/circleci/python_testing/tenki (master %)
$ make update-all
We need to ensure that the developers and qa/ci environments are *identical*
To do that we use PyEnv to define the virtualenv and to also ensure that it is active for the project.
This removes the one single largest cause of CI/CD pipeline failure ... a developer's laptop having multiple changes to the system's python.
13
pytest -- http://pytest.org/latest/
runs on Posix/Windows, Python 2.6-3.5, PyPy
Mature testing framework that has no boilerplate,
Doesn't require special syntax for assertions,
Tests can be written as xUnit classes or as functions,
pytest can run tests written using nose, unittest, and doctest.
Marking test functions with attributes,
Skip and xfail
pytest is another key point along our testing path
It allows us to create tests without a lot of boilerplate, to use assertions as they are normally written
to be able to run xUnit, doctests and other test formats in a standard manner.
pytest also gives us the ability to mark (using attributes) different tests as web, integration or unit tests which will come in handy later
14
git pre-commit -- https://pypi.python.org/pypi/git-pre-commit-hook
built-in plugins for:
validate json files
validate Python-code with flake8
validate Python-code with frosted
validate .rst files with restructuredtext_lint
validate .ini files with configparser
validate .yaml files with PyYAML
validate .xml files with xml.etree.ElementTree
check filesize
http://trixie-j-lulamoon.deviantart.com/art/You-shall-not-pass-424148297
pre-commit hooks are essential to enforcing that our tests, lint’ing and a variety of other validation steps are performed *before* the code makes it to the git repo.
This will allow everyone in the CI pipeline to feel confident that the code being tested is not going to fail from someone typo'ing a json blob or something equally face-palm-y.
The python module git-pre-commit-hook is an amazing tool for this and comes out of the box with quite a few plugins to make life more sane.
15
[check-manifest]
ignore =
circle.yml
[pycodestyle]
ignore = E111,E124,E126,E201,E202,E221,E241,E302,E501
[tool:pytest]
markers =
web: tests that require chrome webdriver
integration: tests that require mocking or external services
setup.cfg
a majority of the tools so far all can be customized for the specific environment.
The file setup.cfg is used by pytest, flake8, tox and a number of other tools to read in any overrides so it is worthwhile to keep it current.
Here we are letting flake8 know which warnings and errors it can skip reporting about.
We also are defining what markers are pesent in our tests so pytest can know how to parse the command line we give it later.
16
Component Testing
Docker Compose
nginx
uwsgi
robcherry/docker-chromedriver:latest
NOTE - Docker is not the only way to do WebUI or Component testing. Some consider it heavy-handed in that it requires developers to maintain a tech stack (which perversely re-introduces “worked on my stack” issues.)
In order to properly test the web ui you need to be able to run the app and then have it respond to requests from a web server.
In the past this required different staging or testing servers that would constantly be out of date; require that the magic phrase be uttered to the sysadmin just to get that server updated.
Fortunately now we can take advantage of Docker (and other VM environments) to create, deploy and run all of the required items on the developer's laptop...
17
uwsgi:
build: .
dockerfile: docker-uwsgi
web:
build: .
dockerfile: docker-nginx
links:
- uwsgi
expose:
- 8000
ports:
- "8000:8000"
chromedriver:
image: robcherry/docker-chromedriver:latest
links:
- web
ports:
- "4444:4444"
environment:
CHROMEDRIVER_WHITELISTED_IPS: ""
docker-compose.yml
docker-compose hides a *lot* of the complexity involved in coordinating multiple vm's - the ports involved, getting host names to match across vms, setting up network tunnels for port forwarding, etc
Here we see three docker vm's being defined: uwsgi, web and chromedriver.
uwsgi is the vm that runs the app using uwsgi; web is the vm that will run nginx which then will proxy-pass all requests to the uwsgi vm.
chromedriver is the vm that creates a headless browser environment that can be manipulated by selenium's web driver.
The build: . lets us store the docker-compose files in the same directory as our application so we can take advantage of the docker tools to inject our app into the vm's file space.
The Links, expose and ports items are all the magic items to let docker know what the networking relationship is between the vm's - it then goes off and ensures each vm has the proper port configurations.
18
docker-cleanup:
docker rm $(docker ps -q -f 'status=exited')
docker rmi $(docker images -q -f "dangling=true")
docker-build:
docker-compose build
docker-compose pull
docker-compose rm -f
docker-start:
docker-compose up -d
webtest: docker-start
$(eval DRIVER_IP := $(shell ./wait_for_ip.sh))
DOCKER_IP=$(DOCKER_IP) python manage.py webtest
docker-compose stop
makefile
of course we are going to use our Makefile to document what the specific commands we need to manage our docker environment
docker-build outlines the three steps to creating; storing and cleanup of the vm's
docker-start shows the command to get the docker environment up and running
webtest runs a bash script that determines what the docker ip address is
(this step varies between linux and OS X),
then the script waits for the exposed ports to become available and exits
we then use our management script to run the web tests and afterwords tell docker to stop
19
upstream app {
server uwsgi:8001;
}
server {
listen 8000 default_server;
server_name _;
charset utf-8;
location / {
uwsgi_pass app;
include /etc/nginx/uwsgi_params;
}
}
tenki-nginx.conf
The best part about going thru the process to deploy our app locally using nginx and uwsgi is that it's almost the exact same process involved in deploying our app in a production environment.
The nginx config shown here would only need to be tweaked a little bit - such as changing the upstream server configs and also the server_name and listen items.
but this is all information that the ops team would have to discover the hard way so having this already documented gives them a heads-up.
20
# bundle application as a tarball
BDATE=`date +%Y%m%d` git archive -o tenki_${BDATE}.tar.gz branch
# start uwsgi service
uwsgi --socket :8001 --chdir /opt/tenki --module uwsgi-app --callable application
#!/usr/bin/env python
# uwsgi-app.py
import os
from tenki import create_app
env = os.environ.get('TENKI_ENV', 'prod')
application = create_app('tenki.settings.%sConfig' % env.capitalize())
if __name__ == "__main__":
application.run()
once our application has passed the unit, integration and web ui tests it is ready for further testing downstream within the CI pipeline.
Because we have now proved that the application is functional, we can bundle the app into a named tarball and use that for any further deploys.
This removes the need for git credentials to be required.
(point out a simple method for creating the tarball)
The tarball and the uwsgi line paired with the uwsgi-app.py are examples of what could be used in acceptance and system testing of our app as a part of other pieces of the system being tested.
Our app could be an external requirement for another part of the CI/CD pipeline so this would document the steps required to get our deploy running in that test environment.
Google Cloud and AppEngine
https://circleci.com/docs/deploy-google-app-engine
gcloud -q preview app deploy app.yaml --promote --version=staging
21
Deploy
now deploys are not as simple as tossing a few files at a server and crossing your fingers.
fortunately there are already good walkthrus on how to deploy to Google AppEngine and Google Cloud Compute
Resources
22
Tox -- Run tests using different Python versions
Coverage.py -- Measure code coverage during test runs
Mock -- Replace code with mock objects
https://pages.18f.gov/testing-cookbook/python/
http://docs.python-guide.org/en/latest/writing/tests/
https://pythonhosted.org/testing/
These are tools that would normally be present in any production app’s environment but are hard to describe or demonstrate for a talk.
Tox would be used as part of the Acceptance phase of any sane CI/CD pipeline
Coverage.py is an amazing tool that looks into your code and shines a light into those dark recesses of old and unused code - all of which are breeding grounds for future bugs.
Mock you will see in this test_owm.py integration test so we can check our use of the external API without having to hammer that API (or have an network connection)
Locust.io is a python based load testing tool
The URLs shown here are very good guides to python testing that go much deeper into the details and whys and how-tos, much more than I ever could.
23
Review
Created a Developer environment that enforced good patterns
lint, unit tests, pre-commit checks, local testing
Created a component/integration test environment using Docker
This allows us to have developer and QA tests be identical
Created a Makefile to coordinate start, stop and test runs
Again, allows us to have the identical paths for Dev, QA, CI, CD
Hopefully learned something new!
24
Questions?
CircleCI Support
https://discuss.circleci.com/
Example Repo
https://github.com/bear/tenki/