Skip to content

Commit 0bfc9ba

Browse files
author
Jon Wayne Parrott
committed
Merge pull request GoogleCloudPlatform#25 from GoogleCloudPlatform/pytest-refactor-step-2
Refactoring 2-structured-data tests to use py.test
2 parents 852eedc + d1467ce commit 0bfc9ba

File tree

4 files changed

+78
-85
lines changed

4 files changed

+78
-85
lines changed
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

2-structured-data/requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
Flask==0.10.1
2-
gcloud==0.9.0
2+
gcloud==0.10.1
33
gunicorn==19.4.5
4-
oauth2client==1.5.2
4+
oauth2client==2.0.0.post1
55
Flask-SQLAlchemy==2.1
6-
PyMySQL==0.7.1
6+
PyMySQL==0.7.2
77
Flask-PyMongo==0.4.0
88
PyMongo==3.2.1
99
six==1.10.0

2-structured-data/tests/test_crud.py

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

15-
import unittest
16-
1715
import bookshelf
1816
import config
1917
from flaky import flaky
2018
from gcloud.exceptions import ServiceUnavailable
21-
from nose.plugins.attrib import attr
19+
import pytest
2220

2321

24-
def flaky_filter(e, *args):
25-
return isinstance(e, ServiceUnavailable)
22+
@pytest.yield_fixture(params=['datastore', 'cloudsql', 'mongodb'])
23+
def app(request):
24+
"""This fixtures provides a Flask app instance configured for testing.
2625
26+
Because it's parametric, it will cause every test that uses this fixture
27+
to run three times: one time for each backend (datastore, cloudsql, and
28+
mongodb).
29+
30+
It also ensures the tests run within a request context, allowing
31+
any calls to flask.request, flask.current_app, etc. to work."""
32+
app = bookshelf.create_app(
33+
config,
34+
testing=True,
35+
config_overrides={
36+
'DATA_BACKEND': request.param
37+
})
38+
39+
with app.test_request_context():
40+
yield app
2741

28-
@flaky(rerun_filter=flaky_filter)
29-
class IntegrationBase(unittest.TestCase):
3042

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)})
43+
@pytest.yield_fixture
44+
def model(monkeypatch, app):
45+
"""This fixture provides a modified version of the app's model that tracks
46+
all created items and deletes them at the end of the test.
3547
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-
)
48+
Any tests that directly or indirectly interact with the database should use
49+
this to ensure that resources are properly cleaned up.
4450
45-
with self.app.app_context():
46-
self.model = bookshelf.get_model()
51+
Monkeypatch is provided by pytest and used to patch the model's create
52+
method.
4753
48-
self.ids_to_delete = []
54+
The app fixture is needed to provide the configuration and context needed
55+
to get the proper model object.
56+
"""
57+
model = bookshelf.get_model()
4958

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
59+
ids_to_delete = []
5360

54-
def tracking_create(*args, **kwargs):
55-
res = self.original_create(*args, **kwargs)
56-
self.ids_to_delete.append(res['id'])
57-
return res
61+
# Monkey-patch create so we can track the IDs of every item
62+
# created and delete them after the test case.
63+
original_create = model.create
5864

59-
self.model.create = tracking_create
65+
def tracking_create(*args, **kwargs):
66+
res = original_create(*args, **kwargs)
67+
ids_to_delete.append(res['id'])
68+
return res
6069

61-
def tearDown(self):
70+
monkeypatch.setattr(model, 'create', tracking_create)
6271

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))
72+
yield model
6673

67-
self.model.create = self.original_create
74+
# Delete all items that we created during tests.
75+
list(map(model.delete, ids_to_delete))
6876

6977

70-
@attr('slow')
71-
class CrudTestsMixin(object):
72-
def testList(self):
73-
self.createBooks(11)
78+
def flaky_filter(e, *args):
79+
"""Used by flaky to determine when to re-run a test case."""
80+
return isinstance(e, ServiceUnavailable)
81+
82+
83+
# Mark all test cases in this class as flaky, so that if errors occur they
84+
# can be retried. This is useful when databases are temporarily unavailable.
85+
@flaky(rerun_filter=flaky_filter)
86+
# Tell pytest to use both the app and model fixtures for all test cases.
87+
# This ensures that configuration is properly applied and that all database
88+
# resources created during tests are cleaned up.
89+
@pytest.mark.usefixtures('app', 'model')
90+
class TestCrudActions(object):
7491

75-
with self.app.test_client() as c:
92+
def test_list(self, app, model):
93+
for i in range(1, 12):
94+
model.create({'title': u'Book {0}'.format(i)})
95+
96+
with app.test_client() as c:
7697
rv = c.get('/books/')
7798

7899
assert rv.status == '200 OK'
@@ -84,66 +105,45 @@ def testList(self):
84105
assert 'Book 9' not in body, "Should not show more than 10 books"
85106
assert 'More' in body, "Should have more than one page"
86107

87-
def testAddAndView(self):
108+
def test_add(self, app):
88109
data = {
89110
'title': 'Test Book',
90111
'author': 'Test Author',
91112
'publishedDate': 'Test Date Published',
92113
'description': 'Test Description'
93114
}
94115

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

98119
assert rv.status == '200 OK'
99-
100120
body = rv.data.decode('utf-8')
101121
assert 'Test Book' in body
102122
assert 'Test Author' in body
103123
assert 'Test Date Published' in body
104124
assert 'Test Description' in body
105125

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

110-
with self.app.test_client() as c:
129+
with app.test_client() as c:
111130
rv = c.post(
112131
'/books/%s/edit' % existing['id'],
113132
data={'title': 'Updated Title'},
114133
follow_redirects=True)
115134

116135
assert rv.status == '200 OK'
117-
118136
body = rv.data.decode('utf-8')
119137
assert 'Updated Title' in body
120138
assert 'Temp Title' not in body
121139

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

126-
with self.app.test_client() as c:
143+
with app.test_client() as c:
127144
rv = c.get(
128145
'/books/%s/delete' % existing['id'],
129146
follow_redirects=True)
130147

131148
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'
149+
assert not model.read(existing['id'])

2-structured-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)