Unit testing your code is a best practice in software development. By running small automated tests to individually verify each unit of code, such as a module, class or function, you can catch and debug errors early in your development process.
Google App Engine provides strong support for unit testing with Local Unit Testing tools, currently available for Python, Java, and Go. With local unit testing, you run the unit tests within your development environment, without calling any remote components. The Local Unit Testing tools offer service stubs to simulate many App Engine services. You can use stubs as needed to exercise your application code in local unit tests. You can also use open source packages like NoseGAE to further simplify the process of writing local App Engine unit tests.
Local unit tests using service stubs, however, handle routing and login constraints differently than code that calls the services directly. You have to keep these differences in mind when you design your tests.
When a customer had problems unit testing an App Engine cron handler, I was, of course, eager to help. The cron handler was defined by the following entry in app.yaml:
- url: /crontask.*
script: thecron.app
login: admin_only
The App Engine Cron Service recommends that you limit access to URLs used by scheduled tasks to administrator accounts. That’s what the login: admin_only setting does.
The customer wanted to check that non-admin users would indeed be blocked from those URLs.
The customer started with a placeholder implementation for the cron handler, thecron.py:
import webapp2
import time
class TestCronHandler(webapp2.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
self.response.write('Cron: {}'.format(time.time()))
app = webapp2.WSGIApplication([
('/crontask/test', TestCronHandler),
], debug=True)
Then they wrote a unit test , ut.py.
Note: the following test code uses the mock module. Run pip install mock to make the mock module available to your local Python 2.7 installation.
import sys
# configure unit testing for the case
# where the App Engine SDK is installed in
# /usr/local/google_appengine
sdk_path = '/usr/local/google_appengine'
sys.path.insert(0, sdk_path)
import dev_appserver
dev_appserver.fix_sys_path()
import mock
import unittest
import webapp2
from google.appengine.ext import testbed
import thecron
class CronTestCase(unittest.TestCase):
def setUp(self):
self.testbed = testbed.Testbed()
self.testbed.activate()
self.testbed.init_user_stub()
def _aux(self, is_admin):
self.testbed.setup_env(
USER_EMAIL = 'test@example.com',
USER_ID = '123',
USER_IS_ADMIN = str(int(bool(is_admin))),
overwrite = True)
request = webapp2.Request.blank('/crontask/test')
with mock.patch.object(thecron.time,
'time', return_value=12345678):
response = request.get_response(thecron.app)
return response
def testAdminWorks(self):
response = self._aux(True)
self.assertEqual(response.status_int, 200)
self.assertEqual(response.body, 'Cron: 12345678')
if __name__ == '__main__':
unittest.main()
This first test, testAdminWorks, passed with flying colors, so the user added a second one:
def testNonAdminFails(self):
response = self._aux(False)
self.assertEqual(response.status_int, 401)
But the new test failed--the status was 200 (success), not 401 (forbidden) as expected.
The problem was that unit tests do not go all the way back to app.yaml to get the complete routing and login constraints as would an application calling the services, not the unit-testing stubs. This test reached into the secondary routing in thecron.py, which doesn’t impose login constraints. The app.yaml routing and everything else, such as mime-types, login constraints, etc., get tested only by tests calling the services instead of the unit-testing stubs.
So the customer added an admin-checking decorator, needs_admin, to thecron.py:
def needs_admin(func):
def inner(self, *args, **kwargs):
if users.get_current_user():
if not users.is_current_user_admin():
webapp2.abort(401)
return func(self, *args, **kwargs)
return self.redirect(users.create_login_url(request.url))
return inner
Then they decorated CronHandler.get with it:
class CronHandler(webapp2.RequestHandler):
@needs_admin
def get(self): # etc, as before
Now the local unit tests calling the stub version of the cron service work, but an end-to-end test using the actual App Engine Cron Service functionality fails with status 401. What’s going on?
Long story short -- the App Engine Cron Service doesn’t log-in any user as it visits the appointed URLs -- therefore, in the handler, users.get_current_user() returns None. Instead, the Cron Service sets a special request header -- X-AppEngine-Cron: true. This is a header that application code can fully trust, since App Engine removes such headers if they’re set in an external request.
All that the customer needed, to get their unit tests, end-to-end tests, local development application, and deployed application, working, was a slight modification to their needs_admin decorator:
def needs_admin(func):
def inner(self, *args, **kwargs):
if self.request.headers.get('X-AppEngine-Cron') == 'true':
return func(self, *args, **kwargs)
if users.get_current_user():
if not users.is_current_user_admin():
webapp2.abort(401)
return func(self, *args, **kwargs)
return self.redirect(users.create_login_url(request.url))
return inner
The new if statement handles the cron job case, mocked or not. The second if, as before, ensures that non-admin (or non-logged-in) users are blocked.
- Posted By Alex Martelli, Technical Solutions Engineer
0 $type={blogger}
Post a Comment
Thank you for reaching out to us. We are happy to receive your opinion and request. If you need advert or sponsored post, We’re excited you’re considering advertising or sponsoring a post on our blog. Your support is what keeps us going. With the current trend, it’s very obvious content marketing is the way to go. Banner advertising and trying to get customers through Google Adwords may get you customers but it has been proven beyond doubt that Content Marketing has more lasting benefits.
We offer majorly two types of advertising:
1. Sponsored Posts: If you are really interested in publishing a sponsored post or a press release, video content, advertorial or any other kind of sponsored post, then you are at the right place.
WHAT KIND OF SPONSORED POSTS DO WE ACCEPT?
Generally, a sponsored post can be any of the following:
Press release
Advertorial
Video content
Article
Interview
This kind of post is usually written to promote you or your business. However, we do prefer posts that naturally flow with the site’s general content. This means we can also promote artists, songs, cosmetic products and things that you love of all products or services.
DURATION & BONUSES
Every sponsored article will remain live on the site as long as this website exists. The duration is indefinite! Again, we will share your post on our social media channels and our email subscribers too will get to read your article. You’re exposing your article to our: Twitter followers, Facebook fans and other social networks.
We will also try as much as possible to optimize your post for search engines as well.
Submission of Materials : Sponsored post should be well written in English language and all materials must be delivered via electronic medium. All sponsored posts must be delivered via electronic version, either on disk or e-mail on Microsoft Word unless otherwise noted.
PRICING
The price largely depends on if you’re writing the content or we’re to do that. But if your are writing the content, it is $60 per article.
2. Banner Advertising: We also offer banner advertising in various sizes and of course, our prices are flexible. you may choose to for the weekly rate or simply buy your desired number of impressions.
Technical Details And Pricing
Banner Size 300 X 250 pixels : Appears on the home page and below all pages on the site.
Banner Size 728 X 90 pixels: Appears on the top right Corner of the homepage and all pages on the site.
Large rectangle Banner Size (336x280) : Appears on the home page and below all pages on the site.
Small square (200x200) : Appears on the right side of the home page and all pages on the site.
Half page (300x600) : Appears on the right side of the home page and all pages on the site.
Portrait (300x1050) : Appears on the right side of the home page and all pages on the site.
Billboard (970x250) : Appears on the home page.
Submission of Materials : Banner ads can be in jpeg, jpg and gif format. All materials must be deliverd via electronic medium. All ads must be delivered via electronic version, either on disk or e-mail in the ordered pixel dimensions unless otherwise noted.
For advertising offers, send an email with your name,company, website, country and advert or sponsored post you want to appear on our website to omodjk(at)gmail(dot)com
Normally, we should respond within 48 hours.