This started out life as a simple web app that solved a problem that I have been experiencing. I need a good way to keep track of the stuff on the web that I have read. The solution is obvious - write a web app in Python, host it on Heroku, keep the data in MongoDB, and use some authentication scheme that is based on a whitelist of allowed users.
Functionality | Component(s) |
---|---|
Web Application | flask |
Test Environment | unittest, mock |
Persistence | pymongo |
Authentication | Flask-OpenID |
Packaging | setuptools, pip |
This section describes some of the common development tasks and the tools that are used to accomplish them.
Pull source code from repository.
Setup a virtual environment using virtualenv:
readit$ virtualenv --no-site-packages --quiet env readit$ source env/bin/activate (env) readit$
Use pip to install the packages listed in requirements.txt as well as those in dev-requirements.txt:
(env) readit$ pip install -r requirements.txt ... lots of output (env) readit$ pip install -r dev-requirements.txt ... lots more output
Run the tests to make sure everything is set up correctly:
(env) readit$ nosetests
You can run tests without installing py.test or nose though I would highly recommend installing the latter. You do need to install mock since the unit tests themselves depend on it. Once you have mock installed, you can run the unit tests using the unittest module directly:
(env) readit$ python -m unittest tests ..................................................................... ------------------------------------------------------------------------- Ran 92 tests in 0.200s OK (env) readit$
A more realistic development environment, and the one that I use personally, includes installing the nose and coverage packages and running nosetests religiously. This will ensure that additional code is at least executed during the test process. The tests should not take more than a few seconds to execute and you get a warm and fuzzy feeling when the "Missing" column is still empty at the end of the day:
(env) readit$ nosetests .................................................................... Name Stmts Miss Cover Missing --------------------------------------------------- readit 7 0 100% readit.flaskapp 141 0 100% readit.helpers 42 0 100% readit.json_support 54 0 100% readit.mongo 54 0 100% readit.reading 45 0 100% readit.user 46 0 100% --------------------------------------------------- TOTAL 389 0 100% ---------------------------------------------------------------------- Ran 100 tests in 3.787s OK
Astute readers may have noticed that the number of tests executed between the minimalistic environment and nosetests differs. This is expected. Nose also executes the any defined doctest blocks.
In addition to Python code, the application contains a bit of Javascript and HTML that implements the browser-based interface. Just like Python code, Javascript requires testing or it feels shoddy to me. After quite a bit of playing around, I have settled on using the QUnit framework developed by the same folk that brought us jQuery. I load both of them dynamically from the appropriate CDNs so testing is easy. Simply open /javascript/test.html in a web browser. This will run a bunch of Unit Tests and present a nice test report in your browser. It should show a nice green bar that means success.
The next step is to run a coverage tool on the Javascript code. That took a little more work but the JSCover tool was surprisingly easy to integrate into the chain. It requires that you have Java installed. If you do, then you can run the following command:
(env) readit$ java -jar tools/JSCover-all.jar -ws --port=9001 \ --document-root=javascript --report-dir=reports \ --no-instrument=test --no-instrument=ext
Then point a web browser at https://localhost:9001/jscoverage.html?test.html
and you should be shown the QUnit page as a frame in the JSCover UI. The
Summary tab will show you the breakdown of unit test coverage per Javascript
file. The coverage report is saved off in the reports
child of the
project root.
todo: | one day soon I will take the time to write a setup.py extension that automates this! |
---|