Commit 58f36d3e by David Ormsbee

Enable faster ModuleStore tests with SharedModuleStoreTestCase.

This is a new TestCase base class, intended for situations where
you only want to initialize course content in the setUpClass(),
as opposed to setUp(). This is done for performance reasons, since
many test cases only use courses in a read-only fashion,
particularly in LMS tests.

This commit also converts the following modules to use this new
base class:

  lms/djangoapps/ccx/tests/test_ccx_modulestore.py (38s -> 4s)
  lms/djangoapps/discussion_api/tests/test_api.py (2m45s -> 51s)
  lms/djangoapps/teams/tests/test_views.py (1m17s -> 33s)
parent ce9d2439
...@@ -159,6 +159,23 @@ def xml_store_config(data_dir, source_dirs=None): ...@@ -159,6 +159,23 @@ def xml_store_config(data_dir, source_dirs=None):
return store return store
@patch('xmodule.modulestore.django.create_modulestore_instance')
def drop_mongo_collections(mock_create):
"""
If using a Mongo-backed modulestore & contentstore, drop the collections.
"""
# Do not create the modulestore if it does not exist.
mock_create.return_value = None
module_store = modulestore()
if hasattr(module_store, '_drop_database'):
module_store._drop_database() # pylint: disable=protected-access
_CONTENTSTORE.clear()
if hasattr(module_store, 'close_connections'):
module_store.close_connections()
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
# This is an XML only modulestore with only the toy course loaded # This is an XML only modulestore with only the toy course loaded
...@@ -198,6 +215,71 @@ TEST_DATA_SPLIT_MODULESTORE = mixed_store_config( ...@@ -198,6 +215,71 @@ TEST_DATA_SPLIT_MODULESTORE = mixed_store_config(
) )
class SharedModuleStoreTestCase(TestCase):
"""
Subclass for any test case that uses a ModuleStore that can be shared
between individual tests. This class ensures that the ModuleStore is cleaned
before/after the entire test case has run. Use this class if your tests
set up one or a small number of courses that individual tests do not modify.
If your tests modify contents in the ModuleStore, you should use
ModuleStoreTestCase instead.
How to use::
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from student.tests.factories import CourseEnrollmentFactory, UserFactory
class MyModuleStoreTestCase(SharedModuleStoreTestCase):
@classmethod
def setUpClass(cls):
super(MyModuleStoreTestCase, cls).setUpClass()
cls.course = CourseFactory.create()
def setUp(self):
super(MyModuleStoreTestCase, self).setUp()
self.user = UserFactory.create()
CourseEnrollmentFactory.create(
user=self.user, course_id=self.course.id
)
Important things to note:
1. You're creating the course in setUpClass(), *not* in setUp().
2. Any Django ORM operations should still happen in setUp(). Models created
in setUpClass() will *not* be cleaned up, and will leave side-effects
that can break other, completely unrelated test cases.
In Django 1.8, we will be able to use setUpTestData() to do class level init
for Django ORM models that will get cleaned up properly.
"""
MODULESTORE = mixed_store_config(mkdtemp_clean(), {}, include_xml=False)
@classmethod
def setUpClass(cls):
super(SharedModuleStoreTestCase, cls).setUpClass()
cls._settings_override = override_settings(MODULESTORE=cls.MODULESTORE)
cls._settings_override.__enter__()
XMODULE_FACTORY_LOCK.enable()
clear_existing_modulestores()
cls.store = modulestore()
@classmethod
def tearDownClass(cls):
drop_mongo_collections() # pylint: disable=no-value-for-parameter
RequestCache().clear_request_cache()
XMODULE_FACTORY_LOCK.disable()
cls._settings_override.__exit__(None, None, None)
super(SharedModuleStoreTestCase, cls).tearDownClass()
def setUp(self):
# OverrideFieldData.provider_classes is always reset to `None` so
# that they're recalculated for every test
OverrideFieldData.provider_classes = None
super(SharedModuleStoreTestCase, self).setUp()
class ModuleStoreTestCase(TestCase): class ModuleStoreTestCase(TestCase):
""" """
Subclass for any test case that uses a ModuleStore. Subclass for any test case that uses a ModuleStore.
...@@ -254,8 +336,7 @@ class ModuleStoreTestCase(TestCase): ...@@ -254,8 +336,7 @@ class ModuleStoreTestCase(TestCase):
# which will cause them to be re-created # which will cause them to be re-created
clear_existing_modulestores() clear_existing_modulestores()
self.addCleanup(self.drop_mongo_collections) self.addCleanup(drop_mongo_collections)
self.addCleanup(RequestCache().clear_request_cache) self.addCleanup(RequestCache().clear_request_cache)
# Enable XModuleFactories for the space of this test (and its setUp). # Enable XModuleFactories for the space of this test (and its setUp).
...@@ -317,22 +398,6 @@ class ModuleStoreTestCase(TestCase): ...@@ -317,22 +398,6 @@ class ModuleStoreTestCase(TestCase):
updated_course = self.store.get_course(course.id) updated_course = self.store.get_course(course.id)
return updated_course return updated_course
@staticmethod
@patch('xmodule.modulestore.django.create_modulestore_instance')
def drop_mongo_collections(mock_create):
"""
If using a Mongo-backed modulestore & contentstore, drop the collections.
"""
# Do not create the modulestore if it does not exist.
mock_create.return_value = None
module_store = modulestore()
if hasattr(module_store, '_drop_database'):
module_store._drop_database() # pylint: disable=protected-access
_CONTENTSTORE.clear()
if hasattr(module_store, 'close_connections'):
module_store.close_connections()
def create_sample_course(self, org, course, run, block_info_tree=None, course_fields=None): def create_sample_course(self, org, course, run, block_info_tree=None, course_fields=None):
""" """
create a course in the default modulestore from the collection of BlockInfo create a course in the default modulestore from the collection of BlockInfo
......
...@@ -8,57 +8,64 @@ from itertools import izip_longest, chain ...@@ -8,57 +8,64 @@ from itertools import izip_longest, chain
import pytz import pytz
from student.tests.factories import AdminFactory from student.tests.factories import AdminFactory
from xmodule.modulestore.tests.django_utils import ( from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase, SharedModuleStoreTestCase,
TEST_DATA_SPLIT_MODULESTORE TEST_DATA_SPLIT_MODULESTORE
) )
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from ..models import CustomCourseForEdX from ..models import CustomCourseForEdX
class TestCCXModulestoreWrapper(ModuleStoreTestCase): class TestCCXModulestoreWrapper(SharedModuleStoreTestCase):
"""tests for a modulestore wrapped by CCXModulestoreWrapper """tests for a modulestore wrapped by CCXModulestoreWrapper
""" """
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
def setUp(self): @classmethod
""" def setUpClass(cls):
Set up tests super(TestCCXModulestoreWrapper, cls).setUpClass()
""" cls.course = course = CourseFactory.create()
super(TestCCXModulestoreWrapper, self).setUp() cls.mooc_start = start = datetime.datetime(
self.course = course = CourseFactory.create() 2010, 5, 12, 2, 42, tzinfo=pytz.UTC
)
# Create instructor account cls.mooc_due = due = datetime.datetime(
coach = AdminFactory.create() 2010, 7, 7, 0, 0, tzinfo=pytz.UTC
)
# Create a course outline # Create a course outline
self.mooc_start = start = datetime.datetime( cls.chapters = chapters = [
2010, 5, 12, 2, 42, tzinfo=pytz.UTC)
self.mooc_due = due = datetime.datetime(
2010, 7, 7, 0, 0, tzinfo=pytz.UTC)
self.chapters = chapters = [
ItemFactory.create(start=start, parent=course) for _ in xrange(2) ItemFactory.create(start=start, parent=course) for _ in xrange(2)
] ]
self.sequentials = sequentials = [ cls.sequentials = sequentials = [
ItemFactory.create(parent=c) for _ in xrange(2) for c in chapters ItemFactory.create(parent=c) for _ in xrange(2) for c in chapters
] ]
self.verticals = verticals = [ cls.verticals = verticals = [
ItemFactory.create( ItemFactory.create(
due=due, parent=s, graded=True, format='Homework' due=due, parent=s, graded=True, format='Homework'
) for _ in xrange(2) for s in sequentials ) for _ in xrange(2) for s in sequentials
] ]
self.blocks = [ cls.blocks = [
ItemFactory.create(parent=v) for _ in xrange(2) for v in verticals ItemFactory.create(parent=v) for _ in xrange(2) for v in verticals
] ]
def setUp(self):
"""
Set up tests
"""
super(TestCCXModulestoreWrapper, self).setUp()
self.user = UserFactory.create()
# Create instructor account
coach = AdminFactory.create()
self.ccx = ccx = CustomCourseForEdX( self.ccx = ccx = CustomCourseForEdX(
course_id=course.id, course_id=self.course.id,
display_name='Test CCX', display_name='Test CCX',
coach=coach coach=coach
) )
ccx.save() ccx.save()
self.ccx_locator = CCXLocator.from_course_locator(course.id, ccx.id) # pylint: disable=no-member self.ccx_locator = CCXLocator.from_course_locator(self.course.id, ccx.id) # pylint: disable=no-member
def get_all_children_bf(self, block): def get_all_children_bf(self, block):
"""traverse the children of block in a breadth-first order""" """traverse the children of block in a breadth-first order"""
......
...@@ -15,22 +15,26 @@ from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentF ...@@ -15,22 +15,26 @@ from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentF
from student.models import CourseEnrollment from student.models import CourseEnrollment
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from .factories import CourseTeamFactory from .factories import CourseTeamFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
@attr('shard_1') @attr('shard_1')
class TestDashboard(ModuleStoreTestCase): class TestDashboard(SharedModuleStoreTestCase):
"""Tests for the Teams dashboard.""" """Tests for the Teams dashboard."""
test_password = "test" test_password = "test"
@classmethod
def setUpClass(cls):
super(TestDashboard, cls).setUpClass()
cls.course = CourseFactory.create(
teams_configuration={"max_team_size": 10, "topics": [{"name": "foo", "id": 0, "description": "test topic"}]}
)
def setUp(self): def setUp(self):
""" """
Set up tests Set up tests
""" """
super(TestDashboard, self).setUp() super(TestDashboard, self).setUp()
self.course = CourseFactory.create(
teams_configuration={"max_team_size": 10, "topics": [{"name": "foo", "id": 0, "description": "test topic"}]}
)
# will be assigned to self.client by default # will be assigned to self.client by default
self.user = UserFactory.create(password=self.test_password) self.user = UserFactory.create(password=self.test_password)
self.teams_url = reverse('teams_dashboard', args=[self.course.id]) self.teams_url = reverse('teams_dashboard', args=[self.course.id])
...@@ -96,14 +100,14 @@ class TestDashboard(ModuleStoreTestCase): ...@@ -96,14 +100,14 @@ class TestDashboard(ModuleStoreTestCase):
self.assertEqual(404, response.status_code) self.assertEqual(404, response.status_code)
class TeamAPITestCase(APITestCase, ModuleStoreTestCase): class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
"""Base class for Team API test cases.""" """Base class for Team API test cases."""
test_password = 'password' test_password = 'password'
def setUp(self): @classmethod
super(TeamAPITestCase, self).setUp() def setUpClass(cls):
super(TeamAPITestCase, cls).setUpClass()
teams_configuration = { teams_configuration = {
'topics': 'topics':
[ [
...@@ -114,16 +118,17 @@ class TeamAPITestCase(APITestCase, ModuleStoreTestCase): ...@@ -114,16 +118,17 @@ class TeamAPITestCase(APITestCase, ModuleStoreTestCase):
} for i, name in enumerate([u'sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power']) } for i, name in enumerate([u'sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power'])
] ]
} }
self.topics_count = 4 cls.test_course_1 = CourseFactory.create(
self.test_course_1 = CourseFactory.create(
org='TestX', org='TestX',
course='TS101', course='TS101',
display_name='Test Course', display_name='Test Course',
teams_configuration=teams_configuration teams_configuration=teams_configuration
) )
self.test_course_2 = CourseFactory.create(org='MIT', course='6.002x', display_name='Circuits') cls.test_course_2 = CourseFactory.create(org='MIT', course='6.002x', display_name='Circuits')
def setUp(self):
super(TeamAPITestCase, self).setUp()
self.topics_count = 4
self.users = { self.users = {
'student_unenrolled': UserFactory.create(password=self.test_password), 'student_unenrolled': UserFactory.create(password=self.test_password),
'student_enrolled': UserFactory.create(password=self.test_password), 'student_enrolled': UserFactory.create(password=self.test_password),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment