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):
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
# This is an XML only modulestore with only the toy course loaded
......@@ -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):
"""
Subclass for any test case that uses a ModuleStore.
......@@ -254,8 +336,7 @@ class ModuleStoreTestCase(TestCase):
# which will cause them to be re-created
clear_existing_modulestores()
self.addCleanup(self.drop_mongo_collections)
self.addCleanup(drop_mongo_collections)
self.addCleanup(RequestCache().clear_request_cache)
# Enable XModuleFactories for the space of this test (and its setUp).
......@@ -317,22 +398,6 @@ class ModuleStoreTestCase(TestCase):
updated_course = self.store.get_course(course.id)
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):
"""
create a course in the default modulestore from the collection of BlockInfo
......
......@@ -8,57 +8,64 @@ from itertools import izip_longest, chain
import pytz
from student.tests.factories import AdminFactory
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
SharedModuleStoreTestCase,
TEST_DATA_SPLIT_MODULESTORE
)
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from ..models import CustomCourseForEdX
class TestCCXModulestoreWrapper(ModuleStoreTestCase):
class TestCCXModulestoreWrapper(SharedModuleStoreTestCase):
"""tests for a modulestore wrapped by CCXModulestoreWrapper
"""
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
def setUp(self):
"""
Set up tests
"""
super(TestCCXModulestoreWrapper, self).setUp()
self.course = course = CourseFactory.create()
# Create instructor account
coach = AdminFactory.create()
@classmethod
def setUpClass(cls):
super(TestCCXModulestoreWrapper, cls).setUpClass()
cls.course = course = CourseFactory.create()
cls.mooc_start = start = datetime.datetime(
2010, 5, 12, 2, 42, tzinfo=pytz.UTC
)
cls.mooc_due = due = datetime.datetime(
2010, 7, 7, 0, 0, tzinfo=pytz.UTC
)
# Create a course outline
self.mooc_start = start = datetime.datetime(
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 = [
cls.chapters = chapters = [
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
]
self.verticals = verticals = [
cls.verticals = verticals = [
ItemFactory.create(
due=due, parent=s, graded=True, format='Homework'
) for _ in xrange(2) for s in sequentials
]
self.blocks = [
cls.blocks = [
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(
course_id=course.id,
course_id=self.course.id,
display_name='Test CCX',
coach=coach
)
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):
"""traverse the children of block in a breadth-first order"""
......
......@@ -15,22 +15,26 @@ from student.tests.factories import UserFactory, AdminFactory, CourseEnrollmentF
from student.models import CourseEnrollment
from xmodule.modulestore.tests.factories import CourseFactory
from .factories import CourseTeamFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
@attr('shard_1')
class TestDashboard(ModuleStoreTestCase):
class TestDashboard(SharedModuleStoreTestCase):
"""Tests for the Teams dashboard."""
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):
"""
Set up tests
"""
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
self.user = UserFactory.create(password=self.test_password)
self.teams_url = reverse('teams_dashboard', args=[self.course.id])
......@@ -96,14 +100,14 @@ class TestDashboard(ModuleStoreTestCase):
self.assertEqual(404, response.status_code)
class TeamAPITestCase(APITestCase, ModuleStoreTestCase):
class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase):
"""Base class for Team API test cases."""
test_password = 'password'
def setUp(self):
super(TeamAPITestCase, self).setUp()
@classmethod
def setUpClass(cls):
super(TeamAPITestCase, cls).setUpClass()
teams_configuration = {
'topics':
[
......@@ -114,16 +118,17 @@ class TeamAPITestCase(APITestCase, ModuleStoreTestCase):
} for i, name in enumerate([u'sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power'])
]
}
self.topics_count = 4
self.test_course_1 = CourseFactory.create(
cls.test_course_1 = CourseFactory.create(
org='TestX',
course='TS101',
display_name='Test Course',
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 = {
'student_unenrolled': 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