From 14ad8cd78f6204606d439a2b9493fd8d52691c0f Mon Sep 17 00:00:00 2001 From: John Eskew Date: Mon, 29 Feb 2016 14:41:17 -0500 Subject: [PATCH 1/3] SharedModuleStoreTestCase conversion for speedup. --- .../courseware/tests/test_field_overrides.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/courseware/tests/test_field_overrides.py b/lms/djangoapps/courseware/tests/test_field_overrides.py index 65c263aad8..70eb9660f3 100644 --- a/lms/djangoapps/courseware/tests/test_field_overrides.py +++ b/lms/djangoapps/courseware/tests/test_field_overrides.py @@ -7,7 +7,7 @@ from nose.plugins.attrib import attr from django.test.utils import override_settings from xblock.field_data import DictFieldData from xmodule.modulestore.tests.factories import CourseFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from ..field_overrides import ( disable_overrides, @@ -23,14 +23,21 @@ TESTUSER = "testuser" @attr('shard_1') @override_settings(FIELD_OVERRIDE_PROVIDERS=( 'courseware.tests.test_field_overrides.TestOverrideProvider',)) -class OverrideFieldDataTests(ModuleStoreTestCase): +class OverrideFieldDataTests(SharedModuleStoreTestCase): """ Tests for `OverrideFieldData`. """ + @classmethod + def setUpClass(cls): + """ + Course is created here and shared by all the class's tests. + """ + super(OverrideFieldDataTests, cls).setUpClass() + cls.course = CourseFactory.create(enable_ccx=True) + def setUp(self): super(OverrideFieldDataTests, self).setUp() - self.course = CourseFactory.create(enable_ccx=True) OverrideFieldData.provider_classes = None def tearDown(self): From 65e26e3ec24c6a9b75000230a378ff3f7aaa0544 Mon Sep 17 00:00:00 2001 From: John Eskew Date: Mon, 29 Feb 2016 15:08:28 -0500 Subject: [PATCH 2/3] Move user/admin model creation to class level. Wrap whole test with several publish/unpublish in same bulk_op. --- .../ccx/tests/test_ccx_modulestore.py | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py index 6f0ddbe14d..81f1e0a154 100644 --- a/lms/djangoapps/ccx/tests/test_ccx_modulestore.py +++ b/lms/djangoapps/ccx/tests/test_ccx_modulestore.py @@ -25,16 +25,12 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): @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 - ) + cls.course = CourseFactory.create() + start = datetime.datetime(2010, 5, 12, 2, 42, tzinfo=pytz.UTC) + due = datetime.datetime(2010, 7, 7, 0, 0, tzinfo=pytz.UTC) # Create a course outline cls.chapters = chapters = [ - ItemFactory.create(start=start, parent=course) for _ in xrange(2) + ItemFactory.create(start=start, parent=cls.course) for _ in xrange(2) ] cls.sequentials = sequentials = [ ItemFactory.create(parent=c) for _ in xrange(2) for c in chapters @@ -48,20 +44,24 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): ItemFactory.create(parent=v, category='html') for _ in xrange(2) for v in verticals ] + @classmethod + def setUpTestData(cls): + """ + Set up models for the whole TestCase. + """ + cls.user = UserFactory.create() + # Create instructor account + cls.coach = AdminFactory.create() + 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=self.course.id, display_name='Test CCX', - coach=coach + coach=self.coach ) ccx.save() @@ -132,12 +132,13 @@ class TestCCXModulestoreWrapper(SharedModuleStoreTestCase): def test_publication_api(self): """verify that we can correctly discern a published item by ccx key""" - for expected in self.blocks: - block_key = self.ccx_locator.make_usage_key( - expected.location.block_type, expected.location.block_id - ) - self.assertTrue(self.store.has_published_version(expected)) - self.store.unpublish(block_key, self.user.id) - self.assertFalse(self.store.has_published_version(expected)) - self.store.publish(block_key, self.user.id) - self.assertTrue(self.store.has_published_version(expected)) + with self.store.bulk_operations(self.ccx_locator): + for expected in self.blocks: + block_key = self.ccx_locator.make_usage_key( + expected.location.block_type, expected.location.block_id + ) + self.assertTrue(self.store.has_published_version(expected)) + self.store.unpublish(block_key, self.user.id) + self.assertFalse(self.store.has_published_version(expected)) + self.store.publish(block_key, self.user.id) + self.assertTrue(self.store.has_published_version(expected)) From b777530dac9755ad5a9266533e28fc1f8096fbf9 Mon Sep 17 00:00:00 2001 From: John Eskew Date: Mon, 29 Feb 2016 17:05:00 -0500 Subject: [PATCH 3/3] Add SharedModuleStoreTestCase.setUpClassAndTestData() context manager for use when a test class has a setUpTestData() method which uses variables set up in the setUpClass() method. Change base teams API test class to use the context manager. --- .../xmodule/modulestore/tests/django_utils.py | 42 +++- lms/djangoapps/teams/tests/test_views.py | 192 +++++++++--------- 2 files changed, 136 insertions(+), 98 deletions(-) diff --git a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py index 5ed4ce201e..29110f2ccc 100644 --- a/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py +++ b/common/lib/xmodule/xmodule/modulestore/tests/django_utils.py @@ -4,6 +4,7 @@ Modulestore configuration for test cases. """ import functools from uuid import uuid4 +from contextlib import contextmanager from mock import patch @@ -267,15 +268,50 @@ class SharedModuleStoreTestCase(TestCase): MODULESTORE = mixed_store_config(mkdtemp_clean(), {}, include_xml=False) @classmethod - def setUpClass(cls): - super(SharedModuleStoreTestCase, cls).setUpClass() - + def _setUpModuleStore(cls): # pylint: disable=invalid-name + """ + Set up the modulestore for an entire test class. + """ cls._settings_override = override_settings(MODULESTORE=cls.MODULESTORE) cls._settings_override.__enter__() XMODULE_FACTORY_LOCK.enable() clear_existing_modulestores() cls.store = modulestore() + @classmethod + @contextmanager + def setUpClassAndTestData(cls): # pylint: disable=invalid-name + """ + For use when the test class has a setUpTestData() method that uses variables + that are setup during setUpClass() of the same test class. + + Use it like so: + + @classmethod + def setUpClass(cls): + with super(MyTestClass, cls).setUpClassAndTestData(): + + + @classmethod + def setUpTestData(cls): + + + """ + cls._setUpModuleStore() + # Now yield to allow the test class to run its setUpClass() setup code. + yield + # Now call the base class, which calls back into the test class's setUpTestData(). + super(SharedModuleStoreTestCase, cls).setUpClass() + + @classmethod + def setUpClass(cls): + """ + For use when the test class has no setUpTestData() method -or- + when that method does not use variable set up in setUpClass(). + """ + super(SharedModuleStoreTestCase, cls).setUpClass() + cls._setUpModuleStore() + @classmethod def tearDownClass(cls): drop_mongo_collections() # pylint: disable=no-value-for-parameter diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py index 4f0673103e..bb2402aca8 100644 --- a/lms/djangoapps/teams/tests/test_views.py +++ b/lms/djangoapps/teams/tests/test_views.py @@ -200,89 +200,90 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): @classmethod def setUpClass(cls): - super(TeamAPITestCase, cls).setUpClass() - teams_configuration_1 = { - 'topics': - [ - { - 'id': 'topic_{}'.format(i), - 'name': name, - 'description': 'Description for topic {}.'.format(i) - } for i, name in enumerate([u'Sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power']) - ] - } - cls.test_course_1 = CourseFactory.create( - org='TestX', - course='TS101', - display_name='Test Course', - teams_configuration=teams_configuration_1 - ) + with super(TeamAPITestCase, cls).setUpClassAndTestData(): + teams_configuration_1 = { + 'topics': + [ + { + 'id': 'topic_{}'.format(i), + 'name': name, + 'description': 'Description for topic {}.'.format(i) + } for i, name in enumerate([u'Sólar power', 'Wind Power', 'Nuclear Power', 'Coal Power']) + ] + } + cls.test_course_1 = CourseFactory.create( + org='TestX', + course='TS101', + display_name='Test Course', + teams_configuration=teams_configuration_1 + ) - teams_configuration_2 = { - 'topics': - [ - { - 'id': 'topic_5', - 'name': 'Other Interests', - 'description': 'Description for topic 5.' - }, - { - 'id': 'topic_6', - 'name': 'Public Profiles', - 'description': 'Description for topic 6.' - }, - { - 'id': 'Topic_6.5', - 'name': 'Test Accessibility Topic', - 'description': 'Description for Topic_6.5' - }, - ], - 'max_team_size': 1 - } - cls.test_course_2 = CourseFactory.create( - org='MIT', - course='6.002x', - display_name='Circuits', - teams_configuration=teams_configuration_2 - ) + teams_configuration_2 = { + 'topics': + [ + { + 'id': 'topic_5', + 'name': 'Other Interests', + 'description': 'Description for topic 5.' + }, + { + 'id': 'topic_6', + 'name': 'Public Profiles', + 'description': 'Description for topic 6.' + }, + { + 'id': 'Topic_6.5', + 'name': 'Test Accessibility Topic', + 'description': 'Description for Topic_6.5' + }, + ], + 'max_team_size': 1 + } + cls.test_course_2 = CourseFactory.create( + org='MIT', + course='6.002x', + display_name='Circuits', + teams_configuration=teams_configuration_2 + ) - def setUp(self): - super(TeamAPITestCase, self).setUp() - self.topics_count = 4 - self.users = { - 'staff': AdminFactory.create(password=self.test_password), - 'course_staff': StaffFactory.create(course_key=self.test_course_1.id, password=self.test_password) + @classmethod + def setUpTestData(cls): + super(TeamAPITestCase, cls).setUpTestData() + cls.topics_count = 4 + cls.users = { + 'staff': AdminFactory.create(password=cls.test_password), + 'course_staff': StaffFactory.create(course_key=cls.test_course_1.id, password=cls.test_password) } - self.create_and_enroll_student(username='student_enrolled') - self.create_and_enroll_student(username='student_enrolled_not_on_team') - self.create_and_enroll_student(username='student_unenrolled', courses=[]) + cls.create_and_enroll_student(username='student_enrolled') + cls.create_and_enroll_student(username='student_enrolled_not_on_team') + cls.create_and_enroll_student(username='student_unenrolled', courses=[]) # Make this student a community TA. - self.create_and_enroll_student(username='community_ta') - seed_permissions_roles(self.test_course_1.id) - community_ta_role = Role.objects.get(name=FORUM_ROLE_COMMUNITY_TA, course_id=self.test_course_1.id) - community_ta_role.users.add(self.users['community_ta']) + cls.create_and_enroll_student(username='community_ta') + seed_permissions_roles(cls.test_course_1.id) + community_ta_role = Role.objects.get(name=FORUM_ROLE_COMMUNITY_TA, course_id=cls.test_course_1.id) + community_ta_role.users.add(cls.users['community_ta']) # This student is enrolled in both test courses and is a member of a team in each course, but is not on the # same team as student_enrolled. - self.create_and_enroll_student( - courses=[self.test_course_1, self.test_course_2], + cls.create_and_enroll_student( + courses=[cls.test_course_1, cls.test_course_2], username='student_enrolled_both_courses_other_team' ) # Make this student have a public profile - self.create_and_enroll_student( - courses=[self.test_course_2], + cls.create_and_enroll_student( + courses=[cls.test_course_2], username='student_enrolled_public_profile' ) - profile = self.users['student_enrolled_public_profile'].profile + profile = cls.users['student_enrolled_public_profile'].profile profile.year_of_birth = 1970 profile.save() # This student is enrolled in the other course, but not yet a member of a team. This is to allow # course_2 to use a max_team_size of 1 without breaking other tests on course_1 - self.create_and_enroll_student( - courses=[self.test_course_2], + cls.create_and_enroll_student( + courses=[cls.test_course_2], username='student_enrolled_other_course_not_on_team' ) @@ -292,58 +293,58 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): sender=CourseTeam, dispatch_uid='teams.signals.course_team_post_save_callback' ): - self.solar_team = CourseTeamFactory.create( + cls.solar_team = CourseTeamFactory.create( name=u'Sólar team', - course_id=self.test_course_1.id, + course_id=cls.test_course_1.id, topic_id='topic_0' ) - self.wind_team = CourseTeamFactory.create(name='Wind Team', course_id=self.test_course_1.id) - self.nuclear_team = CourseTeamFactory.create(name='Nuclear Team', course_id=self.test_course_1.id) - self.another_team = CourseTeamFactory.create(name='Another Team', course_id=self.test_course_2.id) - self.public_profile_team = CourseTeamFactory.create( + cls.wind_team = CourseTeamFactory.create(name='Wind Team', course_id=cls.test_course_1.id) + cls.nuclear_team = CourseTeamFactory.create(name='Nuclear Team', course_id=cls.test_course_1.id) + cls.another_team = CourseTeamFactory.create(name='Another Team', course_id=cls.test_course_2.id) + cls.public_profile_team = CourseTeamFactory.create( name='Public Profile Team', - course_id=self.test_course_2.id, + course_id=cls.test_course_2.id, topic_id='topic_6' ) - self.search_team = CourseTeamFactory.create( + cls.search_team = CourseTeamFactory.create( name='Search', description='queryable text', country='GS', language='to', - course_id=self.test_course_2.id, + course_id=cls.test_course_2.id, topic_id='topic_7' ) - self.chinese_team = CourseTeamFactory.create( + cls.chinese_team = CourseTeamFactory.create( name=u'著文企臺個', description=u'共樣地面較,件展冷不護者這與民教過住意,國制銀產物助音是勢一友', country='CN', language='zh_HANS', - course_id=self.test_course_2.id, + course_id=cls.test_course_2.id, topic_id='topic_7' ) - self.test_team_name_id_map = {team.name: team for team in ( - self.solar_team, - self.wind_team, - self.nuclear_team, - self.another_team, - self.public_profile_team, - self.search_team, - self.chinese_team, + cls.test_team_name_id_map = {team.name: team for team in ( + cls.solar_team, + cls.wind_team, + cls.nuclear_team, + cls.another_team, + cls.public_profile_team, + cls.search_team, + cls.chinese_team, )} - for user, course in [('staff', self.test_course_1), ('course_staff', self.test_course_1)]: + for user, course in [('staff', cls.test_course_1), ('course_staff', cls.test_course_1)]: CourseEnrollment.enroll( - self.users[user], course.id, check_access=True + cls.users[user], course.id, check_access=True ) # Django Rest Framework v3 requires us to pass a request to serializers # that have URL fields. Since we're invoking this code outside the context # of a request, we need to simulate that there's a request. - self.solar_team.add_user(self.users['student_enrolled']) - self.nuclear_team.add_user(self.users['student_enrolled_both_courses_other_team']) - self.another_team.add_user(self.users['student_enrolled_both_courses_other_team']) - self.public_profile_team.add_user(self.users['student_enrolled_public_profile']) + cls.solar_team.add_user(cls.users['student_enrolled']) + cls.nuclear_team.add_user(cls.users['student_enrolled_both_courses_other_team']) + cls.another_team.add_user(cls.users['student_enrolled_both_courses_other_team']) + cls.public_profile_team.add_user(cls.users['student_enrolled_public_profile']) def build_membership_data_raw(self, username, team): """Assembles a membership creation payload based on the raw values provided.""" @@ -353,21 +354,22 @@ class TeamAPITestCase(APITestCase, SharedModuleStoreTestCase): """Assembles a membership creation payload based on the username and team model provided.""" return self.build_membership_data_raw(self.users[username].username, team.team_id) - def create_and_enroll_student(self, courses=None, username=None): + @classmethod + def create_and_enroll_student(cls, courses=None, username=None): """ Creates a new student and enrolls that student in the course. - Adds the new user to the self.users dictionary with the username as the key. + Adds the new user to the cls.users dictionary with the username as the key. Returns the username once the user has been created. """ if username is not None: - user = UserFactory.create(password=self.test_password, username=username) + user = UserFactory.create(password=cls.test_password, username=username) else: - user = UserFactory.create(password=self.test_password) - courses = courses if courses is not None else [self.test_course_1] + user = UserFactory.create(password=cls.test_password) + courses = courses if courses is not None else [cls.test_course_1] for course in courses: CourseEnrollment.enroll(user, course.id, check_access=True) - self.users[user.username] = user + cls.users[user.username] = user return user.username