| #summary Guide on tips and tricks for writing tests for Melange |
| #labels Contents-Draft,Importance-Useful,Phase-QA |
| |
| This page is inteded for tips and tricks for writing tests for Melange. |
| |
| <wiki:toc max_depth="3" /> |
| |
| |
| = Writing tests = |
| == Testing framework= |
| All existing logic and models tests of Melange are built on Python unittest TestCase (unittest.TestCase) and most views tests are built on Django TestCase (django.test.TestCase) which extends Python unittest TestCase by adding extra functions, e.g. Django test client for view tests which works with GAE/Melange and fixtures which does not work with GAE without any modifications, patches or helpers. |
| |
| == Naming convention == |
| The name of the test module for testing a codebase module is the name of the codebase module plus a prefix of "test`_`" (without quote), e.g. test_base.py. The name of the test class for testing a codebase class is the name of the codebase class plus a surfix of "Test" (without quote), e.g. BaseTest. The name of the test method for testing a codebase method is the name of the codebase method with the first letter capitalized plus a prefix of "test" (without quote), e.g. testGetForFields. |
| |
| == Organization of tests == |
| All tests are in a separate directory |
| {{{ |
| MELANGE-ROOT/tests |
| }}} |
| The organization of the tests for a module within the directory is the same with the corresponding module in the codebase but with a "test`_`" (without quote) prefix before the module name. For example, the tests for the module app.soc.logic.models.base are placed in the module tests.app.soc.logic.models.test_base. |
| |
| == Utilities == |
| === `MockRequest` === |
| This is a shared dummy request object to mock common aspects of a request similar to Django test client. |
| === `StuboutHelper` === |
| This is a helper class using the the StubOutForTesting class of pymox. It is mainly used to stub out/ replace a function or a method of the codebase with a dummy one during the test. |
| === `DjangoTestCase` === |
| It extends Django TestCase in order to extend its functions as well as remove the functions which are not supported by Google App Engine, e.g. database flush and fixtures loading without the assistance of Google App Engine Helper or patches for Django. It can be used to do view tests in the replacement of Python unittest TestCase because of its extra assert methods and test client for view tests. |
| |
| *Note*: it has not been merged to the trunk yet and only available in the clone of hiddenpython-melange-testing. |
| |
| == Tools == |
| === pymox === |
| * http://code.google.com/p/pymox/ |
| * Pymox can be used to isolate objects from other interacted objects by mocking the behaviours of those interacted objects. |
| * It is in tests/pymox. |
| === fixture === |
| * http://farmdev.com/projects/fixture/ |
| * Fixture can be used to load and reference preset test data. |
| * It is in MELANGE-ROOT/eggs |
| === Django test client === |
| * http://docs.djangoproject.com/en/dev/topics/testing/#module-django.test.client |
| * It acts as a dummy web browser which simulates HTTP requests on a URL and returns the response. |
| * It is mainly useful for Django view tests |
| * It is in app.django.test.Client. |
| === gaeftest === |
| It is in MELANGE-ROOT/eggs. |
| === zope.testbrowser === |
| It is in MELANGE-ROOT/eggs. |
| |
| = Running tests = |
| == Running all the tests == |
| Suppose that the current working directory is the root of your local clone of the Melange project. You can run all the tests by running the following command |
| {{{ |
| bin/run-tests |
| }}} |
| |
| == Running options == |
| If you want to get test coverage report, add option --coverage, i.e. |
| {{{ |
| bin/run-tests --coverage |
| }}} |
| The default covered package is the whole soc package (--cover-package=soc.). |
| |
| Internally, it uses nose test runner. So, all the options of nosetests should also work here. For example, you can use -e REGEX or --exclude=REGEX to exclude tests that match the regular expression REGEX. E.g. to ignore the functional tests run: |
| {{{ |
| bin/run-tests -e test_functional |
| }}} |
| *Note*: prebuilt nose library (MELANGE-ROOT/eggs/nose-0.11.3-py2.5.egg) does not include the coverage plugin, so you cannot use the -with--coverage option to get test coverage report. |
| |
| == Running the tests in one module == |
| If you want to just run all the tests in one module (say test_base), execute |
| {{{ |
| bin/run-tests tests/app/soc/logic/models/test_base.py |
| }}} |
| or |
| {{{ |
| bin/run-tests tests.app.soc.logic.models.test_base |
| }}} |
| |
| == Running a group of test cases in one module == |
| To just run a group of test cases in one module (say test_base.BaseTest), execute |
| {{{ |
| bin/run-tests tests/app/soc/logic/models/test_base.py:BaseTest |
| }}} |
| or |
| {{{ |
| bin/run-tests tests.app.soc.logic.models.test_base:BaseTest |
| }}} |
| == Running a single test case in one module == |
| To just run a single test case in one module (say test_base.BaseTest.testGetForFields), execute |
| {{{ |
| bin/run-tests tests/app/soc/logic/models/test_base.py:BaseTest.testGetForFields |
| }}} |
| or |
| {{{ |
| bin/run-tests tests.app.soc.logic.models.test_base:BaseTest.testGetForFields |
| }}} |
| |
| *Note:* although running only one test module or a group of test cases can save much time, it is desirable to run all the tests at least once before merging your code just in case it breaks others' code. |
| |
| |
| = The test environment = |
| The test environment is set up by the tests/run.py file. It adds libraries to Python path, sets up Python environ variables, datastores, memcache etc, creates a Melange core, registers callbacks, and runs tests through nose. |
| == User account == |
| * apiproxy_stub_map.apiproxy.RegisterStub('user', user_service_stub.UserServiceStub()) |
| * The email of the user account of the test environment is test@example.com and the user is logged on automatically if the user account (test@example.com) is in Melange User datastore. |
| == Datastore == |
| * apiproxy_stub_map.apiproxy.RegisterStub('datastore', datastore_file_stub.DatastoreFileStub('test-app-run', None, None)) |
| * It is a memory based datastore which is cleared after each test case, so it is not necessary to clear it manually after each test. |
| == Memcache == |
| * apiproxy_stub_map.apiproxy.RegisterStub('memcache', memcache_stub.MemcacheServiceStub()) |
| * It keeps all data in the local process' memory. |
| == Mail service == |
| * apiproxy_stub_map.apiproxy.RegisterStub('mail', mail_stub.MailServiceStub()) |
| * It does not really send email, but only logs a description of the email to the developers console. |
| == URL fetch service == |
| * apiproxy_stub_map.apiproxy.RegisterStub('urlfetch', urlfetch_stub.URLFetchServiceStub()) |
| * A Python httplib based stub version of the GAE urlfetch API . |