Python 2.7 has reached end of supportand will bedeprecatedon January 31, 2026. After deprecation, you won't be able to deploy Python 2.7applications, even if your organization previously used an organization policy tore-enable deployments of legacy runtimes. Your existing Python2.7 applications will continue to run and receive traffic after theirdeprecation date. We recommend thatyoumigrate to the latest supported version of Python.

Local Unit Testing for Python 2

Unit testing allows you to check thequality of your code after you've written it, but you can also use unit testingto improve your development process as you go along. Instead of writing testsafter you finish developing your application, consider writing the tests as yougo. This helps you design small, maintainable, reusable units of code. It alsomakes it easier for you to test your code thoroughly and quickly.

When you do local unit testing, you run tests that stay inside your owndevelopment environment without involving remote components. App Engine providestesting utilities that use local implementations of datastore and otherAppEngine services. This means youcan exercise your code's use of these services locally, without deploying yourcode to App Engine, by using service stubs.

A service stub is a method that simulates the behavior of the service. Forexample, the datastore service stub shown inWriting Datastore and Memcache Tests allows you totest your datastore code without making any requests to the real datastore. Anyentity stored during a datastore unit test is held in memory, not in thedatastore, and is deleted after the test run. You can run small, fast testswithout any dependency on datastore itself.

This document describes how to write unit tests against several local App Engineservices, then gives some information about setting up a testing framework.

Introducing the Python 2 testing utilities

An App Engine Python module calledtestbedmakes service stubs available for unit testing.

Service stubs are available for the following services:

  • App Identityinit_app_identity_stub
  • Blobstore (useinit_blobstore_stub)
  • Capability (useinit_capability_stub)
  • Datastore (useinit_datastore_v3_stub)
  • Files (useinit_files_stub)
  • Images (only fordev_appserver;useinit_images_stub)
  • LogService (useinit_logservice_stub)
  • Mail (useinit_mail_stub)
  • Memcache (useinit_memcache_stub)
  • Task Queue (useinit_taskqueue_stub)
  • URL fetch (useinit_urlfetch_stub)
  • User service (useinit_user_stub)

To initialize all stubs at the same time, you can useinit_all_stubs.

Note: In some cases, service stubs behave differently than the actual services.Even if tests pass, your code could still fail when it runs in a productionenvironment.

Writing Datastore and memcache tests

This section shows an example of how to write code that tests the use of thedatastore andmemcache services.

Make sure your test runner has the appropriate libraries on the Python loadpath, including the App Engine libraries,yaml (included in the App EngineSDK), the application root, and any other modifications to the library pathexpected by application code (such as a local./lib directory, if you haveone). For example:

importsyssys.path.insert(1,'google-cloud-sdk/platform/google_appengine')sys.path.insert(1,'google-cloud-sdk/platform/google_appengine/lib/yaml/lib')sys.path.insert(1,'myapp/lib')

Import the Pythonunittest module and the App Engine modules that are relevantto the services being tested—in this casememcache andndb, which uses boththe datastore and memcache. Also import thetestbedmodule.

importunittestfromgoogle.appengine.apiimportmemcachefromgoogle.appengine.extimportndbfromgoogle.appengine.extimporttestbed

Then create aTestModel class. In this example, a function checks to seewhether an entity is stored in memcache. If no entity is found, it checks for anentity in the datastore. This can often be redundant in real life, sincendbuses memcache itself behind the curtains, but it's still an OK pattern for atest.

classTestModel(ndb.Model):"""A model class used for testing."""number=ndb.IntegerProperty(default=42)text=ndb.StringProperty()classTestEntityGroupRoot(ndb.Model):"""Entity group root"""passdefGetEntityViaMemcache(entity_key):"""Get entity from memcache if available, from datastore if not."""entity=memcache.get(entity_key)ifentityisnotNone:returnentitykey=ndb.Key(urlsafe=entity_key)entity=key.get()ifentityisnotNone:memcache.set(entity_key,entity)returnentity

Next, create a test case. No matter what services you are testing, the test casemust create aTestbed instance and activate it. The test case must alsoinitialize the relevant service stubs, in this case usinginit_datastore_v3_stub andinit_memcache_stub. The methods for initializingother App Engine service stubs are listed inIntroducing the Python TestingUtilities.

classDatastoreTestCase(unittest.TestCase):defsetUp(self):# First, create an instance of the Testbed class.self.testbed=testbed.Testbed()# Then activate the testbed, which prepares the service stubs for use.self.testbed.activate()# Next, declare which service stubs you want to use.self.testbed.init_datastore_v3_stub()self.testbed.init_memcache_stub()# Clear ndb's in-context cache between tests.# This prevents data from leaking between tests.# Alternatively, you could disable caching by# using ndb.get_context().set_cache_policy(False)ndb.get_context().clear_cache()

Theinit_datastore_v3_stub() method with no argument uses an in-memorydatastore that is initially empty. If you want to test an existing datastoreentity, include its pathname as an argument toinit_datastore_v3_stub().

Note: ThesetUp() method uses a default project ID for test runs. The test canonly access entities in the datastore that have that same project ID. Tochange the project ID of a test, usesetup_env(), as described inChanging the Default EnvironmentVariables.

In addition tosetUp(), include atearDown() method that deactivates thetestbed. This restores the original stubs so that tests do not interfere witheach other.

deftearDown(self):self.testbed.deactivate()

Then implement the tests.

deftestInsertEntity(self):TestModel().put()self.assertEqual(1,len(TestModel.query().fetch(2)))

Now you can useTestModel to write tests that use the datastore or memcacheservice stubs instead of using the real services.

For example, the method shown below creates two entities: the first entity usesthe default value for thenumber attribute (42), and the second uses anon-default value fornumber (17). The method then builds a query forTestModel entities, but only for those with the default value ofnumber.

After retrieving all matching entities, the method tests that exactly one entitywas found, and that thenumber attribute value of that entity is the defaultvalue.

deftestFilterByNumber(self):root=TestEntityGroupRoot(id="root")TestModel(parent=root.key).put()TestModel(number=17,parent=root.key).put()query=TestModel.query(ancestor=root.key).filter(TestModel.number==42)results=query.fetch(2)self.assertEqual(1,len(results))self.assertEqual(42,results[0].number)

As another example, the following method creates an entity and retrieves itusing theGetEntityViaMemcache() function that we created above. The methodthen tests that an entity was returned, and that itsnumber value is the sameas for the previously created entity.

deftestGetEntityViaMemcache(self):entity_key=TestModel(number=18).put().urlsafe()retrieved_entity=GetEntityViaMemcache(entity_key)self.assertNotEqual(None,retrieved_entity)self.assertEqual(18,retrieved_entity.number)

And finally, invokeunittest.main().

if__name__=='__main__':unittest.main()

To run the tests, seeRunning tests.

Writing Cloud Datastore tests

If your app uses Cloud Datastore, you might want to writetests that verify your application's behavior in the face of eventualconsistency.db.testbed exposes options that make thiseasy:

fromgoogle.appengine.datastoreimportdatastore_stub_util# noqaclassHighReplicationTestCaseOne(unittest.TestCase):defsetUp(self):# First, create an instance of the Testbed class.self.testbed=testbed.Testbed()# Then activate the testbed, which prepares the service stubs for use.self.testbed.activate()# Create a consistency policy that will simulate the High Replication# consistency model.self.policy=datastore_stub_util.PseudoRandomHRConsistencyPolicy(probability=0)# Initialize the datastore stub with this policy.self.testbed.init_datastore_v3_stub(consistency_policy=self.policy)# Initialize memcache stub too, since ndb also uses memcacheself.testbed.init_memcache_stub()# Clear in-context cache before each test.ndb.get_context().clear_cache()deftearDown(self):self.testbed.deactivate()deftestEventuallyConsistentGlobalQueryResult(self):classTestModel(ndb.Model):passuser_key=ndb.Key('User','ryan')# Put two entitiesndb.put_multi([TestModel(parent=user_key),TestModel(parent=user_key)])# Global query doesn't see the data.self.assertEqual(0,TestModel.query().count(3))# Ancestor query does see the data.self.assertEqual(2,TestModel.query(ancestor=user_key).count(3))

ThePseudoRandomHRConsistencyPolicy class lets you control the likelihood of awrite applying before each global (non-ancestor) query. By setting theprobability to 0%, we are instructing the datastore stub to operate with themaximum amount of eventual consistency. Maximum eventual consistency meanswrites will commit but always fail to apply, so global (non-ancestor) querieswill consistently fail to see changes. This is of course not representative ofthe amount of eventual consistency your application will see when running inproduction, but for testing purposes, it's very useful to be able to configurethe local datastore to behave this way every time. If you use a non-zeroprobability,PseudoRandomHRConsistencyPolicy makes a deterministic sequence ofconsistency decisions so test outcomes are consistent:

deftestDeterministicOutcome(self):# 50% chance to apply.self.policy.SetProbability(.5)# Use the pseudo random sequence derived from seed=2.self.policy.SetSeed(2)classTestModel(ndb.Model):passTestModel().put()self.assertEqual(0,TestModel.query().count(3))self.assertEqual(0,TestModel.query().count(3))# Will always be applied before the third query.self.assertEqual(1,TestModel.query().count(3))

The testing APIs are useful for verifying that your application behaves properlyin the face of eventual consistency, but please keep in mind that the local HighReplication read consistency model is an approximation of the production HighReplication read consistency model, not an exact replica. In the localenvironment, performing aget() of anEntity that belongs to an entity groupwith an unapplied write will always make the results of the unapplied writevisible to subsequent global queries. In production this is not the case.

Writing mail tests

You can use the mail service stub to test themail service. Similar to other services supported by testbed, at first you initialize the stub, then invoke the code which uses the mail API, and finally test whether the correct messages were sent.

importunittestfromgoogle.appengine.apiimportmailfromgoogle.appengine.extimporttestbedclassMailTestCase(unittest.TestCase):defsetUp(self):self.testbed=testbed.Testbed()self.testbed.activate()self.testbed.init_mail_stub()self.mail_stub=self.testbed.get_stub(testbed.MAIL_SERVICE_NAME)deftearDown(self):self.testbed.deactivate()deftestMailSent(self):mail.send_mail(to='alice@example.com',subject='This is a test',sender='bob@example.com',body='This is a test e-mail')messages=self.mail_stub.get_sent_messages(to='alice@example.com')self.assertEqual(1,len(messages))self.assertEqual('alice@example.com',messages[0].to)

Writing task queue tests

You can use the taskqueue stub to write tests that use thetaskqueue service. Similar to otherservices supported by testbed, at first you initialize the stub, then invoke thecode which uses the taskqueue API, and finally test whether the tasks wereproperly added to the queue.

importoperatorimportosimportunittestfromgoogle.appengine.apiimporttaskqueuefromgoogle.appengine.extimportdeferredfromgoogle.appengine.extimporttestbedclassTaskQueueTestCase(unittest.TestCase):defsetUp(self):self.testbed=testbed.Testbed()self.testbed.activate()# root_path must be set the the location of queue.yaml.# Otherwise, only the 'default' queue will be available.self.testbed.init_taskqueue_stub(root_path=os.path.join(os.path.dirname(__file__),'resources'))self.taskqueue_stub=self.testbed.get_stub(testbed.TASKQUEUE_SERVICE_NAME)deftearDown(self):self.testbed.deactivate()deftestTaskAddedToQueue(self):taskqueue.Task(name='my_task',url='/url/of/my/task/').add()tasks=self.taskqueue_stub.get_filtered_tasks()self.assertEqual(len(tasks),1)self.assertEqual(tasks[0].name,'my_task')

Setting thequeue.yaml configuration file

If you want to run tests on code that interacts a non-default queue, you willneed to create and specify aqueue.yaml file for your application to use.Below is an examplequeue.yaml:

For more information on the queue.yaml options available, seetask queueconfiguration.

queue:-name:defaultrate:5/s-name:queue-1rate:5/s-name:queue-2rate:5/s

The location of thequeue.yaml is specified when initializing the stub:

self.testbed.init_taskqueue_stub(root_path='.')

In the sample,queue.yaml is in the same directory as the tests. If it were inanother folder, that path would need to be specified inroot_path.

Filtering tasks

The taskqueue stub'sget_filtered_tasks allows you to filter queued tasks.This makes it easier to write tests that need to verify code that enqueuesmultiple tasks.

deftestFiltering(self):taskqueue.Task(name='task_one',url='/url/of/task/1/').add('queue-1')taskqueue.Task(name='task_two',url='/url/of/task/2/').add('queue-2')# All taskstasks=self.taskqueue_stub.get_filtered_tasks()self.assertEqual(len(tasks),2)# Filter by nametasks=self.taskqueue_stub.get_filtered_tasks(name='task_one')self.assertEqual(len(tasks),1)self.assertEqual(tasks[0].name,'task_one')# Filter by URLtasks=self.taskqueue_stub.get_filtered_tasks(url='/url/of/task/1/')self.assertEqual(len(tasks),1)self.assertEqual(tasks[0].name,'task_one')# Filter by queuetasks=self.taskqueue_stub.get_filtered_tasks(queue_names='queue-1')self.assertEqual(len(tasks),1)self.assertEqual(tasks[0].name,'task_one')# Multiple queuestasks=self.taskqueue_stub.get_filtered_tasks(queue_names=['queue-1','queue-2'])self.assertEqual(len(tasks),2)

Writing deferred task tests

If your application code uses the deferred library, you can use the taskqueuestub along withdeferred to verify that deferred functions are queued andexecuted correctly.

deftestTaskAddedByDeferred(self):deferred.defer(operator.add,1,2)tasks=self.taskqueue_stub.get_filtered_tasks()self.assertEqual(len(tasks),1)result=deferred.run(tasks[0].payload)self.assertEqual(result,3)

Changing the default environment variables

App Engine services often depend onenvironment variables. Theactivate() methodof classtestbed.Testbed uses default values for these, but you can set customvalues based on your testing needs with thesetup_envmethod of classtestbed.Testbed.

Note: be sure any call tosetup_env comesafter the call toactivate, sothat the eventualdeactivate properly restores the original environment; thisis important to decouple your unit tests from each other.

For example, let's say you have a test that stores several entities indatastore, all of them linked to the same application ID. Now you want to runthe same tests again, but using an application ID different from the one that islinked to the stored entities. To do this, pass the new value intoself.setup_env() asapp_id.

For example:

importosimportunittestfromgoogle.appengine.extimporttestbedclassEnvVarsTestCase(unittest.TestCase):defsetUp(self):self.testbed=testbed.Testbed()self.testbed.activate()self.testbed.setup_env(app_id='your-app-id',my_config_setting='example',overwrite=True)deftearDown(self):self.testbed.deactivate()deftestEnvVars(self):self.assertEqual(os.environ['APPLICATION_ID'],'your-app-id')self.assertEqual(os.environ['MY_CONFIG_SETTING'],'example')

Simulating login

Another frequent use forsetup_env is to simulate a user being logged in,either with or without admin privileges, to check if your handlers operateproperly in each case.

importunittestfromgoogle.appengine.apiimportusersfromgoogle.appengine.extimporttestbedclassLoginTestCase(unittest.TestCase):defsetUp(self):self.testbed=testbed.Testbed()self.testbed.activate()self.testbed.init_user_stub()deftearDown(self):self.testbed.deactivate()defloginUser(self,email='user@example.com',id='123',is_admin=False):self.testbed.setup_env(user_email=email,user_id=id,user_is_admin='1'ifis_adminelse'0',overwrite=True)deftestLogin(self):self.assertFalse(users.get_current_user())self.loginUser()self.assertEquals(users.get_current_user().email(),'user@example.com')self.loginUser(is_admin=True)self.assertTrue(users.is_current_user_admin())

Now, your test methods can call, for example,self.loginUser('', '') tosimulate no user being logged in,self.loginUser('test@example.com', '123') tosimulate a non-admin user being logged in,self.loginUser('test@example.com','123', is_admin=True) to simulate an admin user being logged in.

Setting up a testing framework

The SDK's testing utilities are not tied to a specific framework. You can runyour unit tests with any available App Engine testrunner, for examplenose-gae orferrisnose. You can also write asimple testrunner of your own, or use the one shown below.

The following scripts use Python'sunittest module.

You can name the script anything you want. When you run it, provide the path toyour Google Cloud CLI or Google App Engine SDK installation and the path to yourtest modules. The script will discover all tests in the path provided and willprint results to the standard error stream. Test files follow the convention ofhavingtest prefixed to their name.

"""App Engine local test runner example.This program handles properly importing the App Engine SDK so that test modulescan use google.appengine.* APIs and the Google App Engine testbed.Example invocation:    $ python runner.py ~/google-cloud-sdk"""importargparseimportosimportsysimportunittestdeffixup_paths(path):"""Adds GAE SDK path to system path and appends it to the google path    if that already exists."""# Not all Google packages are inside namespace packages, which means# there might be another non-namespace package named `google` already on# the path and simply appending the App Engine SDK to the path will not# work since the other package will get discovered and used first.# This emulates namespace packages by first searching if a `google` package# exists by importing it, and if so appending to its module search path.try:importgooglegoogle.__path__.append("{0}/google".format(path))exceptImportError:passsys.path.insert(0,path)defmain(sdk_path,test_path,test_pattern):# If the SDK path points to a Google Cloud SDK installation# then we should alter it to point to the GAE platform location.ifos.path.exists(os.path.join(sdk_path,'platform/google_appengine')):sdk_path=os.path.join(sdk_path,'platform/google_appengine')# Make sure google.appengine.* modules are importable.fixup_paths(sdk_path)# Make sure all bundled third-party packages are available.importdev_appserverdev_appserver.fix_sys_path()# Loading appengine_config from the current project ensures that any# changes to configuration there are available to all tests (e.g.# sys.path modifications, namespaces, etc.)try:importappengine_config(appengine_config)exceptImportError:print('Note: unable to import appengine_config.')# Discover and run tests.suite=unittest.loader.TestLoader().discover(test_path,test_pattern)returnunittest.TextTestRunner(verbosity=2).run(suite)if__name__=='__main__':parser=argparse.ArgumentParser(description=__doc__,formatter_class=argparse.RawDescriptionHelpFormatter)parser.add_argument('sdk_path',help='The path to the Google App Engine SDK or the Google Cloud SDK.')parser.add_argument('--test-path',help='The path to look for tests, defaults to the current directory.',default=os.getcwd())parser.add_argument('--test-pattern',help='The file pattern for test modules, defaults to *_test.py.',default='*_test.py')args=parser.parse_args()result=main(args.sdk_path,args.test_path,args.test_pattern)ifnotresult.wasSuccessful():sys.exit(1)

Running the tests

You can run these tests simply by running the scriptrunner.py, which isdescribed in detail underSetting up a testing framework:

pythonrunner.py <path-to-appengine-or-gcloud-SDK>.

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2025-12-15 UTC.