Skip to content

Commit e51137a

Browse files
author
Jon Wayne Parrott
committed
Refactoring 3-binary-data tests to use py.test
1 parent 0bfc9ba commit e51137a

File tree

5 files changed

+127
-109
lines changed

5 files changed

+127
-109
lines changed

3-binary-data/requirements-dev.txt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
tox==2.3.1
2-
unittest2==1.1.0
3-
nose==1.3.7
42
flake8==2.5.4
5-
coverage==4.1b2
6-
BeautifulSoup4==4.4.1
7-
mock==1.3.0
8-
flaky==3.0.3
3+
flaky==3.1.0
4+
pytest==2.8.7
5+
pytest-cov==2.2.1

3-binary-data/tests/conftest.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright 2015 Google Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""conftest.py is used to define common test fixtures for pytest."""
16+
17+
import bookshelf
18+
import config
19+
from gcloud.exceptions import ServiceUnavailable
20+
import pytest
21+
22+
23+
@pytest.yield_fixture(params=['datastore', 'cloudsql', 'mongodb'])
24+
def app(request):
25+
"""This fixtures provides a Flask app instance configured for testing.
26+
27+
Because it's parametric, it will cause every test that uses this fixture
28+
to run three times: one time for each backend (datastore, cloudsql, and
29+
mongodb).
30+
31+
It also ensures the tests run within a request context, allowing
32+
any calls to flask.request, flask.current_app, etc. to work."""
33+
app = bookshelf.create_app(
34+
config,
35+
testing=True,
36+
config_overrides={
37+
'DATA_BACKEND': request.param
38+
})
39+
40+
with app.test_request_context():
41+
yield app
42+
43+
44+
@pytest.yield_fixture
45+
def model(monkeypatch, app):
46+
"""This fixture provides a modified version of the app's model that tracks
47+
all created items and deletes them at the end of the test.
48+
49+
Any tests that directly or indirectly interact with the database should use
50+
this to ensure that resources are properly cleaned up.
51+
52+
Monkeypatch is provided by pytest and used to patch the model's create
53+
method.
54+
55+
The app fixture is needed to provide the configuration and context needed
56+
to get the proper model object.
57+
"""
58+
model = bookshelf.get_model()
59+
60+
ids_to_delete = []
61+
62+
# Monkey-patch create so we can track the IDs of every item
63+
# created and delete them after the test case.
64+
original_create = model.create
65+
66+
def tracking_create(*args, **kwargs):
67+
res = original_create(*args, **kwargs)
68+
ids_to_delete.append(res['id'])
69+
return res
70+
71+
monkeypatch.setattr(model, 'create', tracking_create)
72+
73+
yield model
74+
75+
# Delete all items that we created during tests.
76+
list(map(model.delete, ids_to_delete))
77+
78+
79+
def flaky_filter(e, *args):
80+
"""Used by flaky to determine when to re-run a test case."""
81+
return isinstance(e, ServiceUnavailable)

3-binary-data/tests/test_crud.py

Lines changed: 25 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -12,67 +12,26 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import unittest
16-
17-
import bookshelf
18-
import config
15+
from conftest import flaky_filter
1916
from flaky import flaky
20-
from gcloud.exceptions import ServiceUnavailable
21-
from nose.plugins.attrib import attr
22-
23-
24-
def flaky_filter(e, *args):
25-
return isinstance(e, ServiceUnavailable)
17+
import pytest
2618

2719

20+
# Mark all test cases in this class as flaky, so that if errors occur they
21+
# can be retried. This is useful when databases are temporarily unavailable.
2822
@flaky(rerun_filter=flaky_filter)
29-
class IntegrationBase(unittest.TestCase):
30-
31-
def createBooks(self, n=1):
32-
with self.app.test_request_context():
33-
for i in range(1, n + 1):
34-
self.model.create({'title': u'Book {0}'.format(i)})
35-
36-
def setUp(self):
37-
self.app = bookshelf.create_app(
38-
config,
39-
testing=True,
40-
config_overrides={
41-
'DATA_BACKEND': self.backend
42-
}
43-
)
44-
45-
with self.app.app_context():
46-
self.model = bookshelf.get_model()
47-
48-
self.ids_to_delete = []
49-
50-
# Monkey-patch create so we can track the IDs of every item
51-
# created and delete them during tearDown.
52-
self.original_create = self.model.create
53-
54-
def tracking_create(*args, **kwargs):
55-
res = self.original_create(*args, **kwargs)
56-
self.ids_to_delete.append(res['id'])
57-
return res
58-
59-
self.model.create = tracking_create
60-
61-
def tearDown(self):
62-
63-
# Delete all items that we created during tests.
64-
with self.app.test_request_context():
65-
list(map(self.model.delete, self.ids_to_delete))
66-
67-
self.model.create = self.original_create
68-
69-
70-
@attr('slow')
71-
class CrudTestsMixin(object):
72-
def testList(self):
73-
self.createBooks(11)
74-
75-
with self.app.test_client() as c:
23+
# Tell pytest to use both the app and model fixtures for all test cases.
24+
# This ensures that configuration is properly applied and that all database
25+
# resources created during tests are cleaned up. These fixtures are defined
26+
# in conftest.py
27+
@pytest.mark.usefixtures('app', 'model')
28+
class TestCrudActions(object):
29+
30+
def test_list(self, app, model):
31+
for i in range(1, 12):
32+
model.create({'title': u'Book {0}'.format(i)})
33+
34+
with app.test_client() as c:
7635
rv = c.get('/books/')
7736

7837
assert rv.status == '200 OK'
@@ -84,66 +43,45 @@ def testList(self):
8443
assert 'Book 9' not in body, "Should not show more than 10 books"
8544
assert 'More' in body, "Should have more than one page"
8645

87-
def testAddAndView(self):
46+
def test_add(self, app):
8847
data = {
8948
'title': 'Test Book',
9049
'author': 'Test Author',
9150
'publishedDate': 'Test Date Published',
9251
'description': 'Test Description'
9352
}
9453

95-
with self.app.test_client() as c:
54+
with app.test_client() as c:
9655
rv = c.post('/books/add', data=data, follow_redirects=True)
9756

9857
assert rv.status == '200 OK'
99-
10058
body = rv.data.decode('utf-8')
10159
assert 'Test Book' in body
10260
assert 'Test Author' in body
10361
assert 'Test Date Published' in body
10462
assert 'Test Description' in body
10563

106-
def testEditAndView(self):
107-
with self.app.test_request_context():
108-
existing = self.model.create({'title': "Temp Title"})
64+
def test_edit(self, app, model):
65+
existing = model.create({'title': "Temp Title"})
10966

110-
with self.app.test_client() as c:
67+
with app.test_client() as c:
11168
rv = c.post(
11269
'/books/%s/edit' % existing['id'],
11370
data={'title': 'Updated Title'},
11471
follow_redirects=True)
11572

11673
assert rv.status == '200 OK'
117-
11874
body = rv.data.decode('utf-8')
11975
assert 'Updated Title' in body
12076
assert 'Temp Title' not in body
12177

122-
def testDelete(self):
123-
with self.app.test_request_context():
124-
existing = self.model.create({'title': "Temp Title"})
78+
def test_delete(self, app, model):
79+
existing = model.create({'title': "Temp Title"})
12580

126-
with self.app.test_client() as c:
81+
with app.test_client() as c:
12782
rv = c.get(
12883
'/books/%s/delete' % existing['id'],
12984
follow_redirects=True)
13085

13186
assert rv.status == '200 OK'
132-
133-
with self.app.test_request_context():
134-
assert not self.model.read(existing['id'])
135-
136-
137-
@attr('datastore')
138-
class TestDatastore(CrudTestsMixin, IntegrationBase):
139-
backend = 'datastore'
140-
141-
142-
@attr('cloudsql')
143-
class TestCloudSql(CrudTestsMixin, IntegrationBase):
144-
backend = 'cloudsql'
145-
146-
147-
@attr('mongodb')
148-
class TestMongo(CrudTestsMixin, IntegrationBase):
149-
backend = 'mongodb'
87+
assert not model.read(existing['id'])

3-binary-data/tests/test_storage.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@
1515
import re
1616

1717
import httplib2
18-
from nose.plugins.attrib import attr
18+
from conftest import flaky_filter
19+
from flaky import flaky
1920
from six import BytesIO
20-
from test_crud import IntegrationBase
21+
import pytest
2122

2223

23-
@attr('slow')
24-
class StorageIntegrationTest(IntegrationBase):
24+
# Mark all test cases in this class as flaky, so that if errors occur they
25+
# can be retried. This is useful when databases are temporarily unavailable.
26+
@flaky(rerun_filter=flaky_filter)
27+
# Tell pytest to use both the app and model fixtures for all test cases.
28+
# This ensures that configuration is properly applied and that all database
29+
# resources created during tests are cleaned up. These fixtures are defined
30+
# in conftest.py
31+
@pytest.mark.usefixtures('app', 'model')
32+
class TestStorage(object):
2533

26-
backend = 'datastore'
27-
28-
def testAddWithImage(self):
34+
def test_upload_image(self, app):
2935
data = {
3036
'title': 'Test Book',
3137
'author': 'Test Author',
@@ -34,7 +40,7 @@ def testAddWithImage(self):
3440
'image': (BytesIO(b'hello world'), 'hello.jpg')
3541
}
3642

37-
with self.app.test_client() as c:
43+
with app.test_client() as c:
3844
rv = c.post('/books/add', data=data, follow_redirects=True)
3945

4046
assert rv.status == '200 OK'
@@ -47,7 +53,7 @@ def testAddWithImage(self):
4753
assert resp.status == 200
4854
assert content == b'hello world'
4955

50-
def testAddBadFileType(self):
56+
def test_upload_bad_file(self, app):
5157
data = {
5258
'title': 'Test Book',
5359
'author': 'Test Author',
@@ -57,7 +63,7 @@ def testAddBadFileType(self):
5763
'1337h4x0r.php')
5864
}
5965

60-
with self.app.test_client() as c:
66+
with app.test_client() as c:
6167
rv = c.post('/books/add', data=data,
6268
follow_redirects=True)
6369
# check we weren't pwned

3-binary-data/tox.ini

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,9 @@ deps =
88
-rrequirements.txt
99
-rrequirements-dev.txt
1010
commands =
11-
nosetests \
12-
--with-flaky \
13-
--no-success-flaky-report \
14-
--with-coverage \
15-
--cover-package bookshelf \
16-
{posargs:-a '!e2e'}
11+
py.test --cov=bookshelf --no-success-flaky-report {posargs} tests
1712
passenv = GOOGLE_APPLICATION_CREDENTIALS
13+
setenv = PYTHONPATH={toxinidir}
1814

1915
[testenv:py34]
2016
basepython = python3.4

0 commit comments

Comments
 (0)