From bc20a37c2b9edf4976f7141522dbc59772c31e6f Mon Sep 17 00:00:00 2001
From: Calen Pennington <cale@edx.org>
Date: Wed, 27 Apr 2016 16:46:48 -0400
Subject: [PATCH] Simplify how shards are selected in CI

---
 .../course_modes/tests/test_views.py          |  2 +-
 common/djangoapps/embargo/tests/test_api.py   |  2 +-
 .../embargo/tests/test_middleware.py          |  2 +-
 .../djangoapps/enrollment/tests/test_views.py |  2 +-
 .../external_auth/tests/test_shib.py          |  2 +-
 .../student/tests/test_enrollment.py          |  2 +-
 .../student/tests/test_recent_enrollments.py  |  2 +-
 .../student/tests/test_verification_status.py |  2 +-
 common/djangoapps/student/tests/tests.py      |  2 +-
 .../track/views/tests/test_segmentio.py       |  2 +-
 .../discussion/test_cohort_management.py      |  6 +-
 .../tests/discussion/test_cohorts.py          |  8 +--
 .../tests/discussion/test_discussion.py       | 22 +++----
 .../tests/lms/test_account_settings.py        |  4 +-
 .../acceptance/tests/lms/test_bookmarks.py    |  2 +-
 common/test/acceptance/tests/lms/test_ccx.py  |  2 +-
 .../tests/lms/test_certificate_web_view.py    |  4 +-
 .../tests/lms/test_learner_profile.py         |  4 +-
 .../test/acceptance/tests/lms/test_library.py |  6 +-
 common/test/acceptance/tests/lms/test_lms.py  | 30 +++++-----
 .../test_lms_cohorted_courseware_search.py    |  2 +-
 .../tests/lms/test_lms_courseware_search.py   |  2 +-
 .../acceptance/tests/lms/test_lms_edxnotes.py |  8 +--
 .../lms/test_lms_instructor_dashboard.py      | 12 ++--
 .../test_lms_split_test_courseware_search.py  |  2 +-
 .../tests/lms/test_lms_user_preview.py        | 10 ++--
 .../tests/lms/test_problem_types.py           |  8 +--
 .../test/acceptance/tests/lms/test_teams.py   | 12 ++--
 .../tests/studio/test_import_export.py        | 16 +++---
 .../tests/studio/test_studio_container.py     | 18 +++---
 .../tests/studio/test_studio_course_create.py |  2 +-
 .../tests/studio/test_studio_course_team.py   |  2 +-
 .../tests/studio/test_studio_library.py       |  4 +-
 .../studio/test_studio_library_container.py   |  2 +-
 .../tests/studio/test_studio_outline.py       | 32 +++++------
 .../tests/studio/test_studio_settings.py      |  6 +-
 .../test_studio_settings_certificates.py      |  2 +-
 .../studio/test_studio_settings_details.py    |  6 +-
 .../tests/studio/test_studio_split_test.py    |  6 +-
 .../tests/studio/test_studio_textbooks.py     |  2 +-
 .../tests/test_cohorted_courseware.py         |  5 +-
 .../tests/video/test_studio_video_editor.py   |  2 +-
 .../tests/video/test_studio_video_module.py   |  2 +-
 .../video/test_studio_video_transcript.py     |  2 +-
 .../tests/video/test_video_events.py          |  2 +-
 .../tests/video/test_video_handout.py         |  2 +-
 .../tests/video/test_video_license.py         |  2 +-
 .../tests/video/test_video_module.py          | 10 ++--
 lms/djangoapps/badges/api/tests.py            |  2 +-
 lms/djangoapps/badges/tests/test_models.py    |  2 +-
 lms/djangoapps/branding/tests/test_models.py  |  2 +-
 lms/djangoapps/branding/tests/test_page.py    |  6 +-
 .../bulk_email/tests/test_course_optout.py    |  2 +-
 lms/djangoapps/bulk_email/tests/test_email.py |  4 +-
 .../bulk_email/tests/test_err_handling.py     |  2 +-
 lms/djangoapps/bulk_email/tests/test_forms.py |  2 +-
 .../bulk_email/tests/test_models.py           |  8 +--
 lms/djangoapps/bulk_email/tests/test_tasks.py |  2 +-
 lms/djangoapps/ccx/api/v0/tests/test_views.py |  4 +-
 .../tests/test_field_override_performance.py  |  2 +-
 lms/djangoapps/ccx/tests/test_models.py       |  2 +-
 lms/djangoapps/ccx/tests/test_overrides.py    |  2 +-
 lms/djangoapps/ccx/tests/test_utils.py        |  4 +-
 lms/djangoapps/ccx/tests/test_views.py        |  8 +--
 lms/djangoapps/certificates/tests/test_api.py | 14 ++---
 .../tests/test_cert_management.py             |  6 +-
 .../tests/test_create_fake_cert.py            |  2 +-
 .../certificates/tests/test_models.py         | 12 ++--
 .../certificates/tests/test_queue.py          |  4 +-
 .../certificates/tests/test_views.py          |  4 +-
 .../certificates/tests/test_webview_views.py  |  4 +-
 lms/djangoapps/certificates/tests/tests.py    |  2 +-
 .../tests/test_dashboard_data.py              |  2 +-
 .../class_dashboard/tests/test_views.py       |  2 +-
 .../commerce/api/v0/tests/test_views.py       |  4 +-
 .../commerce/api/v1/tests/test_views.py       |  4 +-
 lms/djangoapps/commerce/tests/test_views.py   |  2 +-
 .../transformers/tests/test_milestones.py     |  2 +-
 .../course_api/tests/test_serializers.py      |  2 +-
 lms/djangoapps/course_api/tests/test_views.py |  2 +-
 .../transformers/tests/test_hidden_content.py |  2 +-
 .../transformers/tests/test_split_test.py     |  2 +-
 .../transformers/tests/test_start_date.py     |  2 +-
 .../tests/test_user_partitions.py             |  4 +-
 .../transformers/tests/test_visibility.py     |  2 +-
 .../course_wiki/tests/test_access.py          |  8 +--
 .../tests/test_comprehensive_theming.py       |  2 +-
 .../course_wiki/tests/test_middleware.py      |  2 +-
 lms/djangoapps/course_wiki/tests/tests.py     |  2 +-
 .../commands/tests/test_dump_course.py        |  2 +-
 lms/djangoapps/courseware/tests/test_about.py | 14 ++---
 .../courseware/tests/test_access.py           |  6 +-
 .../courseware/tests/test_course_info.py      | 10 ++--
 .../courseware/tests/test_course_survey.py    |  2 +-
 .../courseware/tests/test_courses.py          | 12 ++--
 .../courseware/tests/test_date_summary.py     |  2 +-
 .../tests/test_draft_modulestore.py           |  2 +-
 .../courseware/tests/test_entrance_exam.py    |  2 +-
 .../courseware/tests/test_favicon.py          |  2 +-
 .../courseware/tests/test_field_overrides.py  |  6 +-
 .../courseware/tests/test_footer.py           |  2 +-
 .../courseware/tests/test_group_access.py     |  2 +-
 lms/djangoapps/courseware/tests/test_i18n.py  |  6 +-
 .../courseware/tests/test_lti_integration.py  |  4 +-
 .../courseware/tests/test_masquerade.py       |  8 +--
 .../courseware/tests/test_microsites.py       |  2 +-
 .../courseware/tests/test_middleware.py       |  2 +-
 .../courseware/tests/test_model_data.py       | 10 ++--
 .../courseware/tests/test_module_render.py    | 36 ++++++------
 .../courseware/tests/test_navigation.py       |  2 +-
 .../courseware/tests/test_password_history.py |  2 +-
 .../courseware/tests/test_split_module.py     |  4 +-
 .../tests/test_submitting_problems.py         |  8 +--
 lms/djangoapps/courseware/tests/test_tabs.py  | 18 +++---
 .../courseware/tests/test_video_handlers.py   | 14 ++---
 .../courseware/tests/test_video_mongo.py      | 14 ++---
 .../courseware/tests/test_video_xml.py        |  2 +-
 .../tests/test_view_authentication.py         |  4 +-
 lms/djangoapps/courseware/tests/test_views.py | 18 +++---
 .../courseware/tests/test_word_cloud.py       |  2 +-
 lms/djangoapps/courseware/tests/tests.py      |  8 +--
 .../coursewarehistoryextended/tests.py        |  2 +-
 .../commands/tests/test_git_add_course.py     |  2 +-
 .../dashboard/tests/test_sysadmin.py          |  2 +-
 .../discussion_api/tests/test_api.py          | 24 ++++----
 .../discussion_api/tests/test_permissions.py  |  2 +-
 .../discussion_api/tests/test_serializers.py  |  2 +-
 .../discussion_api/tests/test_views.py        |  6 +-
 .../django_comment_client/base/tests.py       | 26 ++++-----
 .../tests/test_middleware.py                  |  2 +-
 .../tests/test_models.py                      |  4 +-
 .../django_comment_client/tests/test_utils.py | 14 ++---
 lms/djangoapps/edxnotes/tests.py              |  8 +--
 lms/djangoapps/gating/tests/test_api.py       |  2 +-
 lms/djangoapps/grades/tests/test_grades.py    |  2 +-
 .../instructor/tests/test_access.py           |  8 +--
 lms/djangoapps/instructor/tests/test_api.py   | 38 ++++++-------
 .../tests/test_api_email_localization.py      |  2 +-
 .../instructor/tests/test_certificates.py     | 12 ++--
 .../instructor/tests/test_ecommerce.py        |  2 +-
 lms/djangoapps/instructor/tests/test_email.py |  4 +-
 .../instructor/tests/test_enrollment.py       | 16 +++---
 .../instructor/tests/test_proctoring.py       |  2 +-
 .../tests/test_registration_codes.py          |  2 +-
 .../instructor/tests/test_services.py         |  2 +-
 .../instructor/tests/test_spoc_gradebook.py   |  6 +-
 lms/djangoapps/instructor/tests/test_tools.py | 18 +++---
 .../tests/views/test_instructor_dashboard.py  |  2 +-
 .../instructor_analytics/tests/test_basic.py  |  2 +-
 .../instructor_task/tests/test_api.py         |  4 +-
 .../instructor_task/tests/test_integration.py |  2 +-
 .../instructor_task/tests/test_tasks.py       |  6 +-
 .../tests/test_tasks_helper.py                |  4 +-
 .../lti_provider/tests/test_views.py          |  2 +-
 .../mobile_api/course_info/tests.py           |  4 +-
 lms/djangoapps/mobile_api/users/tests.py      | 14 ++---
 .../mobile_api/video_outlines/tests.py        |  6 +-
 .../shoppingcart/tests/test_models.py         |  4 +-
 .../shoppingcart/tests/test_views.py          |  2 +-
 .../student_account/test/test_views.py        |  2 +-
 lms/djangoapps/support/tests/test_views.py    |  2 +-
 lms/djangoapps/teams/tests/test_views.py      |  2 +-
 .../tests/test_views.py                       |  2 +-
 .../verify_student/tests/test_views.py        | 24 ++++----
 lms/lib/xblock/test/test_mixin.py             |  4 +-
 .../djangoapps/bookmarks/tests/test_api.py    |  2 +-
 .../djangoapps/bookmarks/tests/test_models.py |  4 +-
 .../bookmarks/tests/test_services.py          |  2 +-
 .../djangoapps/bookmarks/tests/test_tasks.py  |  2 +-
 .../djangoapps/bookmarks/tests/test_views.py  |  4 +-
 .../core/djangoapps/ccxcon/tests/test_api.py  |  2 +-
 .../djangoapps/ccxcon/tests/test_signals.py   |  2 +-
 .../djangoapps/ccxcon/tests/test_tasks.py     |  2 +-
 .../tests/test_generate_course_overview.py    |  2 +-
 .../content/course_overviews/tests.py         |  4 +-
 .../content/course_structures/tests.py        |  2 +-
 .../tests/test_post_cohort_membership_fix.py  |  2 +-
 .../course_groups/tests/test_cohorts.py       |  6 +-
 .../tests/test_partition_scheme.py            |  8 +--
 .../course_groups/tests/test_views.py         | 14 ++---
 .../credentials/tests/test_models.py          |  2 +-
 .../credentials/tests/test_utils.py           |  2 +-
 .../core/djangoapps/credit/tests/test_api.py  |  6 +-
 .../djangoapps/credit/tests/test_models.py    |  2 +-
 .../djangoapps/credit/tests/test_partition.py |  2 +-
 .../credit/tests/test_serializers.py          |  4 +-
 .../djangoapps/credit/tests/test_services.py  |  2 +-
 .../djangoapps/credit/tests/test_signals.py   |  2 +-
 .../djangoapps/credit/tests/test_signature.py |  2 +-
 .../djangoapps/credit/tests/test_tasks.py     |  2 +-
 .../credit/tests/test_verification_access.py  |  4 +-
 .../djangoapps/credit/tests/test_views.py     | 10 ++--
 .../models/tests/test_course_details.py       |  2 +-
 .../profile_images/tests/test_images.py       |  6 +-
 .../profile_images/tests/test_views.py        | 10 ++--
 .../djangoapps/programs/tests/test_models.py  |  2 +-
 .../djangoapps/programs/tests/test_signals.py |  2 +-
 .../djangoapps/programs/tests/test_utils.py   |  4 +-
 .../safe_sessions/tests/test_middleware.py    |  6 +-
 .../tests/test_safe_cookie_data.py            |  2 +-
 .../user_api/accounts/tests/test_api.py       |  6 +-
 .../accounts/tests/test_image_helpers.py      |  2 +-
 .../user_api/accounts/tests/test_views.py     |  4 +-
 .../user_api/course_tag/tests/test_api.py     |  2 +-
 .../tests/test_email_opt_in_list.py           |  2 +-
 .../user_api/preferences/tests/test_api.py    |  4 +-
 openedx/core/djangolib/tests/test_js_utils.py |  2 +-
 openedx/core/djangolib/tests/test_markup.py   |  2 +-
 .../core/lib/api/tests/test_authentication.py |  2 +-
 openedx/core/lib/api/tests/test_exceptions.py |  4 +-
 openedx/core/lib/api/tests/test_paginators.py |  4 +-
 openedx/core/lib/api/tests/test_parsers.py    |  2 +-
 .../core/lib/api/tests/test_permissions.py    |  6 +-
 .../tests/test_block_structure.py             |  4 +-
 .../lib/block_structure/tests/test_cache.py   |  2 +-
 .../lib/block_structure/tests/test_factory.py |  2 +-
 .../lib/block_structure/tests/test_manager.py |  2 +-
 .../tests/test_transformer_registry.py        |  2 +-
 .../tests/test_transformers.py                |  2 +-
 openedx/core/lib/gating/tests/test_api.py     |  2 +-
 openedx/core/lib/tests/test_course_tab_api.py |  2 +-
 openedx/core/lib/tests/test_course_tabs.py    |  6 +-
 openedx/core/lib/tests/test_courses.py        |  2 +-
 openedx/core/lib/tests/test_edx_api_utils.py  |  2 +-
 .../core/lib/tests/test_graph_traversals.py   |  2 +-
 openedx/core/lib/tests/test_token_utils.py    |  2 +-
 openedx/core/lib/tests/test_xblock_utils.py   |  2 +-
 .../test_crowdsource_hinter.py                |  2 +-
 .../xblock_integration/test_recommender.py    | 12 ++--
 scripts/generic-ci-tests.sh                   | 57 ++++---------------
 230 files changed, 605 insertions(+), 637 deletions(-)

diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py
index 67a84502f0c..be2e7c2382f 100644
--- a/common/djangoapps/course_modes/tests/test_views.py
+++ b/common/djangoapps/course_modes/tests/test_views.py
@@ -27,7 +27,7 @@ from util.testing import UrlResetMixin
 from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
diff --git a/common/djangoapps/embargo/tests/test_api.py b/common/djangoapps/embargo/tests/test_api.py
index fc4a5c77b48..108b3b95bf3 100644
--- a/common/djangoapps/embargo/tests/test_api.py
+++ b/common/djangoapps/embargo/tests/test_api.py
@@ -38,7 +38,7 @@ from mock import patch
 MODULESTORE_CONFIG = mixed_store_config(settings.COMMON_TEST_DATA_ROOT, {})
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @override_settings(MODULESTORE=MODULESTORE_CONFIG)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
diff --git a/common/djangoapps/embargo/tests/test_middleware.py b/common/djangoapps/embargo/tests/test_middleware.py
index 953989ff5b0..55cac586dfb 100644
--- a/common/djangoapps/embargo/tests/test_middleware.py
+++ b/common/djangoapps/embargo/tests/test_middleware.py
@@ -21,7 +21,7 @@ from embargo.models import RestrictedCourse, IPFilter
 from embargo.test_utils import restrict_course
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class EmbargoMiddlewareAccessTests(UrlResetMixin, ModuleStoreTestCase):
diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py
index 9a8add5fbf8..7409923bd47 100644
--- a/common/djangoapps/enrollment/tests/test_views.py
+++ b/common/djangoapps/enrollment/tests/test_views.py
@@ -126,7 +126,7 @@ class EnrollmentTestMixin(object):
         self.assertEqual(actual_mode, expected_mode)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @override_settings(EDX_API_KEY="i am a key")
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
diff --git a/common/djangoapps/external_auth/tests/test_shib.py b/common/djangoapps/external_auth/tests/test_shib.py
index 028766a1061..62615bb88cc 100644
--- a/common/djangoapps/external_auth/tests/test_shib.py
+++ b/common/djangoapps/external_auth/tests/test_shib.py
@@ -73,7 +73,7 @@ def gen_all_identities():
                     yield _build_identity_dict(mail, display_name, given_name, surname)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt
 @override_settings(SESSION_ENGINE='django.contrib.sessions.backends.cache')
 class ShibSPTest(CacheIsolationTestCase):
diff --git a/common/djangoapps/student/tests/test_enrollment.py b/common/djangoapps/student/tests/test_enrollment.py
index acabe344c39..ce96ad3532b 100644
--- a/common/djangoapps/student/tests/test_enrollment.py
+++ b/common/djangoapps/student/tests/test_enrollment.py
@@ -21,7 +21,7 @@ from student.roles import (
 )
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class EnrollmentTest(UrlResetMixin, SharedModuleStoreTestCase):
diff --git a/common/djangoapps/student/tests/test_recent_enrollments.py b/common/djangoapps/student/tests/test_recent_enrollments.py
index 4aa01451ab1..8dfd143cfed 100644
--- a/common/djangoapps/student/tests/test_recent_enrollments.py
+++ b/common/djangoapps/student/tests/test_recent_enrollments.py
@@ -20,7 +20,7 @@ from student.views import get_course_enrollments, _get_recently_enrolled_courses
 from common.test.utils import XssTestMixin
 
 
-@attr('shard_3')
+@attr(shard=3)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 @ddt.ddt
 class TestRecentEnrollments(ModuleStoreTestCase, XssTestMixin):
diff --git a/common/djangoapps/student/tests/test_verification_status.py b/common/djangoapps/student/tests/test_verification_status.py
index 447fccdd96c..342ad05b073 100644
--- a/common/djangoapps/student/tests/test_verification_status.py
+++ b/common/djangoapps/student/tests/test_verification_status.py
@@ -25,7 +25,7 @@ from lms.djangoapps.verify_student.models import VerificationDeadline, SoftwareS
 from util.testing import UrlResetMixin
 
 
-@attr('shard_3')
+@attr(shard=3)
 @patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 @ddt.ddt
diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py
index b2569025f57..4e0f996cebf 100644
--- a/common/djangoapps/student/tests/tests.py
+++ b/common/djangoapps/student/tests/tests.py
@@ -890,7 +890,7 @@ class AnonymousLookupTable(ModuleStoreTestCase):
 
 
 # TODO: Clean up these tests so that they use program factories and don't mention XSeries!
-@attr('shard_3')
+@attr(shard=3)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 @ddt.ddt
 class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin):
diff --git a/common/djangoapps/track/views/tests/test_segmentio.py b/common/djangoapps/track/views/tests/test_segmentio.py
index 004443eee9a..8b0bf980f3b 100644
--- a/common/djangoapps/track/views/tests/test_segmentio.py
+++ b/common/djangoapps/track/views/tests/test_segmentio.py
@@ -37,7 +37,7 @@ def expect_failure_with_message(message):
     return test_decorator
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt
 @override_settings(
     TRACKING_SEGMENTIO_WEBHOOK_SECRET=SECRET,
diff --git a/common/test/acceptance/tests/discussion/test_cohort_management.py b/common/test/acceptance/tests/discussion/test_cohort_management.py
index b0878d03a77..9b4ba2c2c4f 100644
--- a/common/test/acceptance/tests/discussion/test_cohort_management.py
+++ b/common/test/acceptance/tests/discussion/test_cohort_management.py
@@ -21,7 +21,7 @@ import unicodecsv
 import uuid
 
 
-@attr('shard_8')
+@attr(shard=8)
 class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin):
     """
     Tests for cohort management on the LMS Instructor Dashboard
@@ -690,7 +690,7 @@ class CohortConfigurationTest(EventsTestMixin, UniqueCourseTest, CohortTestMixin
         self.assertEquals(expected_message, messages[0])
 
 
-@attr('shard_6')
+@attr(shard=6)
 class CohortDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin):
     """
     Tests for cohorting the inline and course-wide discussion topics.
@@ -981,7 +981,7 @@ class CohortDiscussionTopicsTest(UniqueCourseTest, CohortTestMixin):
         self.verify_discussion_topics_after_reload(self.inline_key, cohorted_topics_after)
 
 
-@attr('shard_6')
+@attr(shard=6)
 class CohortContentGroupAssociationTest(UniqueCourseTest, CohortTestMixin):
     """
     Tests for linking between content groups and cohort in the instructor dashboard.
diff --git a/common/test/acceptance/tests/discussion/test_cohorts.py b/common/test/acceptance/tests/discussion/test_cohorts.py
index b578101408a..dbdbac82370 100644
--- a/common/test/acceptance/tests/discussion/test_cohorts.py
+++ b/common/test/acceptance/tests/discussion/test_cohorts.py
@@ -79,7 +79,7 @@ class DiscussionTabSingleThreadTest(BaseDiscussionTestCase):
         self.thread_page.wait_for_page()
 
 
-@attr('shard_5')
+@attr(shard=5)
 class CohortedDiscussionTabSingleThreadTest(DiscussionTabSingleThreadTest, CohortedDiscussionTestMixin):
     """
     Tests for the discussion page displaying a single cohorted thread.
@@ -88,7 +88,7 @@ class CohortedDiscussionTabSingleThreadTest(DiscussionTabSingleThreadTest, Cohor
     pass
 
 
-@attr('shard_5')
+@attr(shard=5)
 class NonCohortedDiscussionTabSingleThreadTest(DiscussionTabSingleThreadTest, NonCohortedDiscussionTestMixin):
     """
     Tests for the discussion page displaying a single non-cohorted thread.
@@ -137,7 +137,7 @@ class InlineDiscussionTest(UniqueCourseTest):
         self.show_thread(thread_id)
 
 
-@attr('shard_5')
+@attr(shard=5)
 class CohortedInlineDiscussionTest(InlineDiscussionTest, CohortedDiscussionTestMixin):
     """
     Tests for cohorted inline discussions.
@@ -146,7 +146,7 @@ class CohortedInlineDiscussionTest(InlineDiscussionTest, CohortedDiscussionTestM
     pass
 
 
-@attr('shard_5')
+@attr(shard=5)
 class NonCohortedInlineDiscussionTest(InlineDiscussionTest, NonCohortedDiscussionTestMixin):
     """
     Tests for non-cohorted inline discussions.
diff --git a/common/test/acceptance/tests/discussion/test_discussion.py b/common/test/acceptance/tests/discussion/test_discussion.py
index 28f4f2d0f19..84dc9111d7c 100644
--- a/common/test/acceptance/tests/discussion/test_discussion.py
+++ b/common/test/acceptance/tests/discussion/test_discussion.py
@@ -178,7 +178,7 @@ class DiscussionResponsePaginationTestMixin(BaseDiscussionMixin):
         self.assertFalse(self.thread_page.has_add_response_button())
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionHomePageTest(UniqueCourseTest):
     """
     Tests for the discussion home page.
@@ -217,7 +217,7 @@ class DiscussionHomePageTest(UniqueCourseTest):
         self.page.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionTabSingleThreadTest(BaseDiscussionTestCase, DiscussionResponsePaginationTestMixin):
     """
     Tests for the discussion page displaying a single thread
@@ -290,7 +290,7 @@ class DiscussionTabSingleThreadTest(BaseDiscussionTestCase, DiscussionResponsePa
         self.assertFalse(self.thread_page.is_show_comments_visible(response_id))
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionTabMultipleThreadTest(BaseDiscussionTestCase):
     """
     Tests for the discussion page with multiple threads
@@ -371,7 +371,7 @@ class DiscussionTabMultipleThreadTest(BaseDiscussionTestCase):
         self.thread_page_2.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionOpenClosedThreadTest(BaseDiscussionTestCase):
     """
     Tests for checking the display of attributes on open and closed threads
@@ -446,7 +446,7 @@ class DiscussionOpenClosedThreadTest(BaseDiscussionTestCase):
         page.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionCommentDeletionTest(BaseDiscussionTestCase):
     """
     Tests for deleting comments displayed beneath responses in the single thread view.
@@ -486,7 +486,7 @@ class DiscussionCommentDeletionTest(BaseDiscussionTestCase):
         page.delete_comment("comment_other_author")
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionResponseEditTest(BaseDiscussionTestCase):
     """
     Tests for editing responses displayed beneath thread in the single thread view.
@@ -737,7 +737,7 @@ class DiscussionResponseEditTest(BaseDiscussionTestCase):
         page.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionCommentEditTest(BaseDiscussionTestCase):
     """
     Tests for editing comments displayed beneath responses in the single thread view.
@@ -837,7 +837,7 @@ class DiscussionCommentEditTest(BaseDiscussionTestCase):
         page.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_2')
+@attr(shard=2)
 class InlineDiscussionTest(UniqueCourseTest, DiscussionResponsePaginationTestMixin):
     """
     Tests for inline discussions
@@ -996,7 +996,7 @@ class InlineDiscussionTest(UniqueCourseTest, DiscussionResponsePaginationTestMix
         self.assertFalse(self.additional_discussion_page._is_element_visible(".new-post-article"))
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionUserProfileTest(UniqueCourseTest):
     """
     Tests for user profile page in discussion tab.
@@ -1125,7 +1125,7 @@ class DiscussionUserProfileTest(UniqueCourseTest):
         self.assertTrue(learner_profile_page.field_is_visible('username'))
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionSearchAlertTest(UniqueCourseTest):
     """
     Tests for spawning and dismissing alerts related to user search actions and their results.
@@ -1211,7 +1211,7 @@ class DiscussionSearchAlertTest(UniqueCourseTest):
         self.page.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_2')
+@attr(shard=2)
 class DiscussionSortPreferenceTest(UniqueCourseTest):
     """
     Tests for the discussion page displaying a single thread.
diff --git a/common/test/acceptance/tests/lms/test_account_settings.py b/common/test/acceptance/tests/lms/test_account_settings.py
index 9ffadd658cc..d83ae64663e 100644
--- a/common/test/acceptance/tests/lms/test_account_settings.py
+++ b/common/test/acceptance/tests/lms/test_account_settings.py
@@ -90,7 +90,7 @@ class AccountSettingsTestMixin(EventsTestMixin, WebAppTest):
         self.assert_no_matching_events_were_emitted({'event_type': self.USER_SETTINGS_CHANGED_EVENT_NAME})
 
 
-@attr('shard_8')
+@attr(shard=8)
 class DashboardMenuTest(AccountSettingsTestMixin, WebAppTest):
     """
     Tests that the dashboard menu works correctly with the account settings page.
@@ -113,7 +113,7 @@ class DashboardMenuTest(AccountSettingsTestMixin, WebAppTest):
         dashboard_page.click_account_settings_link()
 
 
-@attr('shard_8')
+@attr(shard=8)
 class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
     """
     Tests that verify behaviour of the Account Settings page.
diff --git a/common/test/acceptance/tests/lms/test_bookmarks.py b/common/test/acceptance/tests/lms/test_bookmarks.py
index ef36dddd97f..b773fff3d93 100644
--- a/common/test/acceptance/tests/lms/test_bookmarks.py
+++ b/common/test/acceptance/tests/lms/test_bookmarks.py
@@ -60,7 +60,7 @@ class BookmarksTestMixin(EventsTestMixin, UniqueCourseTest):
         self.assert_events_match(event_data, actual_events)
 
 
-@attr('shard_8')
+@attr(shard=8)
 class BookmarksTest(BookmarksTestMixin):
     """
     Tests to verify bookmarks functionality.
diff --git a/common/test/acceptance/tests/lms/test_ccx.py b/common/test/acceptance/tests/lms/test_ccx.py
index 7cc78cf3ec9..1a284f36cfa 100644
--- a/common/test/acceptance/tests/lms/test_ccx.py
+++ b/common/test/acceptance/tests/lms/test_ccx.py
@@ -10,7 +10,7 @@ from common.test.acceptance.pages.lms.auto_auth import AutoAuthPage
 from common.test.acceptance.pages.lms.ccx_dashboard_page import CoachDashboardPage
 
 
-@attr('shard_7')
+@attr(shard=7)
 class CreateCCXCoachTest(EventsTestMixin, UniqueCourseTest):
     """
     Test ccx end to end process.
diff --git a/common/test/acceptance/tests/lms/test_certificate_web_view.py b/common/test/acceptance/tests/lms/test_certificate_web_view.py
index deb07e623f4..1b928dd47ee 100644
--- a/common/test/acceptance/tests/lms/test_certificate_web_view.py
+++ b/common/test/acceptance/tests/lms/test_certificate_web_view.py
@@ -13,7 +13,7 @@ from common.test.acceptance.pages.lms.course_nav import CourseNavPage
 from common.test.acceptance.pages.lms.progress import ProgressPage
 
 
-@attr('shard_5')
+@attr(shard=5)
 class CertificateWebViewTest(EventsTestMixin, UniqueCourseTest):
     """
     Tests for verifying certificate web view features
@@ -94,7 +94,7 @@ class CertificateWebViewTest(EventsTestMixin, UniqueCourseTest):
         self.assert_events_match(expected_events, actual_events)
 
 
-@attr('shard_5')
+@attr(shard=5)
 class CertificateProgressPageTest(UniqueCourseTest):
     """
     Tests for verifying Certificate info on Progress tab of course page.
diff --git a/common/test/acceptance/tests/lms/test_learner_profile.py b/common/test/acceptance/tests/lms/test_learner_profile.py
index 7b18ced3c4c..a684b9d57eb 100644
--- a/common/test/acceptance/tests/lms/test_learner_profile.py
+++ b/common/test/acceptance/tests/lms/test_learner_profile.py
@@ -180,7 +180,7 @@ class LearnerProfileTestMixin(EventsTestMixin):
         return username, user_id
 
 
-@attr('shard_4')
+@attr(shard=4)
 class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
     """
     Tests that verify a student's own profile page.
@@ -695,7 +695,7 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
             profile_page.upload_file(filename='image.jpg', wait_for_upload_button=False)
 
 
-@attr('shard_4')
+@attr(shard=4)
 class DifferentUserLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
     """
     Tests that verify viewing the profile page of a different user.
diff --git a/common/test/acceptance/tests/lms/test_library.py b/common/test/acceptance/tests/lms/test_library.py
index 7904a21fcab..a42bf211c23 100644
--- a/common/test/acceptance/tests/lms/test_library.py
+++ b/common/test/acceptance/tests/lms/test_library.py
@@ -21,7 +21,7 @@ SUBSECTION_NAME = 'Test Subsection'
 UNIT_NAME = 'Test Unit'
 
 
-@attr('shard_7')
+@attr(shard=7)
 class LibraryContentTestBase(UniqueCourseTest):
     """ Base class for library content block tests """
     USERNAME = "STUDENT_TESTER"
@@ -144,7 +144,7 @@ class LibraryContentTestBase(UniqueCourseTest):
 
 
 @ddt.ddt
-@attr('shard_7')
+@attr(shard=7)
 class LibraryContentTest(LibraryContentTestBase):
     """
     Test courseware.
@@ -197,7 +197,7 @@ class LibraryContentTest(LibraryContentTestBase):
 
 
 @ddt.ddt
-@attr('shard_7')
+@attr(shard=7)
 class StudioLibraryContainerCapaFilterTest(LibraryContentTestBase, TestWithSearchIndexMixin):
     """
     Test Library Content block in LMS
diff --git a/common/test/acceptance/tests/lms/test_lms.py b/common/test/acceptance/tests/lms/test_lms.py
index 28e0cc5347a..53fe85aa974 100644
--- a/common/test/acceptance/tests/lms/test_lms.py
+++ b/common/test/acceptance/tests/lms/test_lms.py
@@ -42,7 +42,7 @@ from common.test.acceptance.pages.lms.course_wiki import CourseWikiPage, CourseW
 from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc, CourseUpdateDesc
 
 
-@attr('shard_8')
+@attr(shard=8)
 class ForgotPasswordPageTest(UniqueCourseTest):
     """
     Test that forgot password forms is rendered if url contains 'forgot-password-modal'
@@ -84,7 +84,7 @@ class ForgotPasswordPageTest(UniqueCourseTest):
         self.assertIn("Password Reset Email Sent", self.reset_password_page.get_success_message())
 
 
-@attr('shard_8')
+@attr(shard=8)
 class LoginFromCombinedPageTest(UniqueCourseTest):
     """Test that we can log in using the combined login/registration page.
 
@@ -275,7 +275,7 @@ class LoginFromCombinedPageTest(UniqueCourseTest):
         return (email, password)
 
 
-@attr('shard_8')
+@attr(shard=8)
 class RegisterFromCombinedPageTest(UniqueCourseTest):
     """Test that we can register a new user from the combined login/registration page. """
 
@@ -398,7 +398,7 @@ class RegisterFromCombinedPageTest(UniqueCourseTest):
         account_settings.wait_for_message(field_id, "Successfully unlinked")
 
 
-@attr('shard_8')
+@attr(shard=8)
 class PayAndVerifyTest(EventsTestMixin, UniqueCourseTest):
     """Test that we can proceed through the payment and verification flow."""
     def setUp(self):
@@ -537,7 +537,7 @@ class PayAndVerifyTest(EventsTestMixin, UniqueCourseTest):
         self.assertEqual(enrollment_mode, 'verified')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseWikiTest(UniqueCourseTest):
     """
     Tests that verify the course wiki.
@@ -591,7 +591,7 @@ class CourseWikiTest(UniqueCourseTest):
         self.assertEqual(content, actual_content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class HighLevelTabTest(UniqueCourseTest):
     """
     Tests that verify each of the high-level tabs available within a course.
@@ -748,7 +748,7 @@ class HighLevelTabTest(UniqueCourseTest):
             self.assertIn(expected, actual_items)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class PDFTextBooksTabTest(UniqueCourseTest):
     """
     Tests that verify each of the textbook tabs available within a course.
@@ -789,7 +789,7 @@ class PDFTextBooksTabTest(UniqueCourseTest):
             self.tab_nav.go_to_tab("PDF Book {}".format(i))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class VisibleToStaffOnlyTest(UniqueCourseTest):
     """
     Tests that content with visible_to_staff_only set to True cannot be viewed by students.
@@ -874,7 +874,7 @@ class VisibleToStaffOnlyTest(UniqueCourseTest):
         self.assertEqual([u'Test Unit'], self.course_nav.sequence_items)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TooltipTest(UniqueCourseTest):
     """
     Tests that tooltips are displayed
@@ -919,7 +919,7 @@ class TooltipTest(UniqueCourseTest):
         self.courseware_page.verify_tooltips_displayed()
 
 
-@attr('shard_1')
+@attr(shard=1)
 class PreRequisiteCourseTest(UniqueCourseTest):
     """
     Tests that pre-requisite course messages are displayed
@@ -1004,7 +1004,7 @@ class PreRequisiteCourseTest(UniqueCourseTest):
         self.settings_page.save_changes()
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ProblemExecutionTest(UniqueCourseTest):
     """
     Tests of problems.
@@ -1083,7 +1083,7 @@ class ProblemExecutionTest(UniqueCourseTest):
         self.assertFalse(problem_page.is_correct())
 
 
-@attr('shard_1')
+@attr(shard=1)
 class EntranceExamTest(UniqueCourseTest):
     """
     Tests that course has an entrance exam.
@@ -1154,7 +1154,7 @@ class EntranceExamTest(UniqueCourseTest):
         ))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class NotLiveRedirectTest(UniqueCourseTest):
     """
     Test that a banner is shown when the user is redirected to
@@ -1186,7 +1186,7 @@ class NotLiveRedirectTest(UniqueCourseTest):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class EnrollmentClosedRedirectTest(UniqueCourseTest):
     """
     Test that a banner is shown when the user is redirected to the
@@ -1275,7 +1275,7 @@ class EnrollmentClosedRedirectTest(UniqueCourseTest):
         self._assert_dashboard_message()
 
 
-@attr('shard_1')
+@attr(shard=1)
 class LMSLanguageTest(UniqueCourseTest):
     """ Test suite for the LMS Language """
     def setUp(self):
diff --git a/common/test/acceptance/tests/lms/test_lms_cohorted_courseware_search.py b/common/test/acceptance/tests/lms/test_lms_cohorted_courseware_search.py
index f4c27c44fef..5ae5c723ad2 100644
--- a/common/test/acceptance/tests/lms/test_lms_cohorted_courseware_search.py
+++ b/common/test/acceptance/tests/lms/test_lms_cohorted_courseware_search.py
@@ -25,7 +25,7 @@ from common.test.acceptance.pages.lms.instructor_dashboard import InstructorDash
 from bok_choy.promise import EmptyPromise
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CoursewareSearchCohortTest(ContainerBase):
     """
     Test courseware search.
diff --git a/common/test/acceptance/tests/lms/test_lms_courseware_search.py b/common/test/acceptance/tests/lms/test_lms_courseware_search.py
index e566ed44faf..c07479cf9ab 100644
--- a/common/test/acceptance/tests/lms/test_lms_courseware_search.py
+++ b/common/test/acceptance/tests/lms/test_lms_courseware_search.py
@@ -16,7 +16,7 @@ from common.test.acceptance.pages.lms.courseware_search import CoursewareSearchP
 from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
 
 
-@attr('shard_5')
+@attr(shard=5)
 class CoursewareSearchTest(UniqueCourseTest):
     """
     Test courseware search.
diff --git a/common/test/acceptance/tests/lms/test_lms_edxnotes.py b/common/test/acceptance/tests/lms/test_lms_edxnotes.py
index 03eb10862e9..cf0e050532a 100644
--- a/common/test/acceptance/tests/lms/test_lms_edxnotes.py
+++ b/common/test/acceptance/tests/lms/test_lms_edxnotes.py
@@ -122,7 +122,7 @@ class EdxNotesTestMixin(UniqueCourseTest):
         self.edxnotes_fixture.install()
 
 
-@attr('shard_4')
+@attr(shard=4)
 class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
     """
     Tests for creation, editing, deleting annotations inside annotatable components in LMS.
@@ -338,7 +338,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
             self.assertTrue(note.has_sr_label(1, 3, "Tags (space-separated)"))
 
 
-@attr('shard_4')
+@attr(shard=4)
 class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
     """
     Tests for Notes page.
@@ -1410,7 +1410,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
         )
 
 
-@attr('shard_4')
+@attr(shard=4)
 class EdxNotesToggleSingleNoteTest(EdxNotesTestMixin):
     """
     Tests for toggling single annotation.
@@ -1479,7 +1479,7 @@ class EdxNotesToggleSingleNoteTest(EdxNotesTestMixin):
         self.assertTrue(note_2.is_visible)
 
 
-@attr('shard_4')
+@attr(shard=4)
 class EdxNotesToggleNotesTest(EdxNotesTestMixin):
     """
     Tests for toggling visibility of all notes.
diff --git a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py
index 77b9840c12d..cc8fbc1fc4f 100644
--- a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py
+++ b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py
@@ -100,7 +100,7 @@ class BulkEmailTest(BaseInstructorDashboardTest):
         self.send_email_page.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_7')
+@attr(shard=7)
 class AutoEnrollmentWithCSVTest(BaseInstructorDashboardTest):
     """
     End-to-end tests for Auto-Registration and enrollment functionality via CSV file.
@@ -218,7 +218,7 @@ class AutoEnrollmentWithCSVTest(BaseInstructorDashboardTest):
         self.auto_enroll_section.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_7')
+@attr(shard=7)
 class ProctoredExamsTest(BaseInstructorDashboardTest):
     """
     End-to-end tests for Proctoring Sections of the Instructor Dashboard.
@@ -407,7 +407,7 @@ class ProctoredExamsTest(BaseInstructorDashboardTest):
         self.assertFalse(exam_attempts_section.is_student_attempt_visible)
 
 
-@attr('shard_7')
+@attr(shard=7)
 class EntranceExamGradeTest(BaseInstructorDashboardTest):
     """
     Tests for Entrance exam specific student grading tasks.
@@ -606,7 +606,7 @@ class EntranceExamGradeTest(BaseInstructorDashboardTest):
         self.assertTrue(self.student_admin_section.is_background_task_history_table_visible())
 
 
-@attr('shard_7')
+@attr(shard=7)
 class DataDownloadsTest(BaseInstructorDashboardTest):
     """
     Bok Choy tests for the "Data Downloads" tab.
@@ -724,7 +724,7 @@ class DataDownloadsTest(BaseInstructorDashboardTest):
         self.data_download_section.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_7')
+@attr(shard=7)
 @ddt.ddt
 class CertificatesTest(BaseInstructorDashboardTest):
     """
@@ -1057,7 +1057,7 @@ class CertificatesTest(BaseInstructorDashboardTest):
         self.certificates_section.a11y_audit.check_for_accessibility_errors()
 
 
-@attr('shard_7')
+@attr(shard=7)
 class CertificateInvalidationTest(BaseInstructorDashboardTest):
     """
     Tests for Certificates functionality on instructor dashboard.
diff --git a/common/test/acceptance/tests/lms/test_lms_split_test_courseware_search.py b/common/test/acceptance/tests/lms/test_lms_split_test_courseware_search.py
index a9d5782a080..77c133f1bc7 100644
--- a/common/test/acceptance/tests/lms/test_lms_split_test_courseware_search.py
+++ b/common/test/acceptance/tests/lms/test_lms_split_test_courseware_search.py
@@ -21,7 +21,7 @@ from common.test.acceptance.tests.studio.base_studio_test import ContainerBase
 from common.test.acceptance.pages.studio.auto_auth import AutoAuthPage as StudioAutoAuthPage
 
 
-@attr('shard_1')
+@attr(shard=1)
 class SplitTestCoursewareSearchTest(ContainerBase):
     """
     Test courseware search on Split Test Module.
diff --git a/common/test/acceptance/tests/lms/test_lms_user_preview.py b/common/test/acceptance/tests/lms/test_lms_user_preview.py
index 9774c1512d2..ac02b2705d9 100644
--- a/common/test/acceptance/tests/lms/test_lms_user_preview.py
+++ b/common/test/acceptance/tests/lms/test_lms_user_preview.py
@@ -17,7 +17,7 @@ from xmodule.partitions.partitions import Group
 from textwrap import dedent
 
 
-@attr('shard_3')
+@attr(shard=3)
 class StaffViewTest(UniqueCourseTest):
     """
     Tests that verify the staff view.
@@ -55,7 +55,7 @@ class StaffViewTest(UniqueCourseTest):
         return staff_page
 
 
-@attr('shard_3')
+@attr(shard=3)
 class CourseWithoutContentGroupsTest(StaffViewTest):
     """
     Setup for tests that have no content restricted to specific content groups.
@@ -86,7 +86,7 @@ class CourseWithoutContentGroupsTest(StaffViewTest):
         )
 
 
-@attr('shard_3')
+@attr(shard=3)
 class StaffViewToggleTest(CourseWithoutContentGroupsTest):
     """
     Tests for the staff view toggle button.
@@ -103,7 +103,7 @@ class StaffViewToggleTest(CourseWithoutContentGroupsTest):
         self.assertFalse(course_page.has_tab('Instructor'))
 
 
-@attr('shard_3')
+@attr(shard=3)
 class StaffDebugTest(CourseWithoutContentGroupsTest):
     """
     Tests that verify the staff debug info.
@@ -235,7 +235,7 @@ class StaffDebugTest(CourseWithoutContentGroupsTest):
                          'for user {}'.format(self.USERNAME), msg)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class CourseWithContentGroupsTest(StaffViewTest):
     """
     Verifies that changing the "View this course as" selector works properly for content groups.
diff --git a/common/test/acceptance/tests/lms/test_problem_types.py b/common/test/acceptance/tests/lms/test_problem_types.py
index 0654d3e5d69..6370fd48f83 100644
--- a/common/test/acceptance/tests/lms/test_problem_types.py
+++ b/common/test/acceptance/tests/lms/test_problem_types.py
@@ -138,7 +138,7 @@ class ProblemTypeTestMixin(object):
     """
     can_submit_blank = False
 
-    @attr('shard_7')
+    @attr(shard=7)
     def test_answer_correctly(self):
         """
         Scenario: I can answer a problem correctly
@@ -174,7 +174,7 @@ class ProblemTypeTestMixin(object):
         for event in expected_events:
             self.wait_for_events(event_filter=event, number_of_matches=1)
 
-    @attr('shard_7')
+    @attr(shard=7)
     def test_answer_incorrectly(self):
         """
         Scenario: I can answer a problem incorrectly
@@ -194,7 +194,7 @@ class ProblemTypeTestMixin(object):
         self.problem_page.click_check()
         self.wait_for_status('incorrect')
 
-    @attr('shard_7')
+    @attr(shard=7)
     def test_submit_blank_answer(self):
         """
         Scenario: I can submit a blank answer
@@ -215,7 +215,7 @@ class ProblemTypeTestMixin(object):
         self.problem_page.click_check()
         self.wait_for_status('incorrect')
 
-    @attr('shard_7')
+    @attr(shard=7)
     def test_cant_submit_blank_answer(self):
         """
         Scenario: I can't submit a blank answer
diff --git a/common/test/acceptance/tests/lms/test_teams.py b/common/test/acceptance/tests/lms/test_teams.py
index 5763bb30e44..fec3e5a7f13 100644
--- a/common/test/acceptance/tests/lms/test_teams.py
+++ b/common/test/acceptance/tests/lms/test_teams.py
@@ -157,7 +157,7 @@ class TeamsTabBase(EventsTestMixin, UniqueCourseTest):
 
 
 @ddt.ddt
-@attr('shard_5')
+@attr(shard=5)
 class TeamsTabTest(TeamsTabBase):
     """
     Tests verifying when the Teams tab is present.
@@ -303,7 +303,7 @@ class TeamsTabTest(TeamsTabBase):
         self.assertTrue(self.teams_page.q(css=selector).visible)
 
 
-@attr('shard_5')
+@attr(shard=5)
 class MyTeamsTest(TeamsTabBase):
     """
     Tests for the "My Teams" tab of the Teams page.
@@ -367,7 +367,7 @@ class MyTeamsTest(TeamsTabBase):
         self.assertEqual(self.my_teams_page.team_memberships[0], '4 / 10 Members')
 
 
-@attr('shard_5')
+@attr(shard=5)
 @ddt.ddt
 class BrowseTopicsTest(TeamsTabBase):
     """
@@ -602,7 +602,7 @@ class BrowseTopicsTest(TeamsTabBase):
             self.topics_page.visit()
 
 
-@attr('shard_5')
+@attr(shard=5)
 @ddt.ddt
 class BrowseTeamsWithinTopicTest(TeamsTabBase):
     """
@@ -906,7 +906,7 @@ class BrowseTeamsWithinTopicTest(TeamsTabBase):
             alert.accept()
 
 
-@attr('shard_5')
+@attr(shard=5)
 class TeamFormActions(TeamsTabBase):
     """
     Base class for create, edit, and delete team.
@@ -1619,7 +1619,7 @@ class EditMembershipTest(TeamFormActions):
         self.edit_membership_helper(role, cancel=True)
 
 
-@attr('shard_5')
+@attr(shard=5)
 @ddt.ddt
 class TeamPageTest(TeamsTabBase):
     """Tests for viewing a specific team"""
diff --git a/common/test/acceptance/tests/studio/test_import_export.py b/common/test/acceptance/tests/studio/test_import_export.py
index bc3f7fc2158..40987b90ec2 100644
--- a/common/test/acceptance/tests/studio/test_import_export.py
+++ b/common/test/acceptance/tests/studio/test_import_export.py
@@ -40,7 +40,7 @@ class ExportTestMixin(object):
         self.assertTrue(is_tarball_mimetype)
 
 
-@attr('shard_7')
+@attr(shard=7)
 class TestCourseExport(ExportTestMixin, StudioCourseTest):
     """
     Export tests for courses.
@@ -63,7 +63,7 @@ class TestCourseExport(ExportTestMixin, StudioCourseTest):
         self.assertEqual(self.export_page.header_text, 'Course Export')
 
 
-@attr('shard_7')
+@attr(shard=7)
 class TestLibraryExport(ExportTestMixin, StudioLibraryTest):
     """
     Export tests for libraries.
@@ -112,7 +112,7 @@ class BadExportMixin(object):
         )
 
 
-@attr('shard_7')
+@attr(shard=7)
 class TestLibraryBadExport(BadExportMixin, StudioLibraryTest):
     """
     Verify exporting a bad library causes an error.
@@ -136,7 +136,7 @@ class TestLibraryBadExport(BadExportMixin, StudioLibraryTest):
         )
 
 
-@attr('shard_7')
+@attr(shard=7)
 class TestCourseBadExport(BadExportMixin, StudioCourseTest):
     """
     Verify exporting a bad course causes an error.
@@ -168,7 +168,7 @@ class TestCourseBadExport(BadExportMixin, StudioCourseTest):
         )
 
 
-@attr('shard_7')
+@attr(shard=7)
 class ImportTestMixin(object):
     """
     Tests to run for both course and library import pages.
@@ -283,7 +283,7 @@ class ImportTestMixin(object):
         self.import_page.wait_for_tasks(fail_on='Updating')
 
 
-@attr('shard_7')
+@attr(shard=7)
 class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest):
     """
     Tests the Course import page
@@ -329,7 +329,7 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest):
         )
 
 
-@attr('shard_7')
+@attr(shard=7)
 class TestCourseImport(ImportTestMixin, StudioCourseTest):
     """
     Tests the Course import page
@@ -399,7 +399,7 @@ class TestCourseImport(ImportTestMixin, StudioCourseTest):
         self.assertFalse(self.import_page.is_timestamp_visible())
 
 
-@attr('shard_7')
+@attr(shard=7)
 class TestLibraryImport(ImportTestMixin, StudioLibraryTest):
     """
     Tests the Library import page
diff --git a/common/test/acceptance/tests/studio/test_studio_container.py b/common/test/acceptance/tests/studio/test_studio_container.py
index 05c9174bba3..231732ba106 100644
--- a/common/test/acceptance/tests/studio/test_studio_container.py
+++ b/common/test/acceptance/tests/studio/test_studio_container.py
@@ -72,7 +72,7 @@ class NestedVerticalTest(ContainerBase):
 
 
 @skip("Flaky: 01/16/2015")
-@attr('shard_1')
+@attr(shard=1)
 class DragAndDropTest(NestedVerticalTest):
     """
     Tests of reordering within the container page.
@@ -152,7 +152,7 @@ class DragAndDropTest(NestedVerticalTest):
         self.do_action_and_verify(add_new_components_and_rearrange, expected_ordering)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AddComponentTest(NestedVerticalTest):
     """
     Tests of adding a component to the container page.
@@ -192,7 +192,7 @@ class AddComponentTest(NestedVerticalTest):
         self.add_and_verify(container_menu, expected_ordering)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class DuplicateComponentTest(NestedVerticalTest):
     """
     Tests of duplicating a component on the container page.
@@ -238,7 +238,7 @@ class DuplicateComponentTest(NestedVerticalTest):
         self.do_action_and_verify(duplicate_twice, expected_ordering)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class DeleteComponentTest(NestedVerticalTest):
     """
     Tests of deleting a component from the container page.
@@ -261,7 +261,7 @@ class DeleteComponentTest(NestedVerticalTest):
         self.delete_and_verify(group_a_item_1_delete_index, expected_ordering)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class EditContainerTest(NestedVerticalTest):
     """
     Tests of editing a container.
@@ -312,7 +312,7 @@ class EditContainerTest(NestedVerticalTest):
         self.assertEqual(component.student_content, "modified content")
 
 
-@attr('shard_3')
+@attr(shard=3)
 class EditVisibilityModalTest(ContainerBase):
     """
     Tests of the visibility settings modal for components on the unit
@@ -572,7 +572,7 @@ class EditVisibilityModalTest(ContainerBase):
         self.verify_visibility_set(self.html_component, True)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class UnitPublishingTest(ContainerBase):
     """
     Tests of the publishing control and related widgets on the Unit page.
@@ -1042,7 +1042,7 @@ class UnitPublishingTest(ContainerBase):
     #     self.assertEqual('discussion', self.courseware.xblock_component_type(1))
 
 
-@attr('shard_3')
+@attr(shard=3)
 class DisplayNameTest(ContainerBase):
     """
     Test consistent use of display_name_with_default
@@ -1079,7 +1079,7 @@ class DisplayNameTest(ContainerBase):
         self.assertEqual(container.name, title_on_unit_page)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class ProblemCategoryTabsTest(ContainerBase):
     """
     Test to verify tabs in problem category.
diff --git a/common/test/acceptance/tests/studio/test_studio_course_create.py b/common/test/acceptance/tests/studio/test_studio_course_create.py
index b8441dab8b8..a45f9b2fdd3 100644
--- a/common/test/acceptance/tests/studio/test_studio_course_create.py
+++ b/common/test/acceptance/tests/studio/test_studio_course_create.py
@@ -10,7 +10,7 @@ from common.test.acceptance.pages.studio.index import DashboardPage
 from common.test.acceptance.pages.studio.overview import CourseOutlinePage
 
 
-@attr('shard_8')
+@attr(shard=8)
 class CreateCourseTest(WebAppTest):
     """
     Test that we can create a new course the studio home page.
diff --git a/common/test/acceptance/tests/studio/test_studio_course_team.py b/common/test/acceptance/tests/studio/test_studio_course_team.py
index 9a76bdf8c84..8079747124d 100644
--- a/common/test/acceptance/tests/studio/test_studio_course_team.py
+++ b/common/test/acceptance/tests/studio/test_studio_course_team.py
@@ -10,7 +10,7 @@ from common.test.acceptance.pages.studio.users import CourseTeamPage
 from common.test.acceptance.pages.studio.index import DashboardPage
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CourseTeamPageTest(StudioCourseTest):
     """ As a course author, I want to be able to add others to my team """
     def _make_user(self, username):
diff --git a/common/test/acceptance/tests/studio/test_studio_library.py b/common/test/acceptance/tests/studio/test_studio_library.py
index c6049c178db..ee9412f312b 100644
--- a/common/test/acceptance/tests/studio/test_studio_library.py
+++ b/common/test/acceptance/tests/studio/test_studio_library.py
@@ -13,7 +13,7 @@ from common.test.acceptance.pages.studio.library import LibraryEditPage
 from common.test.acceptance.pages.studio.users import LibraryUsersPage
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt
 class LibraryEditPageTest(StudioLibraryTest):
     """
@@ -186,7 +186,7 @@ class LibraryEditPageTest(StudioLibraryTest):
         self.assertIn("Checkboxes", problem_block.name)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt
 class LibraryNavigationTest(StudioLibraryTest):
     """
diff --git a/common/test/acceptance/tests/studio/test_studio_library_container.py b/common/test/acceptance/tests/studio/test_studio_library_container.py
index 27aa0ef850b..1d7bf1831a1 100644
--- a/common/test/acceptance/tests/studio/test_studio_library_container.py
+++ b/common/test/acceptance/tests/studio/test_studio_library_container.py
@@ -17,7 +17,7 @@ SUBSECTION_NAME = 'Test Subsection'
 UNIT_NAME = 'Test Unit'
 
 
-@attr('shard_5')
+@attr(shard=5)
 @ddt.ddt
 class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest, TestWithSearchIndexMixin):
     """
diff --git a/common/test/acceptance/tests/studio/test_studio_outline.py b/common/test/acceptance/tests/studio/test_studio_outline.py
index 934311eecea..6a18e3c4643 100644
--- a/common/test/acceptance/tests/studio/test_studio_outline.py
+++ b/common/test/acceptance/tests/studio/test_studio_outline.py
@@ -74,7 +74,7 @@ class CourseOutlineTest(StudioCourseTest):
         verify_ordering(self, outline_page, expected_ordering)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class CourseOutlineDragAndDropTest(CourseOutlineTest):
     """
     Tests of drag and drop within the outline page.
@@ -129,7 +129,7 @@ class CourseOutlineDragAndDropTest(CourseOutlineTest):
         self.drag_and_verify(self.seq_1_vert_2_handle, self.chap_1_seq_2_handle, expected_ordering, course_outline_page)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class WarningMessagesTest(CourseOutlineTest):
     """
     Feature: Warning messages on sections, subsections, and units
@@ -337,7 +337,7 @@ class WarningMessagesTest(CourseOutlineTest):
             unit.toggle_staff_lock()
 
 
-@attr('shard_3')
+@attr(shard=3)
 class EditingSectionsTest(CourseOutlineTest):
     """
     Feature: Editing Release date, Due date and grading type.
@@ -493,7 +493,7 @@ class EditingSectionsTest(CourseOutlineTest):
         self.assertIn(release_text, self.course_outline_page.section_at(0).subsection_at(0).release_date)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class StaffLockTest(CourseOutlineTest):
     """
     Feature: Sections, subsections, and units can be locked and unlocked from the course outline.
@@ -875,7 +875,7 @@ class StaffLockTest(CourseOutlineTest):
         self._remove_staff_lock_and_verify_warning(subsection, False)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class EditNamesTest(CourseOutlineTest):
     """
     Feature: Click-to-edit section/subsection names
@@ -991,7 +991,7 @@ class EditNamesTest(CourseOutlineTest):
         self.assertTrue(self.course_outline_page.section_at(0).is_collapsed)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class CreateSectionsTest(CourseOutlineTest):
     """
     Feature: Create new sections/subsections/units
@@ -1078,7 +1078,7 @@ class CreateSectionsTest(CourseOutlineTest):
         self.assertTrue(unit_page.is_inline_editing_display_name())
 
 
-@attr('shard_3')
+@attr(shard=3)
 class DeleteContentTest(CourseOutlineTest):
     """
     Feature: Deleting sections/subsections/units
@@ -1190,7 +1190,7 @@ class DeleteContentTest(CourseOutlineTest):
         self.assertTrue(self.course_outline_page.has_no_content_message)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class ExpandCollapseMultipleSectionsTest(CourseOutlineTest):
     """
     Feature: Courses with multiple sections can expand and collapse all sections.
@@ -1322,7 +1322,7 @@ class ExpandCollapseMultipleSectionsTest(CourseOutlineTest):
         self.verify_all_sections(collapsed=False)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class ExpandCollapseSingleSectionTest(CourseOutlineTest):
     """
     Feature: Courses with a single section can expand and collapse all sections.
@@ -1362,7 +1362,7 @@ class ExpandCollapseSingleSectionTest(CourseOutlineTest):
         self.assertFalse(self.course_outline_page.section_at(0).subsection_at(1).is_collapsed)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class ExpandCollapseEmptyTest(CourseOutlineTest):
     """
     Feature: Courses with no sections initially can expand and collapse all sections after addition.
@@ -1400,7 +1400,7 @@ class ExpandCollapseEmptyTest(CourseOutlineTest):
         self.assertFalse(self.course_outline_page.section_at(0).is_collapsed)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class DefaultStatesEmptyTest(CourseOutlineTest):
     """
     Feature: Misc course outline default states/actions when starting with an empty course
@@ -1425,7 +1425,7 @@ class DefaultStatesEmptyTest(CourseOutlineTest):
         self.assertTrue(self.course_outline_page.bottom_add_section_button.is_present())
 
 
-@attr('shard_3')
+@attr(shard=3)
 class DefaultStatesContentTest(CourseOutlineTest):
     """
     Feature: Misc course outline default states/actions when starting with a course with content
@@ -1450,7 +1450,7 @@ class DefaultStatesContentTest(CourseOutlineTest):
         self.assertEqual(courseware.xblock_component_type(2), 'discussion')
 
 
-@attr('shard_3')
+@attr(shard=3)
 class UnitNavigationTest(CourseOutlineTest):
     """
     Feature: Navigate to units
@@ -1471,7 +1471,7 @@ class UnitNavigationTest(CourseOutlineTest):
         self.assertTrue(unit.is_browser_on_page)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class PublishSectionTest(CourseOutlineTest):
     """
     Feature: Publish sections.
@@ -1597,7 +1597,7 @@ class PublishSectionTest(CourseOutlineTest):
         return (section, subsection, unit)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class DeprecationWarningMessageTest(CourseOutlineTest):
     """
     Feature: Verify deprecation warning message.
@@ -1763,7 +1763,7 @@ class DeprecationWarningMessageTest(CourseOutlineTest):
         )
 
 
-@attr('shard_4')
+@attr(shard=4)
 class SelfPacedOutlineTest(CourseOutlineTest):
     """Test the course outline for a self-paced course."""
 
diff --git a/common/test/acceptance/tests/studio/test_studio_settings.py b/common/test/acceptance/tests/studio/test_studio_settings.py
index b0dd1553117..06d42fb8c52 100644
--- a/common/test/acceptance/tests/studio/test_studio_settings.py
+++ b/common/test/acceptance/tests/studio/test_studio_settings.py
@@ -22,7 +22,7 @@ from textwrap import dedent
 from xmodule.partitions.partitions import Group
 
 
-@attr('shard_8')
+@attr(shard=8)
 class ContentGroupConfigurationTest(StudioCourseTest):
     """
     Tests for content groups in the Group Configurations Page.
@@ -234,7 +234,7 @@ class ContentGroupConfigurationTest(StudioCourseTest):
         ).fulfill()
 
 
-@attr('shard_8')
+@attr(shard=8)
 class AdvancedSettingsValidationTest(StudioCourseTest):
     """
     Tests for validation feature in Studio's advanced settings tab
@@ -407,7 +407,7 @@ class AdvancedSettingsValidationTest(StudioCourseTest):
         self.assertEquals(set(displayed_fields), set(expected_fields))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ContentLicenseTest(StudioCourseTest):
     """
     Tests for course-level licensing (that is, setting the license,
diff --git a/common/test/acceptance/tests/studio/test_studio_settings_certificates.py b/common/test/acceptance/tests/studio/test_studio_settings_certificates.py
index f56e75546e6..52721b19acc 100644
--- a/common/test/acceptance/tests/studio/test_studio_settings_certificates.py
+++ b/common/test/acceptance/tests/studio/test_studio_settings_certificates.py
@@ -13,7 +13,7 @@ from common.test.acceptance.pages.studio.settings_advanced import AdvancedSettin
 from common.test.acceptance.tests.helpers import skip_if_browser
 
 
-@attr('shard_8')
+@attr(shard=8)
 class CertificatesTest(StudioCourseTest):
     """
     Tests for settings/certificates Page.
diff --git a/common/test/acceptance/tests/studio/test_studio_settings_details.py b/common/test/acceptance/tests/studio/test_studio_settings_details.py
index 835ea93e946..6cae983ac8f 100644
--- a/common/test/acceptance/tests/studio/test_studio_settings_details.py
+++ b/common/test/acceptance/tests/studio/test_studio_settings_details.py
@@ -19,7 +19,7 @@ from common.test.acceptance.tests.helpers import (
 )
 
 
-@attr('shard_4')
+@attr(shard=4)
 class StudioSettingsDetailsTest(StudioCourseTest):
     """Base class for settings and details page tests."""
 
@@ -37,7 +37,7 @@ class StudioSettingsDetailsTest(StudioCourseTest):
         self.assertTrue(self.settings_detail.is_browser_on_page())
 
 
-@attr('shard_4')
+@attr(shard=4)
 class SettingsMilestonesTest(StudioSettingsDetailsTest):
     """
     Tests for milestones feature in Studio's settings tab
@@ -206,7 +206,7 @@ class SettingsMilestonesTest(StudioSettingsDetailsTest):
         ))
 
 
-@attr('shard_4')
+@attr(shard=4)
 class CoursePacingTest(StudioSettingsDetailsTest):
     """Tests for setting a course to self-paced."""
 
diff --git a/common/test/acceptance/tests/studio/test_studio_split_test.py b/common/test/acceptance/tests/studio/test_studio_split_test.py
index 137dcb1dc2b..e91c1deba43 100644
--- a/common/test/acceptance/tests/studio/test_studio_split_test.py
+++ b/common/test/acceptance/tests/studio/test_studio_split_test.py
@@ -66,7 +66,7 @@ class SplitTestMixin(object):
         Promise(missing_groups_button_not_present, "Add missing groups button should not be showing.").fulfill()
 
 
-@attr('shard_2')
+@attr(shard=2)
 class SplitTest(ContainerBase, SplitTestMixin):
     """
     Tests for creating and editing split test instances in Studio.
@@ -199,7 +199,7 @@ class SplitTest(ContainerBase, SplitTestMixin):
         self.verify_groups(container, ['alpha'], [], verify_missing_groups_not_present=False)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class GroupConfigurationsNoSplitTest(StudioCourseTest):
     """
     Tests how the Group Configuration page should look when the split_test module is not enabled.
@@ -224,7 +224,7 @@ class GroupConfigurationsNoSplitTest(StudioCourseTest):
         self.assertFalse(self.group_configurations_page.experiment_group_sections_present)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class GroupConfigurationsTest(ContainerBase, SplitTestMixin):
     """
     Tests that Group Configurations page works correctly with previously
diff --git a/common/test/acceptance/tests/studio/test_studio_textbooks.py b/common/test/acceptance/tests/studio/test_studio_textbooks.py
index 2d952776380..cdfe8fce97c 100644
--- a/common/test/acceptance/tests/studio/test_studio_textbooks.py
+++ b/common/test/acceptance/tests/studio/test_studio_textbooks.py
@@ -8,7 +8,7 @@ from common.test.acceptance.tests.helpers import disable_animations
 from nose.plugins.attrib import attr
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TextbooksTest(StudioCourseTest):
     """
     Test that textbook functionality is working properly on studio side
diff --git a/common/test/acceptance/tests/test_cohorted_courseware.py b/common/test/acceptance/tests/test_cohorted_courseware.py
index 98eca6c02b6..deee9bdbd85 100644
--- a/common/test/acceptance/tests/test_cohorted_courseware.py
+++ b/common/test/acceptance/tests/test_cohorted_courseware.py
@@ -21,8 +21,11 @@ from bok_choy.promise import EmptyPromise
 from bok_choy.page_object import XSS_INJECTION
 
 
-@attr('shard_5')
+@attr(shard=5)
 class EndToEndCohortedCoursewareTest(ContainerBase):
+    """
+    End-to-end of cohorted courseware.
+    """
 
     def setUp(self, is_staff=True):
 
diff --git a/common/test/acceptance/tests/video/test_studio_video_editor.py b/common/test/acceptance/tests/video/test_studio_video_editor.py
index cea89c37208..90f43dfc557 100644
--- a/common/test/acceptance/tests/video/test_studio_video_editor.py
+++ b/common/test/acceptance/tests/video/test_studio_video_editor.py
@@ -7,7 +7,7 @@ from nose.plugins.attrib import attr
 from common.test.acceptance.tests.video.test_studio_video_module import CMSVideoBaseTest
 
 
-@attr('shard_6')
+@attr(shard=6)
 class VideoEditorTest(CMSVideoBaseTest):
     """
     CMS Video Editor Test Class
diff --git a/common/test/acceptance/tests/video/test_studio_video_module.py b/common/test/acceptance/tests/video/test_studio_video_module.py
index cda6b09a8f3..f1b834803df 100644
--- a/common/test/acceptance/tests/video/test_studio_video_module.py
+++ b/common/test/acceptance/tests/video/test_studio_video_module.py
@@ -161,7 +161,7 @@ class CMSVideoBaseTest(UniqueCourseTest):
         self.unit_page.xblocks[1].save_settings()
 
 
-@attr('shard_4')
+@attr(shard=4)
 class CMSVideoTest(CMSVideoBaseTest):
     """
     CMS Video Test Class
diff --git a/common/test/acceptance/tests/video/test_studio_video_transcript.py b/common/test/acceptance/tests/video/test_studio_video_transcript.py
index d6266cecdc1..7a56991df4f 100644
--- a/common/test/acceptance/tests/video/test_studio_video_transcript.py
+++ b/common/test/acceptance/tests/video/test_studio_video_transcript.py
@@ -22,7 +22,7 @@ from nose.plugins.attrib import attr
 from common.test.acceptance.tests.video.test_studio_video_module import CMSVideoBaseTest
 
 
-@attr('shard_6')
+@attr(shard=6)
 class VideoTranscriptTest(CMSVideoBaseTest):
     """
     CMS Video Transcript Test Class
diff --git a/common/test/acceptance/tests/video/test_video_events.py b/common/test/acceptance/tests/video/test_video_events.py
index f1aa207f012..7e8327fdc32 100644
--- a/common/test/acceptance/tests/video/test_video_events.py
+++ b/common/test/acceptance/tests/video/test_video_events.py
@@ -149,7 +149,7 @@ class VideoEventsTest(VideoEventsTestMixin):
         assert_events_equal(static_fields_pattern, load_video_event)
 
 
-@attr('shard_8')
+@attr(shard=8)
 @ddt.ddt
 class VideoBumperEventsTest(VideoEventsTestMixin):
     """ Test bumper video event emission """
diff --git a/common/test/acceptance/tests/video/test_video_handout.py b/common/test/acceptance/tests/video/test_video_handout.py
index 68c0d7f78a1..06ace1b623d 100644
--- a/common/test/acceptance/tests/video/test_video_handout.py
+++ b/common/test/acceptance/tests/video/test_video_handout.py
@@ -7,7 +7,7 @@ from nose.plugins.attrib import attr
 from common.test.acceptance.tests.video.test_studio_video_module import CMSVideoBaseTest
 
 
-@attr('shard_5')
+@attr(shard=5)
 class VideoHandoutTest(CMSVideoBaseTest):
     """
     CMS Video Handout Test Class
diff --git a/common/test/acceptance/tests/video/test_video_license.py b/common/test/acceptance/tests/video/test_video_license.py
index 80f42b28e7e..b5e073081cd 100644
--- a/common/test/acceptance/tests/video/test_video_license.py
+++ b/common/test/acceptance/tests/video/test_video_license.py
@@ -12,7 +12,7 @@ from common.test.acceptance.pages.lms.courseware import CoursewarePage
 from common.test.acceptance.fixtures.course import XBlockFixtureDesc
 
 
-@attr('shard_2')
+@attr(shard=2)
 class VideoLicenseTest(StudioCourseTest):
     """
     Tests for video module-level licensing (that is, setting the license,
diff --git a/common/test/acceptance/tests/video/test_video_module.py b/common/test/acceptance/tests/video/test_video_module.py
index 9616e7fd625..56e18a198ba 100644
--- a/common/test/acceptance/tests/video/test_video_module.py
+++ b/common/test/acceptance/tests/video/test_video_module.py
@@ -198,7 +198,7 @@ class VideoBaseTest(UniqueCourseTest):
         self.video.wait_for_video_player_render()
 
 
-@attr('shard_4')
+@attr(shard=4)
 class YouTubeVideoTest(VideoBaseTest):
     """ Test YouTube Video Player """
 
@@ -935,7 +935,7 @@ class YouTubeVideoTest(VideoBaseTest):
         execute_video_steps(tab1_video_names)
 
 
-@attr('shard_4')
+@attr(shard=4)
 class YouTubeHtml5VideoTest(VideoBaseTest):
     """ Test YouTube HTML5 Video Player """
 
@@ -957,7 +957,7 @@ class YouTubeHtml5VideoTest(VideoBaseTest):
         self.assertTrue(self.video.is_video_rendered('youtube'))
 
 
-@attr('shard_4')
+@attr(shard=4)
 class Html5VideoTest(VideoBaseTest):
     """ Test HTML5 Video Player """
 
@@ -1143,7 +1143,7 @@ class Html5VideoTest(VideoBaseTest):
         self.assertTrue(all([source in HTML5_SOURCES for source in self.video.sources]))
 
 
-@attr('shard_4')
+@attr(shard=4)
 class YouTubeQualityTest(VideoBaseTest):
     """ Test YouTube Video Quality Button """
 
@@ -1192,7 +1192,7 @@ class YouTubeQualityTest(VideoBaseTest):
         self.video.wait_for(lambda: self.video.is_quality_button_active, 'waiting for quality button activation')
 
 
-@attr('shard_4')
+@attr(shard=4)
 class DragAndDropTest(VideoBaseTest):
     """
     Tests draggability of closed captions within videos.
diff --git a/lms/djangoapps/badges/api/tests.py b/lms/djangoapps/badges/api/tests.py
index 47199bfaead..441c593eba3 100644
--- a/lms/djangoapps/badges/api/tests.py
+++ b/lms/djangoapps/badges/api/tests.py
@@ -152,7 +152,7 @@ class TestUserCourseBadgeAssertions(UserAssertionTestCase):
         self.check_assertion_structure(assertion, response['results'][0])
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt
 class TestUserBadgeAssertionsByClass(UserAssertionTestCase):
     """
diff --git a/lms/djangoapps/badges/tests/test_models.py b/lms/djangoapps/badges/tests/test_models.py
index 87745cb2588..fc56ab66b35 100644
--- a/lms/djangoapps/badges/tests/test_models.py
+++ b/lms/djangoapps/badges/tests/test_models.py
@@ -29,7 +29,7 @@ def get_image(name):
     return ImageFile(open(TEST_DATA_ROOT / 'badges' / name + '.png'))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class BadgeImageConfigurationTest(TestCase):
     """
     Test the validation features of BadgeImageConfiguration.
diff --git a/lms/djangoapps/branding/tests/test_models.py b/lms/djangoapps/branding/tests/test_models.py
index d03f1559219..a3d6c3fcae6 100644
--- a/lms/djangoapps/branding/tests/test_models.py
+++ b/lms/djangoapps/branding/tests/test_models.py
@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr
 from branding.models import BrandingInfoConfig
 
 
-@attr('shard_1')
+@attr(shard=1)
 class BrandingInfoConfigTest(TestCase):
     """
     Test the BrandingInfoConfig model.
diff --git a/lms/djangoapps/branding/tests/test_page.py b/lms/djangoapps/branding/tests/test_page.py
index b5f21850b39..247c4d13322 100644
--- a/lms/djangoapps/branding/tests/test_page.py
+++ b/lms/djangoapps/branding/tests/test_page.py
@@ -40,7 +40,7 @@ def mock_render_to_response(*args, **kwargs):
 RENDER_MOCK = Mock(side_effect=mock_render_to_response)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AnonymousIndexPageTest(ModuleStoreTestCase):
     """
     Tests that anonymous users can access the '/' page,  Need courses with start date
@@ -112,7 +112,7 @@ class AnonymousIndexPageTest(ModuleStoreTestCase):
         self.assertEqual(response._headers.get("location")[1], "/login")  # pylint: disable=protected-access
 
 
-@attr('shard_1')
+@attr(shard=1)
 class PreRequisiteCourseCatalog(ModuleStoreTestCase, LoginEnrollmentTestCase, MilestonesTestCaseMixin):
     """
     Test to simulate and verify fix for disappearing courses in
@@ -156,7 +156,7 @@ class PreRequisiteCourseCatalog(ModuleStoreTestCase, LoginEnrollmentTestCase, Mi
         self.assertIn('course that has pre requisite', resp.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class IndexPageCourseCardsSortingTests(ModuleStoreTestCase):
     """
     Test for Index page course cards sorting
diff --git a/lms/djangoapps/bulk_email/tests/test_course_optout.py b/lms/djangoapps/bulk_email/tests/test_course_optout.py
index 17800668233..d43d94d9e0f 100644
--- a/lms/djangoapps/bulk_email/tests/test_course_optout.py
+++ b/lms/djangoapps/bulk_email/tests/test_course_optout.py
@@ -17,7 +17,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
 from bulk_email.models import BulkEmailFlag
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
 class TestOptoutCourseEmails(ModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/bulk_email/tests/test_email.py b/lms/djangoapps/bulk_email/tests/test_email.py
index cd20d9a3fd0..f922492760e 100644
--- a/lms/djangoapps/bulk_email/tests/test_email.py
+++ b/lms/djangoapps/bulk_email/tests/test_email.py
@@ -129,7 +129,7 @@ class EmailSendFromDashboardTestCase(SharedModuleStoreTestCase):
         BulkEmailFlag.objects.all().delete()
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
 class TestEmailSendFromDashboardMockedHtmlToText(EmailSendFromDashboardTestCase):
     """
@@ -442,7 +442,7 @@ class TestEmailSendFromDashboardMockedHtmlToText(EmailSendFromDashboardTestCase)
         self.assertItemsEqual(outbox_contents, should_send_contents)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @skipIf(os.environ.get("TRAVIS") == 'true', "Skip this test in Travis CI.")
 class TestEmailSendFromDashboard(EmailSendFromDashboardTestCase):
     """
diff --git a/lms/djangoapps/bulk_email/tests/test_err_handling.py b/lms/djangoapps/bulk_email/tests/test_err_handling.py
index 9c4363b4df3..f1bdcbe5dfb 100644
--- a/lms/djangoapps/bulk_email/tests/test_err_handling.py
+++ b/lms/djangoapps/bulk_email/tests/test_err_handling.py
@@ -36,7 +36,7 @@ class EmailTestException(Exception):
     pass
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
 class TestEmailErrors(ModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/bulk_email/tests/test_forms.py b/lms/djangoapps/bulk_email/tests/test_forms.py
index b823acc44e5..7ea5771481b 100644
--- a/lms/djangoapps/bulk_email/tests/test_forms.py
+++ b/lms/djangoapps/bulk_email/tests/test_forms.py
@@ -12,7 +12,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseAuthorizationFormTest(ModuleStoreTestCase):
     """Test the CourseAuthorizationAdminForm form for Mongo-backed courses."""
 
diff --git a/lms/djangoapps/bulk_email/tests/test_models.py b/lms/djangoapps/bulk_email/tests/test_models.py
index db3fd2b591b..b0b588f1126 100644
--- a/lms/djangoapps/bulk_email/tests/test_models.py
+++ b/lms/djangoapps/bulk_email/tests/test_models.py
@@ -21,7 +21,7 @@ from openedx.core.djangoapps.course_groups.models import CourseCohort
 from opaque_keys.edx.keys import CourseKey
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
 class CourseEmailTest(TestCase):
     """Test the CourseEmail model."""
@@ -82,7 +82,7 @@ class CourseEmailTest(TestCase):
         self.assertEqual(target.long_display(), 'Cohort: test cohort')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class NoCourseEmailTemplateTest(TestCase):
     """Test the CourseEmailTemplate model without loading the template data."""
 
@@ -91,7 +91,7 @@ class NoCourseEmailTemplateTest(TestCase):
             CourseEmailTemplate.get_template()
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseEmailTemplateTest(TestCase):
     """Test the CourseEmailTemplate model."""
 
@@ -190,7 +190,7 @@ class CourseEmailTemplateTest(TestCase):
         self.assertIn(context['name'], message)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseAuthorizationTest(TestCase):
     """Test the CourseAuthorization model."""
 
diff --git a/lms/djangoapps/bulk_email/tests/test_tasks.py b/lms/djangoapps/bulk_email/tests/test_tasks.py
index 0f457effc3a..06ad3d07c9b 100644
--- a/lms/djangoapps/bulk_email/tests/test_tasks.py
+++ b/lms/djangoapps/bulk_email/tests/test_tasks.py
@@ -75,7 +75,7 @@ def my_update_subtask_status(entry_id, current_task_id, new_subtask_status):
         update_subtask_status(entry_id, current_task_id, new_subtask_status)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
 class TestBulkEmailInstructorTask(InstructorTaskCourseTestCase):
     """Tests instructor task that send bulk email."""
diff --git a/lms/djangoapps/ccx/api/v0/tests/test_views.py b/lms/djangoapps/ccx/api/v0/tests/test_views.py
index 01aa9cd1f47..a3064336565 100644
--- a/lms/djangoapps/ccx/api/v0/tests/test_views.py
+++ b/lms/djangoapps/ccx/api/v0/tests/test_views.py
@@ -139,7 +139,7 @@ class CcxRestApiTest(CcxTestCase, APITestCase):
         self.assertEqual(expected_field_errors, resp_dict_error)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CcxListTest(CcxRestApiTest):
     """
@@ -655,7 +655,7 @@ class CcxListTest(CcxRestApiTest):
             self.assertEqual(course_user, ccx_user)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CcxDetailTest(CcxRestApiTest):
     """
diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py
index 263715e5ae8..e00a8cedd99 100644
--- a/lms/djangoapps/ccx/tests/test_field_override_performance.py
+++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py
@@ -31,7 +31,7 @@ from lms.djangoapps.ccx.tests.factories import CcxFactory
 from openedx.core.djangoapps.content.block_structure.api import get_course_in_cache
 
 
-@attr('shard_3')
+@attr(shard=3)
 @mock.patch.dict(
     'django.conf.settings.FEATURES',
     {
diff --git a/lms/djangoapps/ccx/tests/test_models.py b/lms/djangoapps/ccx/tests/test_models.py
index e81681c43e6..c756b61de00 100644
--- a/lms/djangoapps/ccx/tests/test_models.py
+++ b/lms/djangoapps/ccx/tests/test_models.py
@@ -25,7 +25,7 @@ from ..overrides import override_field_for_ccx
 
 
 @ddt.ddt
-@attr('shard_1')
+@attr(shard=1)
 class TestCCX(ModuleStoreTestCase):
     """Unit tests for the CustomCourseForEdX model
     """
diff --git a/lms/djangoapps/ccx/tests/test_overrides.py b/lms/djangoapps/ccx/tests/test_overrides.py
index 6ddfde9dcb4..6110a74bf7a 100644
--- a/lms/djangoapps/ccx/tests/test_overrides.py
+++ b/lms/djangoapps/ccx/tests/test_overrides.py
@@ -26,7 +26,7 @@ from lms.djangoapps.ccx.overrides import override_field_for_ccx
 from lms.djangoapps.ccx.tests.utils import flatten, iter_blocks
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(
     XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'],
     MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['ccx.overrides.CustomCoursesForEdxOverrideProvider'],
diff --git a/lms/djangoapps/ccx/tests/test_utils.py b/lms/djangoapps/ccx/tests/test_utils.py
index 76c1ba99677..50af4df9189 100644
--- a/lms/djangoapps/ccx/tests/test_utils.py
+++ b/lms/djangoapps/ccx/tests/test_utils.py
@@ -37,7 +37,7 @@ from lms.djangoapps.ccx.tests.factories import CcxFactory
 from lms.djangoapps.ccx.tests.utils import CcxTestCase
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetCCXFromCCXLocator(ModuleStoreTestCase):
     """Verify that get_ccx_from_ccx_locator functions properly"""
     MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
@@ -70,7 +70,7 @@ class TestGetCCXFromCCXLocator(ModuleStoreTestCase):
         self.assertEqual(result, ccx)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetCourseChapters(CcxTestCase):
     """
     Tests for the `get_course_chapters` util function
diff --git a/lms/djangoapps/ccx/tests/test_views.py b/lms/djangoapps/ccx/tests/test_views.py
index 33ee588f0de..55110aac8c1 100644
--- a/lms/djangoapps/ccx/tests/test_views.py
+++ b/lms/djangoapps/ccx/tests/test_views.py
@@ -188,7 +188,7 @@ class TestAdminAccessCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
         self.assertEqual(response.status_code, 403)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(
     XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'],
     MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['ccx.overrides.CustomCoursesForEdxOverrideProvider'],
@@ -303,7 +303,7 @@ class TestCCXProgressChanges(CcxTestCase, LoginEnrollmentTestCase):
         self.assert_progress_summary(ccx_course_key, due)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
     """
@@ -917,7 +917,7 @@ class TestCoachDashboard(CcxTestCase, LoginEnrollmentTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestCoachDashboardSchedule(CcxTestCase, LoginEnrollmentTestCase, ModuleStoreTestCase):
     """
     Tests of the CCX Coach Dashboard which need to modify the course content.
@@ -1059,7 +1059,7 @@ def patched_get_children(self, usage_key_filter=None):
     return list(iter_children())
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(
     XBLOCK_FIELD_DATA_WRAPPERS=['lms.djangoapps.courseware.field_overrides:OverrideModulestoreFieldData.wrap'],
     MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['ccx.overrides.CustomCoursesForEdxOverrideProvider'],
diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py
index dcf95c4fb4f..1b9812c2da2 100644
--- a/lms/djangoapps/certificates/tests/test_api.py
+++ b/lms/djangoapps/certificates/tests/test_api.py
@@ -83,7 +83,7 @@ class WebCertificateTestMixin(object):
         self.store.update_item(self.course, self.user.id)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTestCase):
     """Tests for the `certificate_downloadable_status` helper function. """
 
@@ -203,7 +203,7 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CertificateisInvalid(WebCertificateTestMixin, ModuleStoreTestCase):
     """Tests for the `is_certificate_invalid` helper function. """
@@ -315,7 +315,7 @@ class CertificateisInvalid(WebCertificateTestMixin, ModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CertificateGetTests(SharedModuleStoreTestCase):
     """Tests for the `test_get_certificate_for_user` helper function. """
     @classmethod
@@ -402,7 +402,7 @@ class CertificateGetTests(SharedModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(CERT_QUEUE='certificates')
 class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, ModuleStoreTestCase):
     """Tests for generating certificates for students. """
@@ -494,7 +494,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu
         self.assertEqual(url, "")
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CertificateGenerationEnabledTest(EventTestMixin, TestCase):
     """Test enabling/disabling self-generated certificates for a course. """
@@ -562,7 +562,7 @@ class CertificateGenerationEnabledTest(EventTestMixin, TestCase):
         self.assertEqual(expect_enabled, actual_enabled)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class GenerateExampleCertificatesTest(TestCase):
     """Test generation of example certificates. """
 
@@ -650,7 +650,7 @@ def set_microsite(domain):
 
 
 @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
-@attr('shard_1')
+@attr(shard=1)
 class CertificatesBrandingTest(TestCase):
     """Test certificates branding. """
 
diff --git a/lms/djangoapps/certificates/tests/test_cert_management.py b/lms/djangoapps/certificates/tests/test_cert_management.py
index e9df7d7420b..1a119d75bd6 100644
--- a/lms/djangoapps/certificates/tests/test_cert_management.py
+++ b/lms/djangoapps/certificates/tests/test_cert_management.py
@@ -65,7 +65,7 @@ class CertificateManagementTest(ModuleStoreTestCase):
         self.assertEqual(cert.status, expected_status)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class ResubmitErrorCertificatesTest(CertificateManagementTest):
     """Tests for the resubmit_error_certificates management command. """
@@ -153,7 +153,7 @@ class ResubmitErrorCertificatesTest(CertificateManagementTest):
 
 
 @ddt.ddt
-@attr('shard_1')
+@attr(shard=1)
 class RegenerateCertificatesTest(CertificateManagementTest):
     """
     Tests for regenerating certificates.
@@ -222,7 +222,7 @@ class RegenerateCertificatesTest(CertificateManagementTest):
         self.assertFalse(mock_send_to_queue.called)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class UngenerateCertificatesTest(CertificateManagementTest):
     """
     Tests for generating certificates.
diff --git a/lms/djangoapps/certificates/tests/test_create_fake_cert.py b/lms/djangoapps/certificates/tests/test_create_fake_cert.py
index a35715e254b..860447ff075 100644
--- a/lms/djangoapps/certificates/tests/test_create_fake_cert.py
+++ b/lms/djangoapps/certificates/tests/test_create_fake_cert.py
@@ -9,7 +9,7 @@ from certificates.management.commands import create_fake_cert
 from certificates.models import GeneratedCertificate
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CreateFakeCertTest(TestCase):
     """Tests for the create_fake_certs management command. """
 
diff --git a/lms/djangoapps/certificates/tests/test_models.py b/lms/djangoapps/certificates/tests/test_models.py
index 9aad0b6771f..0c572478bba 100644
--- a/lms/djangoapps/certificates/tests/test_models.py
+++ b/lms/djangoapps/certificates/tests/test_models.py
@@ -40,7 +40,7 @@ PLATFORM_ROOT = TEST_DIR.parent.parent.parent.parent
 TEST_DATA_ROOT = PLATFORM_ROOT / TEST_DATA_DIR
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ExampleCertificateTest(TestCase):
     """Tests for the ExampleCertificate model. """
 
@@ -106,7 +106,7 @@ class ExampleCertificateTest(TestCase):
         self.assertIs(result, None)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CertificateHtmlViewConfigurationTest(TestCase):
     """
     Test the CertificateHtmlViewConfiguration model.
@@ -173,7 +173,7 @@ class CertificateHtmlViewConfigurationTest(TestCase):
         self.assertEquals(self.config.get_config(), {})
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CertificateTemplateAssetTest(TestCase):
     """
     Test Assets are uploading/saving successfully for CertificateTemplateAsset.
@@ -201,7 +201,7 @@ class CertificateTemplateAssetTest(TestCase):
         self.assertEqual(certificate_template_asset.asset, 'certificate_template_assets/1/picture2.jpg')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class EligibleCertificateManagerTest(SharedModuleStoreTestCase):
     """
     Test the GeneratedCertificate model's object manager for filtering
@@ -240,7 +240,7 @@ class EligibleCertificateManagerTest(SharedModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestCertificateGenerationHistory(TestCase):
     """
@@ -306,7 +306,7 @@ class TestCertificateGenerationHistory(TestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CertificateInvalidationTest(SharedModuleStoreTestCase):
     """
     Test for the Certificate Invalidation model.
diff --git a/lms/djangoapps/certificates/tests/test_queue.py b/lms/djangoapps/certificates/tests/test_queue.py
index 8597ca28455..0b21f652cd1 100644
--- a/lms/djangoapps/certificates/tests/test_queue.py
+++ b/lms/djangoapps/certificates/tests/test_queue.py
@@ -39,7 +39,7 @@ from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVer
 
 
 @ddt.ddt
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(CERT_QUEUE='certificates')
 class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
     """Test the "add to queue" operation of the XQueue interface. """
@@ -283,7 +283,7 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(CERT_QUEUE='certificates')
 class XQueueCertInterfaceExampleCertificateTest(TestCase):
     """Tests for the XQueue interface for certificate generation. """
diff --git a/lms/djangoapps/certificates/tests/test_views.py b/lms/djangoapps/certificates/tests/test_views.py
index b4f0c9b2e45..6ccab264ae2 100644
--- a/lms/djangoapps/certificates/tests/test_views.py
+++ b/lms/djangoapps/certificates/tests/test_views.py
@@ -36,7 +36,7 @@ FEATURES_WITH_CUSTOM_CERTS_ENABLED = {
 FEATURES_WITH_CUSTOM_CERTS_ENABLED.update(FEATURES_WITH_CERTS_ENABLED)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class UpdateExampleCertificateViewTest(CacheIsolationTestCase):
     """Tests for the XQueue callback that updates example certificates. """
@@ -180,7 +180,7 @@ class UpdateExampleCertificateViewTest(CacheIsolationTestCase):
         self.assertEqual(content['return_code'], 0)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
     """
     Tests for the microsite certificates web/html views
diff --git a/lms/djangoapps/certificates/tests/test_webview_views.py b/lms/djangoapps/certificates/tests/test_webview_views.py
index de9e0618729..86b2356b7a5 100644
--- a/lms/djangoapps/certificates/tests/test_webview_views.py
+++ b/lms/djangoapps/certificates/tests/test_webview_views.py
@@ -171,7 +171,7 @@ class CommonCertificatesTestCase(ModuleStoreTestCase):
         template.save()
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CertificatesViewsTests(CommonCertificatesTestCase):
     """
@@ -1145,7 +1145,7 @@ class CertificatesViewsTests(CommonCertificatesTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CertificateEventTests(CommonCertificatesTestCase, EventTrackingTestCase):
     """
     Test events emitted by certificate handling.
diff --git a/lms/djangoapps/certificates/tests/tests.py b/lms/djangoapps/certificates/tests/tests.py
index 6a813d5a968..cb65f8140e0 100644
--- a/lms/djangoapps/certificates/tests/tests.py
+++ b/lms/djangoapps/certificates/tests/tests.py
@@ -26,7 +26,7 @@ from util.milestones_helpers import (
 from milestones.tests.utils import MilestonesTestCaseMixin
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt
 class CertificatesModelTest(ModuleStoreTestCase, MilestonesTestCaseMixin):
     """
diff --git a/lms/djangoapps/class_dashboard/tests/test_dashboard_data.py b/lms/djangoapps/class_dashboard/tests/test_dashboard_data.py
index d968984e142..616c4aa600c 100644
--- a/lms/djangoapps/class_dashboard/tests/test_dashboard_data.py
+++ b/lms/djangoapps/class_dashboard/tests/test_dashboard_data.py
@@ -27,7 +27,7 @@ from class_dashboard.views import has_instructor_access_for_class
 USER_COUNT = 11
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetProblemGradeDistribution(SharedModuleStoreTestCase):
     """
     Tests related to class_dashboard/dashboard_data.py
diff --git a/lms/djangoapps/class_dashboard/tests/test_views.py b/lms/djangoapps/class_dashboard/tests/test_views.py
index 209ecb136ac..adf5ff6b077 100644
--- a/lms/djangoapps/class_dashboard/tests/test_views.py
+++ b/lms/djangoapps/class_dashboard/tests/test_views.py
@@ -13,7 +13,7 @@ from class_dashboard import views
 from student.tests.factories import AdminFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestViews(ModuleStoreTestCase):
     """
     Tests related to class_dashboard/views.py
diff --git a/lms/djangoapps/commerce/api/v0/tests/test_views.py b/lms/djangoapps/commerce/api/v0/tests/test_views.py
index 2ec8039dda6..b17bfdc4bee 100644
--- a/lms/djangoapps/commerce/api/v0/tests/test_views.py
+++ b/lms/djangoapps/commerce/api/v0/tests/test_views.py
@@ -30,7 +30,7 @@ from student.tests.tests import EnrollmentEventTestMixin
 from xmodule.modulestore.django import modulestore
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 @override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY)
 class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase):
@@ -362,7 +362,7 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
             self.assertEqual(self._post_to_view().status_code, 406)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY)
 class BasketOrderViewTests(UserMixin, TestCase):
     """ Tests for the basket order view. """
diff --git a/lms/djangoapps/commerce/api/v1/tests/test_views.py b/lms/djangoapps/commerce/api/v1/tests/test_views.py
index 41efd055953..ca6c4d31dd1 100644
--- a/lms/djangoapps/commerce/api/v1/tests/test_views.py
+++ b/lms/djangoapps/commerce/api/v1/tests/test_views.py
@@ -104,7 +104,7 @@ class CourseListViewTests(CourseApiViewTestMixin, ModuleStoreTestCase):
         self.assertListEqual(actual, expected)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase):
     """ Tests for CourseRetrieveUpdateView. """
@@ -390,7 +390,7 @@ class CourseRetrieveUpdateViewTests(CourseApiViewTestMixin, ModuleStoreTestCase)
         self.assertDictEqual(expected_dict, json.loads(response.content))
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY)
 class OrderViewTests(UserMixin, TestCase):
     """ Tests for the basket order view. """
diff --git a/lms/djangoapps/commerce/tests/test_views.py b/lms/djangoapps/commerce/tests/test_views.py
index 50ab358d89d..2c8e90c786b 100644
--- a/lms/djangoapps/commerce/tests/test_views.py
+++ b/lms/djangoapps/commerce/tests/test_views.py
@@ -23,7 +23,7 @@ class UserMixin(object):
         self.client.login(username=self.user.username, password='test')
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class ReceiptViewTests(UserMixin, TestCase):
     """ Tests for the receipt view. """
diff --git a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py
index 92c0e81fe5f..5e1fd4e960c 100644
--- a/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py
+++ b/lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py
@@ -16,7 +16,7 @@ from ..milestones import MilestonesTransformer
 from ...api import get_course_blocks
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True, 'MILESTONES_APP': True})
 class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseMixin):
diff --git a/lms/djangoapps/course_api/tests/test_serializers.py b/lms/djangoapps/course_api/tests/test_serializers.py
index 5b14ac21b87..96bee1f6442 100644
--- a/lms/djangoapps/course_api/tests/test_serializers.py
+++ b/lms/djangoapps/course_api/tests/test_serializers.py
@@ -21,7 +21,7 @@ from ..serializers import CourseSerializer, CourseDetailSerializer
 from .mixins import CourseApiFactoryMixin
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class TestCourseSerializer(CourseApiFactoryMixin, ModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/course_api/tests/test_views.py b/lms/djangoapps/course_api/tests/test_views.py
index 104e8f17137..bb886883492 100644
--- a/lms/djangoapps/course_api/tests/test_views.py
+++ b/lms/djangoapps/course_api/tests/test_views.py
@@ -47,7 +47,7 @@ class CourseApiTestViewMixin(CourseApiFactoryMixin):
         return response
 
 
-@attr('shard_3')
+@attr(shard=3)
 class CourseListViewTestCase(CourseApiTestViewMixin, SharedModuleStoreTestCase):
     """
     Test responses returned from CourseListView.
diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_hidden_content.py b/lms/djangoapps/course_blocks/transformers/tests/test_hidden_content.py
index 480480e94f9..4424133bcae 100644
--- a/lms/djangoapps/course_blocks/transformers/tests/test_hidden_content.py
+++ b/lms/djangoapps/course_blocks/transformers/tests/test_hidden_content.py
@@ -10,7 +10,7 @@ from ..hidden_content import HiddenContentTransformer
 from .helpers import BlockParentsMapTestCase, update_block
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class HiddenContentTransformerTestCase(BlockParentsMapTestCase):
     """
diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_split_test.py b/lms/djangoapps/course_blocks/transformers/tests/test_split_test.py
index 27fe36748f1..f5110868f5a 100644
--- a/lms/djangoapps/course_blocks/transformers/tests/test_split_test.py
+++ b/lms/djangoapps/course_blocks/transformers/tests/test_split_test.py
@@ -15,7 +15,7 @@ from ..user_partitions import UserPartitionTransformer, _get_user_partition_grou
 from .helpers import CourseStructureTestCase, create_location
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class SplitTestTransformerTestCase(CourseStructureTestCase):
     """
diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_start_date.py b/lms/djangoapps/course_blocks/transformers/tests/test_start_date.py
index 3f823912ee5..bbe4ae0725e 100644
--- a/lms/djangoapps/course_blocks/transformers/tests/test_start_date.py
+++ b/lms/djangoapps/course_blocks/transformers/tests/test_start_date.py
@@ -12,7 +12,7 @@ from ..start_date import StartDateTransformer, DEFAULT_START_DATE
 from .helpers import BlockParentsMapTestCase, update_block
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class StartDateTransformerTestCase(BlockParentsMapTestCase):
     """
diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_user_partitions.py b/lms/djangoapps/course_blocks/transformers/tests/test_user_partitions.py
index 511b8f7016e..2d51078a3b2 100644
--- a/lms/djangoapps/course_blocks/transformers/tests/test_user_partitions.py
+++ b/lms/djangoapps/course_blocks/transformers/tests/test_user_partitions.py
@@ -68,7 +68,7 @@ class UserPartitionTestMixin(object):
             self.partition_cohorts.append(partition_cohorts)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class UserPartitionTransformerTestCase(UserPartitionTestMixin, CourseStructureTestCase):
     """
@@ -241,7 +241,7 @@ class UserPartitionTransformerTestCase(UserPartitionTestMixin, CourseStructureTe
         )
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class MergedGroupAccessTestData(UserPartitionTestMixin, CourseStructureTestCase):
     """
diff --git a/lms/djangoapps/course_blocks/transformers/tests/test_visibility.py b/lms/djangoapps/course_blocks/transformers/tests/test_visibility.py
index 8aa6d8c7e2a..9bc1d01c9e4 100644
--- a/lms/djangoapps/course_blocks/transformers/tests/test_visibility.py
+++ b/lms/djangoapps/course_blocks/transformers/tests/test_visibility.py
@@ -8,7 +8,7 @@ from ..visibility import VisibilityTransformer
 from .helpers import BlockParentsMapTestCase, update_block
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class VisibilityTransformerTestCase(BlockParentsMapTestCase):
     """
diff --git a/lms/djangoapps/course_wiki/tests/test_access.py b/lms/djangoapps/course_wiki/tests/test_access.py
index df2fbaae55d..7c0be1bf368 100644
--- a/lms/djangoapps/course_wiki/tests/test_access.py
+++ b/lms/djangoapps/course_wiki/tests/test_access.py
@@ -52,7 +52,7 @@ class TestWikiAccessBase(ModuleStoreTestCase):
         ]
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestWikiAccess(TestWikiAccessBase):
     """Test wiki access for course staff."""
     def setUp(self):
@@ -113,7 +113,7 @@ class TestWikiAccess(TestWikiAccessBase):
             self.assertFalse(user_is_article_course_staff(course_staff, self.wiki_310b.article))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestWikiAccessForStudent(TestWikiAccessBase):
     """Test access for students."""
     def setUp(self):
@@ -129,7 +129,7 @@ class TestWikiAccessForStudent(TestWikiAccessBase):
             self.assertFalse(user_is_article_course_staff(self.student, page.article))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestWikiAccessForNumericalCourseNumber(TestWikiAccessBase):
     """Test staff has access if course number is numerical and wiki slug has an underscore appended."""
     def setUp(self):
@@ -149,7 +149,7 @@ class TestWikiAccessForNumericalCourseNumber(TestWikiAccessBase):
                 self.assertTrue(user_is_article_course_staff(course_staff, page.article))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestWikiAccessForOldFormatCourseStaffGroups(TestWikiAccessBase):
     """Test staff has access if course group has old format."""
     def setUp(self):
diff --git a/lms/djangoapps/course_wiki/tests/test_comprehensive_theming.py b/lms/djangoapps/course_wiki/tests/test_comprehensive_theming.py
index 375415b252d..ecd677b8053 100644
--- a/lms/djangoapps/course_wiki/tests/test_comprehensive_theming.py
+++ b/lms/djangoapps/course_wiki/tests/test_comprehensive_theming.py
@@ -14,7 +14,7 @@ from courseware.tests.factories import InstructorFactory
 from course_wiki.views import get_or_create_root
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestComprehensiveTheming(ModuleStoreTestCase):
     """Tests for comprehensive theming of wiki pages."""
 
diff --git a/lms/djangoapps/course_wiki/tests/test_middleware.py b/lms/djangoapps/course_wiki/tests/test_middleware.py
index 494b0c4ff29..23fd4e830ff 100644
--- a/lms/djangoapps/course_wiki/tests/test_middleware.py
+++ b/lms/djangoapps/course_wiki/tests/test_middleware.py
@@ -13,7 +13,7 @@ from courseware.tests.factories import InstructorFactory
 from course_wiki.views import get_or_create_root
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestWikiAccessMiddleware(ModuleStoreTestCase):
     """Tests for WikiAccessMiddleware."""
 
diff --git a/lms/djangoapps/course_wiki/tests/tests.py b/lms/djangoapps/course_wiki/tests/tests.py
index 387d964f83e..f6dbaa6a2fb 100644
--- a/lms/djangoapps/course_wiki/tests/tests.py
+++ b/lms/djangoapps/course_wiki/tests/tests.py
@@ -8,7 +8,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
 from mock import patch
 
 
-@attr('shard_1')
+@attr(shard=1)
 class WikiRedirectTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
     """
     Tests for wiki course redirection.
diff --git a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py
index bf4dc2bc6b1..40af376df2b 100644
--- a/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py
+++ b/lms/djangoapps/courseware/management/commands/tests/test_dump_course.py
@@ -27,7 +27,7 @@ DATA_DIR = settings.COMMON_TEST_DATA_ROOT
 XML_COURSE_DIRS = ['simple']
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CommandsTestBase(SharedModuleStoreTestCase):
     """
     Base class for testing different django commands.
diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py
index d2b6e26ae3e..0b733ad20e5 100644
--- a/lms/djangoapps/courseware/tests/test_about.py
+++ b/lms/djangoapps/courseware/tests/test_about.py
@@ -41,7 +41,7 @@ REG_STR = "<form id=\"class_enroll_form\" method=\"post\" data-remote=\"true\" a
 SHIB_ERROR_STR = "The currently logged-in user account does not have permission to enroll in this course."
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase, EventTrackingTestCase, MilestonesTestCaseMixin):
     """
     Tests about xblock.
@@ -195,7 +195,7 @@ class AboutTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase, EventTra
         self.assertEqual(resp.status_code, 200)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AboutTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
     """
     Tests for the course about page
@@ -243,7 +243,7 @@ class AboutTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
         self.assertIn(self.xml_data, resp.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AboutWithCappedEnrollmentsTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
     """
     This test case will check the About page when a course has a capped enrollment
@@ -296,7 +296,7 @@ class AboutWithCappedEnrollmentsTestCase(LoginEnrollmentTestCase, SharedModuleSt
         self.assertNotIn(REG_STR, resp.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AboutWithInvitationOnly(SharedModuleStoreTestCase):
     """
     This test case will check the About page when a course is invitation only.
@@ -345,7 +345,7 @@ class AboutWithInvitationOnly(SharedModuleStoreTestCase):
         self.assertIn(REG_STR, resp.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.dict(settings.FEATURES, {'RESTRICT_ENROLL_BY_REG_METHOD': True})
 class AboutTestCaseShibCourse(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
     """
@@ -389,7 +389,7 @@ class AboutTestCaseShibCourse(LoginEnrollmentTestCase, SharedModuleStoreTestCase
         self.assertIn(REG_STR, resp.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AboutWithClosedEnrollment(ModuleStoreTestCase):
     """
     This test case will check the About page for a course that has enrollment start/end
@@ -432,7 +432,7 @@ class AboutWithClosedEnrollment(ModuleStoreTestCase):
         self.assertNotIn('<span class="important-dates-item-text">$10</span>', resp.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.dict(settings.FEATURES, {'ENABLE_SHOPPING_CART': True})
 @patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
 class AboutPurchaseCourseTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
diff --git a/lms/djangoapps/courseware/tests/test_access.py b/lms/djangoapps/courseware/tests/test_access.py
index 1fba0b50823..cbbfc5bdb0e 100644
--- a/lms/djangoapps/courseware/tests/test_access.py
+++ b/lms/djangoapps/courseware/tests/test_access.py
@@ -143,7 +143,7 @@ class CoachAccessTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase)
         self.assertEqual(resp.status_code, 404)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin):
     """
@@ -608,7 +608,7 @@ class AccessTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTes
         self.assertEqual(response.status_code, 200)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class UserRoleTestCase(TestCase):
     """
     Tests for user roles.
@@ -665,7 +665,7 @@ class UserRoleTestCase(TestCase):
         )
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class CourseOverviewAccessTestCase(ModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/courseware/tests/test_course_info.py b/lms/djangoapps/courseware/tests/test_course_info.py
index acb46287630..76d2edf9a44 100644
--- a/lms/djangoapps/courseware/tests/test_course_info.py
+++ b/lms/djangoapps/courseware/tests/test_course_info.py
@@ -30,7 +30,7 @@ from .helpers import LoginEnrollmentTestCase
 from lms.djangoapps.ccx.tests.factories import CcxFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
     """
     Tests for the Course Info page
@@ -98,7 +98,7 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
         self.assertEqual(response.status_code, 404)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseInfoLastAccessedTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
     """
     Tests of the CourseInfo last accessed link.
@@ -146,7 +146,7 @@ class CourseInfoLastAccessedTestCase(LoginEnrollmentTestCase, ModuleStoreTestCas
         self.assertEqual(content('.page-header-secondary .last-accessed-link').attr('href'), section_url)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseInfoTitleTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
     """
     Tests of the CourseInfo page title.
@@ -236,7 +236,7 @@ class CourseInfoTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
         self.assertRedirects(response, expected, status_code=302, target_status_code=200)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseInfoTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
     """
     Tests for the Course Info page for an XML course
@@ -284,7 +284,7 @@ class CourseInfoTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
         self.assertNotIn(self.xml_data, resp.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(FEATURES=dict(settings.FEATURES, EMBARGO=False))
 class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/courseware/tests/test_course_survey.py b/lms/djangoapps/courseware/tests/test_course_survey.py
index 25ff0adf7de..87e2357d64d 100644
--- a/lms/djangoapps/courseware/tests/test_course_survey.py
+++ b/lms/djangoapps/courseware/tests/test_course_survey.py
@@ -17,7 +17,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 from courseware.tests.helpers import LoginEnrollmentTestCase
 
 
-@attr('shard_1')
+@attr(shard=1)
 class SurveyViewsTests(LoginEnrollmentTestCase, SharedModuleStoreTestCase, XssTestMixin):
     """
     All tests for the views.py file
diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py
index 92151611187..6acf5836e67 100644
--- a/lms/djangoapps/courseware/tests/test_courses.py
+++ b/lms/djangoapps/courseware/tests/test_courses.py
@@ -43,7 +43,7 @@ CMS_BASE_TEST = 'testcms'
 TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CoursesTest(ModuleStoreTestCase):
     """Test methods related to fetching courses."""
@@ -160,7 +160,7 @@ class CoursesTest(ModuleStoreTestCase):
             )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ModuleStoreBranchSettingTest(ModuleStoreTestCase):
     """Test methods related to the modulestore branch setting."""
     @mock.patch(
@@ -186,7 +186,7 @@ class ModuleStoreBranchSettingTest(ModuleStoreTestCase):
         self.assertEqual(_get_modulestore_branch_setting(), 'fake_default_branch')
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(CMS_BASE=CMS_BASE_TEST)
 class MongoCourseImageTestCase(ModuleStoreTestCase):
     """Tests for course image URLs when using a mongo modulestore."""
@@ -242,7 +242,7 @@ class MongoCourseImageTestCase(ModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class XmlCourseImageTestCase(XModuleXmlImportTest):
     """Tests for course image URLs when using an xml modulestore."""
 
@@ -260,7 +260,7 @@ class XmlCourseImageTestCase(XModuleXmlImportTest):
         self.assertEquals(course_image_url(course), u'/static/xml_test_course/before after.jpg')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CoursesRenderTest(ModuleStoreTestCase):
     """Test methods related to rendering courses content."""
 
@@ -306,7 +306,7 @@ class CoursesRenderTest(ModuleStoreTestCase):
             self.assertIn("this module is temporarily unavailable", course_about)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CourseInstantiationTests(ModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py
index 6a79e94599a..7676a9367bc 100644
--- a/lms/djangoapps/courseware/tests/test_date_summary.py
+++ b/lms/djangoapps/courseware/tests/test_date_summary.py
@@ -29,7 +29,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CourseDateSummaryTest(SharedModuleStoreTestCase):
     """Tests for course date summary blocks."""
diff --git a/lms/djangoapps/courseware/tests/test_draft_modulestore.py b/lms/djangoapps/courseware/tests/test_draft_modulestore.py
index 0f0c5d3d0d8..c5217333e74 100644
--- a/lms/djangoapps/courseware/tests/test_draft_modulestore.py
+++ b/lms/djangoapps/courseware/tests/test_draft_modulestore.py
@@ -5,7 +5,7 @@ from xmodule.modulestore.django import modulestore
 from opaque_keys.edx.locations import SlashSeparatedCourseKey
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestDraftModuleStore(TestCase):
     """
     Test the draft modulestore
diff --git a/lms/djangoapps/courseware/tests/test_entrance_exam.py b/lms/djangoapps/courseware/tests/test_entrance_exam.py
index 904b7aa16a2..61963a6dbfc 100644
--- a/lms/djangoapps/courseware/tests/test_entrance_exam.py
+++ b/lms/djangoapps/courseware/tests/test_entrance_exam.py
@@ -38,7 +38,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True})
 class EntranceExamTestCases(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin):
     """
diff --git a/lms/djangoapps/courseware/tests/test_favicon.py b/lms/djangoapps/courseware/tests/test_favicon.py
index bdbbe2fe531..7f61acf04e7 100644
--- a/lms/djangoapps/courseware/tests/test_favicon.py
+++ b/lms/djangoapps/courseware/tests/test_favicon.py
@@ -6,7 +6,7 @@ from nose.plugins.attrib import attr
 from util.testing import UrlResetMixin
 
 
-@attr('shard_1')
+@attr(shard=1)
 class FaviconTestCase(UrlResetMixin, TestCase):
     """
     Tests of the courseware favicon.
diff --git a/lms/djangoapps/courseware/tests/test_field_overrides.py b/lms/djangoapps/courseware/tests/test_field_overrides.py
index 77febdf3b27..142ed5a4b05 100644
--- a/lms/djangoapps/courseware/tests/test_field_overrides.py
+++ b/lms/djangoapps/courseware/tests/test_field_overrides.py
@@ -45,7 +45,7 @@ class TestOverrideProvider(FieldOverrideProvider):
         return True
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(FIELD_OVERRIDE_PROVIDERS=(
     'courseware.tests.test_field_overrides.TestOverrideProvider',))
 class OverrideFieldDataTests(SharedModuleStoreTestCase):
@@ -125,7 +125,7 @@ class OverrideFieldDataTests(SharedModuleStoreTestCase):
         self.assertIsInstance(data, DictFieldData)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(
     MODULESTORE_FIELD_OVERRIDE_PROVIDERS=['courseware.tests.test_field_overrides.TestOverrideProvider']
 )
@@ -142,7 +142,7 @@ class OverrideModulestoreFieldDataTests(FieldOverrideTestMixin, OverrideFieldDat
         self.assertIsInstance(data, DictFieldData)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ResolveDottedTests(unittest.TestCase):
     """
     Tests for `resolve_dotted`.
diff --git a/lms/djangoapps/courseware/tests/test_footer.py b/lms/djangoapps/courseware/tests/test_footer.py
index 1f8c2aa00e2..eb9f97f1d75 100644
--- a/lms/djangoapps/courseware/tests/test_footer.py
+++ b/lms/djangoapps/courseware/tests/test_footer.py
@@ -14,7 +14,7 @@ from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_t
 
 
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-@attr('shard_1')
+@attr(shard=1)
 class TestFooter(TestCase):
     """
     Tests for edx and OpenEdX footer
diff --git a/lms/djangoapps/courseware/tests/test_group_access.py b/lms/djangoapps/courseware/tests/test_group_access.py
index 5a040768dea..4566948eeb2 100644
--- a/lms/djangoapps/courseware/tests/test_group_access.py
+++ b/lms/djangoapps/courseware/tests/test_group_access.py
@@ -51,7 +51,7 @@ def resolve_attrs(test_method):
     return _wrapper
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class GroupAccessTestCase(ModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/courseware/tests/test_i18n.py b/lms/djangoapps/courseware/tests/test_i18n.py
index 263b6ef1f89..725a3f2195b 100644
--- a/lms/djangoapps/courseware/tests/test_i18n.py
+++ b/lms/djangoapps/courseware/tests/test_i18n.py
@@ -69,7 +69,7 @@ class BaseI18nTestCase(TestCase):
         self.client.login(username=self.user.username, password=self.pwd)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class I18nTestCase(BaseI18nTestCase):
     """
     Tests for i18n
@@ -103,7 +103,7 @@ class I18nTestCase(BaseI18nTestCase):
         self.assert_tag_has_attr(response.content, "body", "class", "rtl")
 
 
-@attr('shard_1')
+@attr(shard=1)
 class I18nRegressionTests(BaseI18nTestCase):
     """
     Tests for i18n
@@ -158,7 +158,7 @@ class I18nRegressionTests(BaseI18nTestCase):
         self.assert_tag_has_attr(response.content, "html", "lang", site_lang)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class I18nLangPrefTests(BaseI18nTestCase):
     """
     Regression tests of language presented to the user, when they
diff --git a/lms/djangoapps/courseware/tests/test_lti_integration.py b/lms/djangoapps/courseware/tests/test_lti_integration.py
index 89915e475e0..2f893856596 100644
--- a/lms/djangoapps/courseware/tests/test_lti_integration.py
+++ b/lms/djangoapps/courseware/tests/test_lti_integration.py
@@ -18,7 +18,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 from xmodule.x_module import STUDENT_VIEW
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestLTI(BaseTestXmodule):
     """
     Integration test for lti xmodule.
@@ -124,7 +124,7 @@ class TestLTI(BaseTestXmodule):
         self.assertEqual(generated_content, expected_content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestLTIModuleListing(SharedModuleStoreTestCase):
     """
     a test for the rest endpoint that lists LTI modules in a course
diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py
index 459f48f1b7a..1efa0a49f2b 100644
--- a/lms/djangoapps/courseware/tests/test_masquerade.py
+++ b/lms/djangoapps/courseware/tests/test_masquerade.py
@@ -151,7 +151,7 @@ class MasqueradeTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase, Mil
         self.assertEqual(show_answer_expected, "Show Answer" in problem_html)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class NormalStudentVisibilityTest(MasqueradeTestCase):
     """
     Verify the course displays as expected for a "normal" student (to ensure test setup is correct).
@@ -206,7 +206,7 @@ class StaffMasqueradeTestCase(MasqueradeTestCase):
         return response
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestStaffMasqueradeAsStudent(StaffMasqueradeTestCase):
     """
     Check for staff being able to masquerade as student.
@@ -244,7 +244,7 @@ class TestStaffMasqueradeAsStudent(StaffMasqueradeTestCase):
         self.verify_show_answer_present(True)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmissionTestMixin):
     """
     Check for staff being able to masquerade as a specific student.
@@ -362,7 +362,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi
         self.assertIn("OOGIE BLOOGIE", content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetMasqueradingGroupId(StaffMasqueradeTestCase):
     """
     Check for staff being able to masquerade as belonging to a group.
diff --git a/lms/djangoapps/courseware/tests/test_microsites.py b/lms/djangoapps/courseware/tests/test_microsites.py
index 6b226fb4cd9..10c353841fc 100644
--- a/lms/djangoapps/courseware/tests/test_microsites.py
+++ b/lms/djangoapps/courseware/tests/test_microsites.py
@@ -14,7 +14,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestSites(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
     This is testing of the Site Configuration feature
diff --git a/lms/djangoapps/courseware/tests/test_middleware.py b/lms/djangoapps/courseware/tests/test_middleware.py
index 42755a9a9a3..66e83822b83 100644
--- a/lms/djangoapps/courseware/tests/test_middleware.py
+++ b/lms/djangoapps/courseware/tests/test_middleware.py
@@ -14,7 +14,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CoursewareMiddlewareTestCase(SharedModuleStoreTestCase):
     """Tests that courseware middleware is correctly redirected"""
 
diff --git a/lms/djangoapps/courseware/tests/test_model_data.py b/lms/djangoapps/courseware/tests/test_model_data.py
index 9f62de0875f..dcd6fc74666 100644
--- a/lms/djangoapps/courseware/tests/test_model_data.py
+++ b/lms/djangoapps/courseware/tests/test_model_data.py
@@ -52,7 +52,7 @@ class StudentModuleFactory(cmfStudentModuleFactory):
     course_id = course_id
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInvalidScopes(TestCase):
     def setUp(self):
         super(TestInvalidScopes, self).setUp()
@@ -73,7 +73,7 @@ class TestInvalidScopes(TestCase):
             self.assertRaises(InvalidScopeError, self.kvs.set_many, {key: 'value'})
 
 
-@attr('shard_1')
+@attr(shard=1)
 class OtherUserFailureTestMixin(object):
     """
     Mixin class to add test cases for failures when a user trying to use the kvs is not
@@ -98,7 +98,7 @@ class OtherUserFailureTestMixin(object):
             self.kvs.set(self.other_key_factory(self.existing_field_name), "new_value")
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
     """Tests for user_state storage via StudentModule"""
     other_key_factory = partial(DjangoKeyValueStore.Key, Scope.user_state, 2, location('usage_id'))  # user_id=2, not 1
@@ -227,7 +227,7 @@ class TestStudentModuleStorage(OtherUserFailureTestMixin, TestCase):
         self.assertEquals(exception_context.exception.saved_field_names, [])
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestMissingStudentModule(TestCase):
     # Tell Django to clean out all databases, not just default
     multi_db = True
@@ -283,7 +283,7 @@ class TestMissingStudentModule(TestCase):
             self.assertFalse(self.kvs.has(user_state_key('a_field')))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class StorageTestBase(object):
     """
     A base class for that gets subclassed when testing each of the scopes.
diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py
index 104a8886ba2..8cf1115b224 100644
--- a/lms/djangoapps/courseware/tests/test_module_render.py
+++ b/lms/djangoapps/courseware/tests/test_module_render.py
@@ -120,7 +120,7 @@ class GradedStatelessXBlock(XBlock):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class ModuleRenderTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -406,7 +406,7 @@ class ModuleRenderTestCase(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
         self.assertEqual(hash_resource(resources), 'a76e27c8e80ca3efd7ce743093aa59e0')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestHandleXBlockCallback(SharedModuleStoreTestCase, LoginEnrollmentTestCase, MilestonesTestCaseMixin):
     """
     Test the handle_xblock_callback function
@@ -613,7 +613,7 @@ class TestHandleXBlockCallback(SharedModuleStoreTestCase, LoginEnrollmentTestCas
         self.assertEquals(len(doc('div.xblock-student_view-videosequence')), 1)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestTOC(ModuleStoreTestCase):
     """Check the Table of Contents for a course"""
@@ -719,7 +719,7 @@ class TestTOC(ModuleStoreTestCase):
             self.assertEquals(actual['next_of_active_section']['url_name'], 'video_123456789012')
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 @patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True})
 class TestProctoringRendering(SharedModuleStoreTestCase):
@@ -1047,7 +1047,7 @@ class TestProctoringRendering(SharedModuleStoreTestCase):
         return None
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGatedSubsectionRendering(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
     @classmethod
     def setUpClass(cls):
@@ -1130,7 +1130,7 @@ class TestGatedSubsectionRendering(SharedModuleStoreTestCase, MilestonesTestCase
         self.assertIsNone(actual['next_of_active_section'])
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestHtmlModifiers(ModuleStoreTestCase):
     """
@@ -1294,7 +1294,7 @@ class XBlockWithJsonInitData(XBlock):
         return frag
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class JsonInitDataTest(ModuleStoreTestCase):
     """Tests for JSON data injected into the JS init function."""
@@ -1379,7 +1379,7 @@ class ViewInStudioTest(ModuleStoreTestCase):
         self.child_module = self._get_module(course.id, child_descriptor, child_descriptor.location)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class MongoViewInStudioTest(ViewInStudioTest):
     """Test the 'View in Studio' link visibility in a mongo backed course."""
 
@@ -1408,7 +1408,7 @@ class MongoViewInStudioTest(ViewInStudioTest):
         self.assertNotIn('View Unit in Studio', result_fragment.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class MixedViewInStudioTest(ViewInStudioTest):
     """Test the 'View in Studio' link visibility in a mixed mongo backed course."""
 
@@ -1440,7 +1440,7 @@ class DetachedXBlock(XBlock):
         return frag
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.dict('django.conf.settings.FEATURES', {'DISPLAY_DEBUG_INFO_TO_STAFF': True, 'DISPLAY_HISTOGRAMS_TO_STAFF': True})
 @patch('courseware.module_render.has_access', Mock(return_value=True, autospec=True))
 class TestStaffDebugInfo(SharedModuleStoreTestCase):
@@ -1587,7 +1587,7 @@ PER_STUDENT_ANONYMIZED_DESCRIPTORS = set(
 )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestAnonymousStudentId(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -1670,7 +1670,7 @@ class TestAnonymousStudentId(SharedModuleStoreTestCase, LoginEnrollmentTestCase)
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch('track.views.tracker', autospec=True)
 class TestModuleTrackingContext(SharedModuleStoreTestCase):
     """
@@ -1789,7 +1789,7 @@ class TestModuleTrackingContext(SharedModuleStoreTestCase):
             self.assertEqual(module_info['original_usage_version'], unicode(original_usage_version))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestXmoduleRuntimeEvent(TestSubmittingProblems):
     """
     Inherit from TestSubmittingProblems to get functionality that set up a course and problems structure
@@ -1852,7 +1852,7 @@ class TestXmoduleRuntimeEvent(TestSubmittingProblems):
         send_mock.assert_called_with(**expected_signal_kwargs)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestRebindModule(TestSubmittingProblems):
     """
     Tests to verify the functionality of rebinding a module.
@@ -1929,7 +1929,7 @@ class TestRebindModule(TestSubmittingProblems):
         self.assertEqual(module.descriptor.scope_ids.user_id, user2.id)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestEventPublishing(ModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -1968,7 +1968,7 @@ class TestEventPublishing(ModuleStoreTestCase, LoginEnrollmentTestCase):
         mock_track_function.return_value.assert_called_once_with(event_type, event)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class LMSXBlockServiceBindingTest(SharedModuleStoreTestCase):
     """
@@ -2059,7 +2059,7 @@ BLOCK_TYPES = ['xblock', 'xmodule']
 USER_NUMBERS = range(2)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestFilteredChildren(SharedModuleStoreTestCase):
     """
@@ -2215,7 +2215,7 @@ class TestFilteredChildren(SharedModuleStoreTestCase):
         self.assertEquals(set(child_usage_ids), set(child.scope_ids.usage_id for child in block.get_children()))
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestDisabledXBlockTypes(ModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/courseware/tests/test_navigation.py b/lms/djangoapps/courseware/tests/test_navigation.py
index 919ecafaea1..53b883fb832 100644
--- a/lms/djangoapps/courseware/tests/test_navigation.py
+++ b/lms/djangoapps/courseware/tests/test_navigation.py
@@ -18,7 +18,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 from xmodule.modulestore.django import modulestore
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestNavigation(SharedModuleStoreTestCase, LoginEnrollmentTestCase, MilestonesTestCaseMixin):
     """
     Check that navigation state is saved properly.
diff --git a/lms/djangoapps/courseware/tests/test_password_history.py b/lms/djangoapps/courseware/tests/test_password_history.py
index 61ec39d3cee..ec5656e993e 100644
--- a/lms/djangoapps/courseware/tests/test_password_history.py
+++ b/lms/djangoapps/courseware/tests/test_password_history.py
@@ -21,7 +21,7 @@ from student.models import PasswordHistory
 from courseware.tests.helpers import LoginEnrollmentTestCase
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.dict("django.conf.settings.FEATURES", {'ADVANCED_SECURITY': True})
 class TestPasswordHistory(LoginEnrollmentTestCase):
     """
diff --git a/lms/djangoapps/courseware/tests/test_split_module.py b/lms/djangoapps/courseware/tests/test_split_module.py
index 6fbb0bb1532..83782e4f6b4 100644
--- a/lms/djangoapps/courseware/tests/test_split_module.py
+++ b/lms/djangoapps/courseware/tests/test_split_module.py
@@ -15,7 +15,7 @@ from xmodule.partitions.partitions import Group, UserPartition
 from openedx.core.djangoapps.user_api.tests.factories import UserCourseTagFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class SplitTestBase(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
     """
     Sets up a basic course and user for split test testing.
@@ -285,7 +285,7 @@ class TestVertSplitTestVert(SplitTestBase):
         ]
 
 
-@attr('shard_1')
+@attr(shard=1)
 class SplitTestPosition(SharedModuleStoreTestCase):
     """
     Check that we can change positions in a course with partitions defined
diff --git a/lms/djangoapps/courseware/tests/test_submitting_problems.py b/lms/djangoapps/courseware/tests/test_submitting_problems.py
index 7ad0859bb9c..0cb4b83d6e5 100644
--- a/lms/djangoapps/courseware/tests/test_submitting_problems.py
+++ b/lms/djangoapps/courseware/tests/test_submitting_problems.py
@@ -303,7 +303,7 @@ class TestSubmittingProblems(ModuleStoreTestCase, LoginEnrollmentTestCase, Probl
         return [s.earned for s in hw_section['scores']]
 
 
-@attr('shard_3')
+@attr(shard=3)
 class TestCourseGrader(TestSubmittingProblems):
     """
     Suite of tests for the course grader.
@@ -682,7 +682,7 @@ class TestCourseGrader(TestSubmittingProblems):
         self.assertEqual(req_status[0]["status"], 'satisfied')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ProblemWithUploadedFilesTest(TestSubmittingProblems):
     """Tests of problems with uploaded files."""
     # Tell Django to clean out all databases, not just default
@@ -737,7 +737,7 @@ class ProblemWithUploadedFilesTest(TestSubmittingProblems):
         self.assertItemsEqual(kwargs['files'].keys(), filenames.split())
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestPythonGradedResponse(TestSubmittingProblems):
     """
     Check that we can submit a schematic and custom response, and it answers properly.
@@ -988,7 +988,7 @@ class TestPythonGradedResponse(TestSubmittingProblems):
         self._check_ireset(name)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestConditionalContent(TestSubmittingProblems):
     """
     Check that conditional content works correctly with grading.
diff --git a/lms/djangoapps/courseware/tests/test_tabs.py b/lms/djangoapps/courseware/tests/test_tabs.py
index ce9aefbe79a..a818d540046 100644
--- a/lms/djangoapps/courseware/tests/test_tabs.py
+++ b/lms/djangoapps/courseware/tests/test_tabs.py
@@ -223,7 +223,7 @@ class TextbooksTestCase(TabTestCase):
         self.assertEquals(num_textbooks_found, self.num_textbooks)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class StaticTabDateTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
     """Test cases for Static Tab Dates."""
 
@@ -279,7 +279,7 @@ class StaticTabDateTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
             self.assertIn("this module is temporarily unavailable", static_tab)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
     """
     Tests for the static tab dates of an XML course
@@ -329,7 +329,7 @@ class StaticTabDateTestCaseXML(LoginEnrollmentTestCase, ModuleStoreTestCase):
         self.assertIn(self.xml_data, resp.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.dict('django.conf.settings.FEATURES', {'ENTRANCE_EXAMS': True, 'MILESTONES_APP': True})
 class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, MilestonesTestCaseMixin):
     """
@@ -437,7 +437,7 @@ class EntranceExamsTabsTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase, Mi
         self.assertEqual(len(course_tab_list), 5)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TextBookCourseViewsTestCase(LoginEnrollmentTestCase, SharedModuleStoreTestCase):
     """
     Validate tab behavior when dealing with textbooks.
@@ -562,7 +562,7 @@ class TabListTestCase(TabTestCase):
         self.all_valid_tab_list = xmodule_tabs.CourseTabList().from_json(self.valid_tabs[1])
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ValidateTabsTestCase(TabListTestCase):
     """Test cases for validating tabs."""
 
@@ -592,7 +592,7 @@ class ValidateTabsTestCase(TabListTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CourseTabListTestCase(TabListTestCase):
     """Testing the generator method for iterating through displayable tabs"""
 
@@ -687,7 +687,7 @@ class CourseTabListTestCase(TabListTestCase):
             self.assertEquals(xmodule_tabs.CourseTabList.get_tab_by_id(self.course.tabs, tab.tab_id), tab)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ProgressTestCase(TabTestCase):
     """Test cases for Progress Tab."""
 
@@ -717,7 +717,7 @@ class ProgressTestCase(TabTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class StaticTabTestCase(TabTestCase):
     """Test cases for Static Tab."""
 
@@ -736,7 +736,7 @@ class StaticTabTestCase(TabTestCase):
         self.check_get_and_set_method_for_key(tab, 'url_slug')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class DiscussionLinkTestCase(TabTestCase):
     """Test cases for discussion link tab."""
 
diff --git a/lms/djangoapps/courseware/tests/test_video_handlers.py b/lms/djangoapps/courseware/tests/test_video_handlers.py
index 7b0130dc5bb..33ae6d9d699 100644
--- a/lms/djangoapps/courseware/tests/test_video_handlers.py
+++ b/lms/djangoapps/courseware/tests/test_video_handlers.py
@@ -123,7 +123,7 @@ def attach_bumper_transcript(item, filename, lang="en"):
     item.video_bumper["transcripts"][lang] = filename
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestVideo(BaseTestXmodule):
     """Integration tests: web client + mongo."""
     CATEGORY = "video"
@@ -189,7 +189,7 @@ class TestVideo(BaseTestXmodule):
         super(TestVideo, self).tearDown()
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestTranscriptAvailableTranslationsDispatch(TestVideo):
     """
     Test video handler that provide available translations info.
@@ -249,7 +249,7 @@ class TestTranscriptAvailableTranslationsDispatch(TestVideo):
         self.assertEqual(json.loads(response.body), ['en', 'uk'])
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestTranscriptAvailableTranslationsBumperDispatch(TestVideo):
     """
@@ -372,7 +372,7 @@ class TestTranscriptDownloadDispatch(TestVideo):
         self.assertEqual(response.headers['Content-Disposition'], 'attachment; filename="å¡ž.srt"')
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestTranscriptTranslationGetDispatch(TestVideo):
     """
@@ -604,7 +604,7 @@ class TestTranscriptTranslationGetDispatch(TestVideo):
             store.update_item(self.course, self.user.id)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestStudioTranscriptTranslationGetDispatch(TestVideo):
     """
     Test Studio video handler that provide translation transcripts.
@@ -662,7 +662,7 @@ class TestStudioTranscriptTranslationGetDispatch(TestVideo):
         self.assertEqual(response.headers['Content-Language'], 'zh')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestStudioTranscriptTranslationPostDispatch(TestVideo):
     """
     Test Studio video handler that provide translation transcripts.
@@ -723,7 +723,7 @@ class TestStudioTranscriptTranslationPostDispatch(TestVideo):
         self.assertTrue(_check_asset(self.item_descriptor.location, u'filename.srt'))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetTranscript(TestVideo):
     """
     Make sure that `get_transcript` method works correctly
diff --git a/lms/djangoapps/courseware/tests/test_video_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py
index 13556b12aa1..6cfd2b98dc3 100644
--- a/lms/djangoapps/courseware/tests/test_video_mongo.py
+++ b/lms/djangoapps/courseware/tests/test_video_mongo.py
@@ -34,7 +34,7 @@ from .test_video_xml import SOURCE_XML
 from .test_video_handlers import TestVideo
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestVideoYouTube(TestVideo):
     METADATA = {}
 
@@ -96,7 +96,7 @@ class TestVideoYouTube(TestVideo):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestVideoNonYouTube(TestVideo):
     """Integration tests: web client + mongo."""
     DATA = """
@@ -175,7 +175,7 @@ class TestVideoNonYouTube(TestVideo):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetHtmlMethod(BaseTestXmodule):
     '''
     Make sure that `get_html` works correctly.
@@ -805,7 +805,7 @@ class TestGetHtmlMethod(BaseTestXmodule):
             )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestVideoCDNRewriting(BaseTestXmodule):
     """
     Tests for Video CDN.
@@ -862,7 +862,7 @@ class TestVideoCDNRewriting(BaseTestXmodule):
         self.assertIsNone(rewrite_video_url("", ""))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestVideoDescriptorInitialization(BaseTestXmodule):
     """
     Make sure that module initialization works correctly.
@@ -933,7 +933,7 @@ class TestVideoDescriptorInitialization(BaseTestXmodule):
         self.assertFalse(self.item_descriptor.download_video)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestEditorSavedMethod(BaseTestXmodule):
     """
@@ -1167,7 +1167,7 @@ class TestVideoDescriptorStudentViewJson(TestCase):
         self.verify_result_with_fallback_and_youtube(result)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class VideoDescriptorTest(TestCase, VideoDescriptorTestBase):
     """
     Tests for video descriptor that requires access to django settings.
diff --git a/lms/djangoapps/courseware/tests/test_video_xml.py b/lms/djangoapps/courseware/tests/test_video_xml.py
index e7c5fafeeb6..bc65d5935e6 100644
--- a/lms/djangoapps/courseware/tests/test_video_xml.py
+++ b/lms/djangoapps/courseware/tests/test_video_xml.py
@@ -35,7 +35,7 @@ SOURCE_XML = """
 """
 
 
-@attr('shard_1')
+@attr(shard=1)
 class VideoModuleLogicTest(LogicTest):
     """Tests for logic of Video Xmodule."""
 
diff --git a/lms/djangoapps/courseware/tests/test_view_authentication.py b/lms/djangoapps/courseware/tests/test_view_authentication.py
index 5eedc017d0f..48dd842a743 100644
--- a/lms/djangoapps/courseware/tests/test_view_authentication.py
+++ b/lms/djangoapps/courseware/tests/test_view_authentication.py
@@ -21,7 +21,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 from student.tests.factories import UserFactory, CourseEnrollmentFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
     """
     Check that view authentication works properly.
@@ -390,7 +390,7 @@ class TestViewAuth(ModuleStoreTestCase, LoginEnrollmentTestCase):
         self.assertTrue(self.enroll(self.course))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestBetatesterAccess(ModuleStoreTestCase, CourseAccessTestMixin):
     """
     Tests for the beta tester feature
diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index c4c8c913fe0..155db2aac18 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -64,7 +64,7 @@ from openedx.core.djangoapps.credit.api import set_credit_requirements
 from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestJumpTo(ModuleStoreTestCase):
     """
     Check the jumpto link for a course.
@@ -189,7 +189,7 @@ class TestJumpTo(ModuleStoreTestCase):
         self.assertEqual(response.status_code, 404)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class ViewsTestCase(ModuleStoreTestCase, MilestonesTestCaseMixin):
     """
@@ -936,7 +936,7 @@ class ViewsTestCase(ModuleStoreTestCase, MilestonesTestCaseMixin):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 # setting TIME_ZONE_DISPLAYED_FOR_DEADLINES explicitly
 @override_settings(TIME_ZONE_DISPLAYED_FOR_DEADLINES="UTC")
 class BaseDueDateTests(ModuleStoreTestCase, MilestonesTestCaseMixin):
@@ -1052,7 +1052,7 @@ class TestAccordionDueDate(BaseDueDateTests):
         return self.client.get(reverse('courseware', args=[unicode(course.id)]), follow=True)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class StartDateTests(ModuleStoreTestCase):
     """
     Test that start dates are properly localized and displayed on the student
@@ -1105,7 +1105,7 @@ class StartDateTests(ModuleStoreTestCase):
 
 
 # pylint: disable=protected-access, no-member
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class ProgressPageTests(ModuleStoreTestCase):
     """
@@ -1591,7 +1591,7 @@ class ProgressPageTests(ModuleStoreTestCase):
         }
 
 
-@attr('shard_1')
+@attr(shard=1)
 class VerifyCourseKeyDecoratorTests(TestCase):
     """
     Tests for the ensure_valid_course_key decorator.
@@ -1617,7 +1617,7 @@ class VerifyCourseKeyDecoratorTests(TestCase):
         self.assertFalse(mocked_view.called)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class IsCoursePassedTests(ModuleStoreTestCase):
     """
     Tests for the is_course_passed helper function
@@ -1662,7 +1662,7 @@ class IsCoursePassedTests(ModuleStoreTestCase):
         self.assertTrue(views.is_course_passed(self.course, None, self.student, self.request))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class GenerateUserCertTests(ModuleStoreTestCase):
     """
     Tests for the view function Generated User Certs
@@ -1814,7 +1814,7 @@ class ViewCheckerBlock(XBlock):
         return result
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestIndexView(ModuleStoreTestCase, MilestonesTestCaseMixin):
     """
diff --git a/lms/djangoapps/courseware/tests/test_word_cloud.py b/lms/djangoapps/courseware/tests/test_word_cloud.py
index 5fbed4bf12e..fba6b6ecec6 100644
--- a/lms/djangoapps/courseware/tests/test_word_cloud.py
+++ b/lms/djangoapps/courseware/tests/test_word_cloud.py
@@ -9,7 +9,7 @@ from . import BaseTestXmodule
 from xmodule.x_module import STUDENT_VIEW
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestWordCloud(BaseTestXmodule):
     """Integration test for word cloud xmodule."""
     CATEGORY = "word_cloud"
diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py
index d376896dcb7..6d7727decb5 100644
--- a/lms/djangoapps/courseware/tests/tests.py
+++ b/lms/djangoapps/courseware/tests/tests.py
@@ -19,7 +19,7 @@ from xmodule.modulestore.tests.django_utils import (
 from xmodule.modulestore.tests.factories import ToyCourseFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ActivateLoginTest(LoginEnrollmentTestCase):
     """
     Test logging in and logging out.
@@ -123,7 +123,7 @@ class PageLoaderTestCase(LoginEnrollmentTestCase):
             self.assertNotIsInstance(descriptor, ErrorDescriptor)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestMongoCoursesLoad(ModuleStoreTestCase, PageLoaderTestCase):
     """
     Check that all pages in test courses load properly from Mongo.
@@ -147,7 +147,7 @@ class TestMongoCoursesLoad(ModuleStoreTestCase, PageLoaderTestCase):
         self.assertGreater(len(course.textbooks), 0)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestDraftModuleStore(ModuleStoreTestCase):
     def test_get_items_with_course_items(self):
         store = modulestore()
@@ -160,7 +160,7 @@ class TestDraftModuleStore(ModuleStoreTestCase):
         # not allowed to be passed in (i.e. was throwing exception)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestLmsFieldData(TestCase):
     """
     Tests of the LmsFieldData class
diff --git a/lms/djangoapps/coursewarehistoryextended/tests.py b/lms/djangoapps/coursewarehistoryextended/tests.py
index 759fdfe4be2..114c23794f2 100644
--- a/lms/djangoapps/coursewarehistoryextended/tests.py
+++ b/lms/djangoapps/coursewarehistoryextended/tests.py
@@ -17,7 +17,7 @@ from courseware.models import BaseStudentModuleHistory, StudentModuleHistory, St
 from courseware.tests.factories import StudentModuleFactory, location, course_id
 
 
-@attr('shard_1')
+@attr(shard=1)
 @skipUnless(settings.FEATURES["ENABLE_CSMH_EXTENDED"], "CSMH Extended needs to be enabled")
 class TestStudentModuleHistoryBackends(TestCase):
     """ Tests of data in CSMH and CSMHE """
diff --git a/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py b/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py
index dedf9217f8e..1d87418d5c2 100644
--- a/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py
+++ b/lms/djangoapps/dashboard/management/commands/tests/test_git_add_course.py
@@ -43,7 +43,7 @@ FEATURES_WITH_SSL_AUTH = settings.FEATURES.copy()
 FEATURES_WITH_SSL_AUTH['AUTH_USE_CERTIFICATES'] = True
 
 
-@attr('shard_3')
+@attr(shard=3)
 @override_settings(
     MONGODB_LOG=TEST_MONGODB_LOG,
     GIT_REPO_DIR=settings.TEST_ROOT / "course_repos_{}".format(uuid4().hex)
diff --git a/lms/djangoapps/dashboard/tests/test_sysadmin.py b/lms/djangoapps/dashboard/tests/test_sysadmin.py
index 6dee0935652..c05f38add6e 100644
--- a/lms/djangoapps/dashboard/tests/test_sysadmin.py
+++ b/lms/djangoapps/dashboard/tests/test_sysadmin.py
@@ -109,7 +109,7 @@ class SysadminBaseTestCase(SharedModuleStoreTestCase):
         self.addCleanup(shutil.rmtree, path)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(
     MONGODB_LOG=TEST_MONGODB_LOG,
     GIT_REPO_DIR=settings.TEST_ROOT / "course_repos_{}".format(uuid4().hex)
diff --git a/lms/djangoapps/discussion_api/tests/test_api.py b/lms/djangoapps/discussion_api/tests/test_api.py
index 323b02dccdd..b0d117d3555 100644
--- a/lms/djangoapps/discussion_api/tests/test_api.py
+++ b/lms/djangoapps/discussion_api/tests/test_api.py
@@ -83,7 +83,7 @@ def _discussion_disabled_course_for(user):
     return course_with_disabled_forums
 
 
-@attr('shard_2')
+@attr(shard=2)
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
 class GetCourseTest(UrlResetMixin, SharedModuleStoreTestCase):
     """Test for get_course"""
@@ -130,7 +130,7 @@ class GetCourseTest(UrlResetMixin, SharedModuleStoreTestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
 class GetCourseTestBlackouts(UrlResetMixin, ModuleStoreTestCase):
@@ -174,7 +174,7 @@ class GetCourseTestBlackouts(UrlResetMixin, ModuleStoreTestCase):
         self.assertEqual(result["blackouts"], [])
 
 
-@attr('shard_2')
+@attr(shard=2)
 @mock.patch.dict("django.conf.settings.FEATURES", {"DISABLE_START_DATES": False})
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
 class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase):
@@ -548,7 +548,7 @@ class GetCourseTopicsTest(UrlResetMixin, ModuleStoreTestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
 class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleStoreTestCase):
@@ -998,7 +998,7 @@ class GetThreadListTest(CommentsServiceMockMixin, UrlResetMixin, SharedModuleSto
         })
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
 class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
@@ -1430,7 +1430,7 @@ class GetCommentListTest(CommentsServiceMockMixin, SharedModuleStoreTestCase):
             self.get_comment_list(thread, endorsed=True, page=2, page_size=10)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @disable_signal(api, 'thread_created')
 @disable_signal(api, 'thread_voted')
@@ -1682,7 +1682,7 @@ class CreateThreadTest(
             create_thread(self.request, data)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @disable_signal(api, 'comment_created')
 @disable_signal(api, 'comment_voted')
@@ -1950,7 +1950,7 @@ class CreateCommentTest(
             create_comment(self.request, data)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @disable_signal(api, 'thread_edited')
 @disable_signal(api, 'thread_voted')
@@ -2358,7 +2358,7 @@ class UpdateThreadTest(
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @disable_signal(api, 'comment_edited')
 @disable_signal(api, 'comment_voted')
@@ -2763,7 +2763,7 @@ class UpdateCommentTest(
             )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @disable_signal(api, 'thread_deleted')
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
@@ -2903,7 +2903,7 @@ class DeleteThreadTest(
             self.assertTrue(expected_error)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @disable_signal(api, 'comment_deleted')
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
@@ -3062,7 +3062,7 @@ class DeleteCommentTest(
             self.assertTrue(expected_error)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
 class RetrieveThreadTest(
diff --git a/lms/djangoapps/discussion_api/tests/test_permissions.py b/lms/djangoapps/discussion_api/tests/test_permissions.py
index f631a588b3b..86aa8f1f463 100644
--- a/lms/djangoapps/discussion_api/tests/test_permissions.py
+++ b/lms/djangoapps/discussion_api/tests/test_permissions.py
@@ -65,7 +65,7 @@ class GetInitializableFieldsTest(ModuleStoreTestCase):
         self.assertEqual(actual, expected)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class GetEditableFieldsTest(ModuleStoreTestCase):
     """Tests for get_editable_fields"""
diff --git a/lms/djangoapps/discussion_api/tests/test_serializers.py b/lms/djangoapps/discussion_api/tests/test_serializers.py
index 6e269e26380..a979037e974 100644
--- a/lms/djangoapps/discussion_api/tests/test_serializers.py
+++ b/lms/djangoapps/discussion_api/tests/test_serializers.py
@@ -133,7 +133,7 @@ class SerializerTestMixin(CommentsServiceMockMixin, UrlResetMixin):
         self.assertEqual(serialized["voted"], True)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class ThreadSerializerSerializationTest(SerializerTestMixin, SharedModuleStoreTestCase):
     """Tests for ThreadSerializer serialization."""
diff --git a/lms/djangoapps/discussion_api/tests/test_views.py b/lms/djangoapps/discussion_api/tests/test_views.py
index 4b25ae364a8..c67e0af465d 100644
--- a/lms/djangoapps/discussion_api/tests/test_views.py
+++ b/lms/djangoapps/discussion_api/tests/test_views.py
@@ -306,7 +306,7 @@ class CourseTopicsViewTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
         )
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @httpretty.activate
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
@@ -790,7 +790,7 @@ class ThreadViewSetCreateTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
         self.assertEqual(response_data, expected_response_data)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @httpretty.activate
 @disable_signal(api, 'thread_edited')
@@ -1006,7 +1006,7 @@ class ThreadViewSetDeleteTest(DiscussionAPIViewTestMixin, ModuleStoreTestCase):
         self.assertEqual(response.status_code, 404)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @httpretty.activate
 @mock.patch.dict("django.conf.settings.FEATURES", {"ENABLE_DISCUSSION_SERVICE": True})
diff --git a/lms/djangoapps/django_comment_client/base/tests.py b/lms/djangoapps/django_comment_client/base/tests.py
index 1d9d41fabe4..03f635838d8 100644
--- a/lms/djangoapps/django_comment_client/base/tests.py
+++ b/lms/djangoapps/django_comment_client/base/tests.py
@@ -47,7 +47,7 @@ class MockRequestSetupMixin(object):
         mock_request.return_value = self._create_response_mock(data)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch('lms.lib.comment_client.utils.requests.request', autospec=True)
 class CreateThreadGroupIdTestCase(
         MockRequestSetupMixin,
@@ -83,7 +83,7 @@ class CreateThreadGroupIdTestCase(
         self._assert_json_response_contains_group_info(response)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch('lms.lib.comment_client.utils.requests.request', autospec=True)
 @disable_signal(views, 'thread_edited')
 @disable_signal(views, 'thread_voted')
@@ -341,7 +341,7 @@ class ViewsTestCaseMixin(object):
         self.assertEqual(data['commentable_id'], 'some_topic')
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @patch('lms.lib.comment_client.utils.requests.request', autospec=True)
 @disable_signal(views, 'thread_created')
@@ -388,7 +388,7 @@ class ViewsQueryCountTestCase(UrlResetMixin, ModuleStoreTestCase, MockRequestSet
         self.update_thread_helper(mock_request)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @patch('lms.lib.comment_client.utils.requests.request', autospec=True)
 class ViewsTestCase(
@@ -1016,7 +1016,7 @@ class ViewsTestCase(
         self.assertEqual(response.status_code, 200)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch("lms.lib.comment_client.utils.requests.request", autospec=True)
 @disable_signal(views, 'comment_endorsed')
 class ViewPermissionsTestCase(UrlResetMixin, SharedModuleStoreTestCase, MockRequestSetupMixin):
@@ -1126,7 +1126,7 @@ class ViewPermissionsTestCase(UrlResetMixin, SharedModuleStoreTestCase, MockRequ
         self.assertEqual(response.status_code, 200)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CreateThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
 
     @classmethod
@@ -1162,7 +1162,7 @@ class CreateThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, M
         self.assertEqual(mock_request.call_args[1]["data"]["title"], text)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @disable_signal(views, 'thread_edited')
 class UpdateThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
 
@@ -1200,7 +1200,7 @@ class UpdateThreadUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, M
         self.assertEqual(mock_request.call_args[1]["data"]["commentable_id"], "test_commentable")
 
 
-@attr('shard_2')
+@attr(shard=2)
 @disable_signal(views, 'comment_created')
 class CreateCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
 
@@ -1243,7 +1243,7 @@ class CreateCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin,
             del Thread.commentable_id
 
 
-@attr('shard_2')
+@attr(shard=2)
 @disable_signal(views, 'comment_edited')
 class UpdateCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
 
@@ -1277,7 +1277,7 @@ class UpdateCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin,
         self.assertEqual(mock_request.call_args[1]["data"]["body"], text)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @disable_signal(views, 'comment_created')
 class CreateSubCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixin, MockRequestSetupMixin):
     """
@@ -1324,7 +1324,7 @@ class CreateSubCommentUnicodeTestCase(SharedModuleStoreTestCase, UnicodeTestMixi
             del Thread.commentable_id
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @patch("lms.lib.comment_client.utils.requests.request", autospec=True)
 @disable_signal(views, 'thread_voted')
@@ -1596,7 +1596,7 @@ class TeamsPermissionsTestCase(UrlResetMixin, SharedModuleStoreTestCase, MockReq
 TEAM_COMMENTABLE_ID = 'test-team-discussion'
 
 
-@attr('shard_2')
+@attr(shard=2)
 @disable_signal(views, 'comment_created')
 @ddt.ddt
 class ForumEventTestCase(SharedModuleStoreTestCase, MockRequestSetupMixin):
@@ -1782,7 +1782,7 @@ class ForumEventTestCase(SharedModuleStoreTestCase, MockRequestSetupMixin):
         self.assertEqual(event['vote_value'], 'up')
 
 
-@attr('shard_2')
+@attr(shard=2)
 class UsersEndpointTestCase(SharedModuleStoreTestCase, MockRequestSetupMixin):
 
     @classmethod
diff --git a/lms/djangoapps/django_comment_client/tests/test_middleware.py b/lms/djangoapps/django_comment_client/tests/test_middleware.py
index 1c7becaadc5..8b223eaa1b4 100644
--- a/lms/djangoapps/django_comment_client/tests/test_middleware.py
+++ b/lms/djangoapps/django_comment_client/tests/test_middleware.py
@@ -7,7 +7,7 @@ import lms.lib.comment_client
 import django_comment_client.middleware as middleware
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AjaxExceptionTestCase(TestCase):
     def setUp(self):
         super(AjaxExceptionTestCase, self).setUp()
diff --git a/lms/djangoapps/django_comment_client/tests/test_models.py b/lms/djangoapps/django_comment_client/tests/test_models.py
index 5429e686611..bb59e6a7c7f 100644
--- a/lms/djangoapps/django_comment_client/tests/test_models.py
+++ b/lms/djangoapps/django_comment_client/tests/test_models.py
@@ -12,7 +12,7 @@ from xmodule.modulestore.tests.django_utils import (
 from xmodule.modulestore.tests.factories import ToyCourseFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class RoleClassTestCase(ModuleStoreTestCase):
     """
     Tests for roles of the comment client service integration
@@ -54,7 +54,7 @@ class RoleClassTestCase(ModuleStoreTestCase):
         self.TA_role_2.inherit_permissions(self.TA_role)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class PermissionClassTestCase(TestCase):
     """
     Tests for permissions of the comment client service integration
diff --git a/lms/djangoapps/django_comment_client/tests/test_utils.py b/lms/djangoapps/django_comment_client/tests/test_utils.py
index ea72e272a47..984b76de75e 100644
--- a/lms/djangoapps/django_comment_client/tests/test_utils.py
+++ b/lms/djangoapps/django_comment_client/tests/test_utils.py
@@ -31,7 +31,7 @@ from xmodule.modulestore.django import modulestore
 from lms.djangoapps.teams.tests.factories import CourseTeamFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class DictionaryTestCase(TestCase):
     def test_extract(self):
         d = {'cats': 'meow', 'dogs': 'woof'}
@@ -56,7 +56,7 @@ class DictionaryTestCase(TestCase):
         self.assertEqual(utils.merge_dict(d1, d2), expected)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class AccessUtilsTestCase(ModuleStoreTestCase):
     """
     Base testcase class for access and roles for the
@@ -112,7 +112,7 @@ class AccessUtilsTestCase(ModuleStoreTestCase):
 
 
 @ddt.ddt
-@attr('shard_1')
+@attr(shard=1)
 class CoursewareContextTestCase(ModuleStoreTestCase):
     """
     Base testcase class for courseware context for the
@@ -201,7 +201,7 @@ class CoursewareContextTestCase(ModuleStoreTestCase):
         self.assertEqual(len(utils.get_accessible_discussion_xblocks(course, self.user)), expected_discussion_xblocks)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class CachedDiscussionIdMapTestCase(ModuleStoreTestCase):
     """
     Tests that using the cache of discussion id mappings has the same behavior as searching through the course.
@@ -334,7 +334,7 @@ class CategoryMapTestMixin(object):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
     """
     Base testcase class for discussion categories for the
@@ -1022,7 +1022,7 @@ class CategoryMapTestCase(CategoryMapTestMixin, ModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class ContentGroupCategoryMapTestCase(CategoryMapTestMixin, ContentGroupTestCase):
     """
     Tests `get_discussion_category_map` on discussion xblocks which are
@@ -1196,7 +1196,7 @@ class JsonResponseTestCase(TestCase, UnicodeTestMixin):
         self.assertEqual(reparsed, text)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class RenderMustacheTests(TestCase):
     """
     Test the `render_mustache` utility function.
diff --git a/lms/djangoapps/edxnotes/tests.py b/lms/djangoapps/edxnotes/tests.py
index 803daec2086..24943615c26 100644
--- a/lms/djangoapps/edxnotes/tests.py
+++ b/lms/djangoapps/edxnotes/tests.py
@@ -87,7 +87,7 @@ class TestProblem(object):
         return "original_get_html"
 
 
-@attr('shard_3')
+@attr(shard=3)
 @skipUnless(settings.FEATURES["ENABLE_EDXNOTES"], "EdxNotes feature needs to be enabled.")
 class EdxNotesDecoratorTest(ModuleStoreTestCase):
     """
@@ -171,7 +171,7 @@ class EdxNotesDecoratorTest(ModuleStoreTestCase):
         self.assertEqual("original_get_html", self.problem.get_html())
 
 
-@attr('shard_3')
+@attr(shard=3)
 @skipUnless(settings.FEATURES["ENABLE_EDXNOTES"], "EdxNotes feature needs to be enabled.")
 @ddt.ddt
 class EdxNotesHelpersTest(ModuleStoreTestCase):
@@ -944,7 +944,7 @@ class EdxNotesHelpersTest(ModuleStoreTestCase):
         verify_url(previous_url, previous_api_url)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @skipUnless(settings.FEATURES["ENABLE_EDXNOTES"], "EdxNotes feature needs to be enabled.")
 @ddt.ddt
 class EdxNotesViewsTest(ModuleStoreTestCase):
@@ -1140,7 +1140,7 @@ class EdxNotesViewsTest(ModuleStoreTestCase):
         self.assertEqual(response.status_code, 400)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @skipUnless(settings.FEATURES["ENABLE_EDXNOTES"], "EdxNotes feature needs to be enabled.")
 @ddt.ddt
 class EdxNotesPluginTest(ModuleStoreTestCase):
diff --git a/lms/djangoapps/gating/tests/test_api.py b/lms/djangoapps/gating/tests/test_api.py
index 92716becad8..315bc1f0bd3 100644
--- a/lms/djangoapps/gating/tests/test_api.py
+++ b/lms/djangoapps/gating/tests/test_api.py
@@ -115,7 +115,7 @@ class TestGetXBlockParent(GatingTestCase):
         self.assertIsNone(result)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt
 class TestEvaluatePrerequisite(GatingTestCase, MilestonesTestCaseMixin):
     """
diff --git a/lms/djangoapps/grades/tests/test_grades.py b/lms/djangoapps/grades/tests/test_grades.py
index 162c7f55f09..69d18ab1d5e 100644
--- a/lms/djangoapps/grades/tests/test_grades.py
+++ b/lms/djangoapps/grades/tests/test_grades.py
@@ -41,7 +41,7 @@ def _grade_with_errors(student, course, keep_raw_scores=False):
     return grades_summary(student, course, keep_raw_scores=keep_raw_scores)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGradeIteration(SharedModuleStoreTestCase):
     """
     Test iteration through student gradesets.
diff --git a/lms/djangoapps/instructor/tests/test_access.py b/lms/djangoapps/instructor/tests/test_access.py
index 3ee1a427d10..263745f0cdb 100644
--- a/lms/djangoapps/instructor/tests/test_access.py
+++ b/lms/djangoapps/instructor/tests/test_access.py
@@ -18,7 +18,7 @@ from instructor.access import (allow_access,
                                update_forum_role)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAccessList(SharedModuleStoreTestCase):
     """ Test access listings. """
     @classmethod
@@ -44,7 +44,7 @@ class TestInstructorAccessList(SharedModuleStoreTestCase):
         self.assertEqual(set(beta_testers), set(self.beta_testers))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAccessAllow(SharedModuleStoreTestCase):
     """ Test access allow. """
     @classmethod
@@ -90,7 +90,7 @@ class TestInstructorAccessAllow(SharedModuleStoreTestCase):
         allow_access(self.course, user, 'staff')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAccessRevoke(SharedModuleStoreTestCase):
     """ Test access revoke. """
     @classmethod
@@ -128,7 +128,7 @@ class TestInstructorAccessRevoke(SharedModuleStoreTestCase):
         revoke_access(self.course, user, 'robot-not-a-level')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAccessForum(SharedModuleStoreTestCase):
     """
     Test forum access control.
diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index c47fd50330c..79bbfe7ba5f 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -227,7 +227,7 @@ def view_alreadyrunningerror(request):  # pylint: disable=unused-argument
     raise AlreadyRunningError()
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestCommonExceptions400(TestCase):
     """
     Testing the common_exceptions_400 decorator.
@@ -269,7 +269,7 @@ class TestCommonExceptions400(TestCase):
         self.assertIn("Task is already running", result["error"])
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestEndpointHttpMethods(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -324,7 +324,7 @@ class TestEndpointHttpMethods(SharedModuleStoreTestCase, LoginEnrollmentTestCase
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
 class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -529,7 +529,7 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest
             )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.dict(settings.FEATURES, {'ALLOW_AUTOMATED_SIGNUPS': True})
 class TestInstructorAPIBulkAccountCreationAndEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -942,7 +942,7 @@ class TestInstructorAPIBulkAccountCreationAndEnrollment(SharedModuleStoreTestCas
             self.assertEqual(enrollment.enrollment.mode, CourseMode.DEFAULT_SHOPPINGCART_MODE_SLUG)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -1698,7 +1698,7 @@ class TestInstructorAPIEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTest
         return response
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestInstructorAPIBulkBetaEnrollment(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -2022,7 +2022,7 @@ class TestInstructorAPIBulkBetaEnrollment(SharedModuleStoreTestCase, LoginEnroll
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
     Test endpoints whereby instructors can change permissions
@@ -2264,7 +2264,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe
             self.assertNotIn(rolename, user_roles)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 @patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
 class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
@@ -3111,7 +3111,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment
         self.assertEqual(response.status_code, 400)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
     Test endpoints whereby instructors can change student grades.
@@ -3274,7 +3274,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes
         self.assertEqual(response.status_code, 400)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.dict(settings.FEATURES, {'ENTRANCE_EXAMS': True})
 @ddt.ddt
 class TestEntranceExamInstructorAPIRegradeTask(
@@ -3551,7 +3551,7 @@ class TestEntranceExamInstructorAPIRegradeTask(
         self.assertContains(response, message)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
 class TestInstructorSendEmail(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -3653,7 +3653,7 @@ class MockCompletionInfo(object):
         return False, 'Task Errored In Some Way'
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
     Test instructor task list endpoint.
@@ -3815,7 +3815,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC
         self.assertEqual(actual_tasks, expected_tasks)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.object(instructor_task.api, 'get_instructor_task_history', autospec=True)
 class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
@@ -3949,7 +3949,7 @@ class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentT
         self.assertDictEqual(expected_info, returned_info)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAPIHelpers(TestCase):
     """ Test helpers for instructor.api """
 
@@ -4003,7 +4003,7 @@ def get_extended_due(course, unit, user):
         return None
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
     """
     Test data dumps for reporting.
@@ -4172,7 +4172,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
                 self.user1.profile.name, self.user1.username)})
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestDueDateExtensionsDeletedDate(ModuleStoreTestCase, LoginEnrollmentTestCase):
     def setUp(self):
         """
@@ -4280,7 +4280,7 @@ class TestDueDateExtensionsDeletedDate(ModuleStoreTestCase, LoginEnrollmentTestC
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase):
     """
     Test data dumps for issued certificates.
@@ -4391,7 +4391,7 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(REGISTRATION_CODE_LENGTH=8)
 class TestCourseRegistrationCodes(SharedModuleStoreTestCase):
     """
@@ -4854,7 +4854,7 @@ class TestCourseRegistrationCodes(SharedModuleStoreTestCase):
         self.assertTrue(body.startswith(EXPECTED_COUPON_CSV_HEADER))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestBulkCohorting(SharedModuleStoreTestCase):
     """
     Test adding users to cohorts in bulk via CSV upload.
diff --git a/lms/djangoapps/instructor/tests/test_api_email_localization.py b/lms/djangoapps/instructor/tests/test_api_email_localization.py
index a702820dac7..5406374f7e3 100644
--- a/lms/djangoapps/instructor/tests/test_api_email_localization.py
+++ b/lms/djangoapps/instructor/tests/test_api_email_localization.py
@@ -17,7 +17,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
 from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorAPIEnrollmentEmailLocalization(SharedModuleStoreTestCase):
     """
     Test whether the enroll, unenroll and beta role emails are sent in the
diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py
index b8e8e0317c7..530947a210c 100644
--- a/lms/djangoapps/instructor/tests/test_certificates.py
+++ b/lms/djangoapps/instructor/tests/test_certificates.py
@@ -23,7 +23,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
 import io
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CertificatesInstructorDashTest(SharedModuleStoreTestCase):
     """Tests for the certificate panel of the instructor dash. """
@@ -197,7 +197,7 @@ class CertificatesInstructorDashTest(SharedModuleStoreTestCase):
         self.assertContains(response, expected_html)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(CERT_QUEUE='certificates')
 @ddt.ddt
 class CertificatesInstructorApiTest(SharedModuleStoreTestCase):
@@ -387,7 +387,7 @@ class CertificatesInstructorApiTest(SharedModuleStoreTestCase):
         self.assertEqual(res_json['message'], u'Please select certificate statuses from the list only.')
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(CERT_QUEUE='certificates')
 @ddt.ddt
 class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase):
@@ -674,7 +674,7 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(CERT_QUEUE='certificates')
 @ddt.ddt
 class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase):
@@ -792,7 +792,7 @@ class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase):
         )
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class TestCertificatesInstructorApiBulkWhiteListExceptions(SharedModuleStoreTestCase):
     """
@@ -950,7 +950,7 @@ class TestCertificatesInstructorApiBulkWhiteListExceptions(SharedModuleStoreTest
         return data
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt.ddt
 class CertificateInvalidationViewTests(SharedModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/instructor/tests/test_ecommerce.py b/lms/djangoapps/instructor/tests/test_ecommerce.py
index 95db6658643..a69b1d849e5 100644
--- a/lms/djangoapps/instructor/tests/test_ecommerce.py
+++ b/lms/djangoapps/instructor/tests/test_ecommerce.py
@@ -17,7 +17,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestECommerceDashboardViews(SharedModuleStoreTestCase):
     """
     Check for E-commerce view on the new instructor dashboard
diff --git a/lms/djangoapps/instructor/tests/test_email.py b/lms/djangoapps/instructor/tests/test_email.py
index 89293a0a39a..4297b241e82 100644
--- a/lms/djangoapps/instructor/tests/test_email.py
+++ b/lms/djangoapps/instructor/tests/test_email.py
@@ -17,7 +17,7 @@ from student.tests.factories import AdminFactory
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestNewInstructorDashboardEmailViewMongoBacked(SharedModuleStoreTestCase):
     """
     Check for email view on the new instructor dashboard
@@ -110,7 +110,7 @@ class TestNewInstructorDashboardEmailViewMongoBacked(SharedModuleStoreTestCase):
         self.assertNotIn(self.email_link, response.content)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestNewInstructorDashboardEmailViewXMLBacked(SharedModuleStoreTestCase):
     """
     Check for email view on the new instructor dashboard
diff --git a/lms/djangoapps/instructor/tests/test_enrollment.py b/lms/djangoapps/instructor/tests/test_enrollment.py
index 02b7348d6c8..f339235fbda 100644
--- a/lms/djangoapps/instructor/tests/test_enrollment.py
+++ b/lms/djangoapps/instructor/tests/test_enrollment.py
@@ -39,7 +39,7 @@ from student.models import anonymous_id_for_user
 from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestSettableEnrollmentState(TestCase):
     """ Test the basis class for enrollment tests. """
     def setUp(self):
@@ -103,7 +103,7 @@ class TestEnrollmentChangeBase(TestCase):
         self.assertEqual(after, after_ideal)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorEnrollDB(TestEnrollmentChangeBase):
     """ Test instructor.enrollment.enroll_email """
     def test_enroll(self):
@@ -221,7 +221,7 @@ class TestInstructorEnrollDB(TestEnrollmentChangeBase):
         return self._run_state_change_test(before_ideal, after_ideal, action)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorUnenrollDB(TestEnrollmentChangeBase):
     """ Test instructor.enrollment.unenroll_email """
     def test_unenroll(self):
@@ -301,7 +301,7 @@ class TestInstructorUnenrollDB(TestEnrollmentChangeBase):
         return self._run_state_change_test(before_ideal, after_ideal, action)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestInstructorEnrollmentStudentModule(SharedModuleStoreTestCase):
     """ Test student module manipulations. """
     @classmethod
@@ -564,7 +564,7 @@ class SettableEnrollmentState(EmailEnrollmentState):
             return EnrollmentObjects(email, None, None, None)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestSendBetaRoleEmail(TestCase):
     """
     Test edge cases for `send_beta_role_email`
@@ -582,7 +582,7 @@ class TestSendBetaRoleEmail(TestCase):
             send_beta_role_email(bad_action, self.user, self.email_params)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetEmailParamsCCX(SharedModuleStoreTestCase):
     """
     Test what URLs the function get_email_params for CCX student enrollment.
@@ -631,7 +631,7 @@ class TestGetEmailParamsCCX(SharedModuleStoreTestCase):
         self.assertEqual(result['course_url'], self.course_url)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetEmailParams(SharedModuleStoreTestCase):
     """
     Test what URLs the function get_email_params returns under different
@@ -677,7 +677,7 @@ class TestGetEmailParams(SharedModuleStoreTestCase):
         self.assertEqual(result['course_url'], self.course_url)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestRenderMessageToString(SharedModuleStoreTestCase):
     """
     Test that email templates can be rendered in a language chosen manually.
diff --git a/lms/djangoapps/instructor/tests/test_proctoring.py b/lms/djangoapps/instructor/tests/test_proctoring.py
index ce6057efc51..22d5bb363ad 100644
--- a/lms/djangoapps/instructor/tests/test_proctoring.py
+++ b/lms/djangoapps/instructor/tests/test_proctoring.py
@@ -14,7 +14,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_1')
+@attr(shard=1)
 @patch.dict(settings.FEATURES, {'ENABLE_SPECIAL_EXAMS': True})
 class TestProctoringDashboardViews(SharedModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/instructor/tests/test_registration_codes.py b/lms/djangoapps/instructor/tests/test_registration_codes.py
index e6f250d6333..497f1966ff3 100644
--- a/lms/djangoapps/instructor/tests/test_registration_codes.py
+++ b/lms/djangoapps/instructor/tests/test_registration_codes.py
@@ -19,7 +19,7 @@ from django.test.utils import override_settings
 from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(REGISTRATION_CODE_LENGTH=8)
 class TestCourseRegistrationCodeStatus(SharedModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/instructor/tests/test_services.py b/lms/djangoapps/instructor/tests/test_services.py
index f9780d92b38..2cf59a663ed 100644
--- a/lms/djangoapps/instructor/tests/test_services.py
+++ b/lms/djangoapps/instructor/tests/test_services.py
@@ -15,7 +15,7 @@ from student.tests.factories import UserFactory
 import mock
 
 
-@attr('shard_1')
+@attr(shard=1)
 class InstructorServiceTests(SharedModuleStoreTestCase):
     """
     Tests for the InstructorService
diff --git a/lms/djangoapps/instructor/tests/test_spoc_gradebook.py b/lms/djangoapps/instructor/tests/test_spoc_gradebook.py
index 7f930ea7d28..07fe049924f 100644
--- a/lms/djangoapps/instructor/tests/test_spoc_gradebook.py
+++ b/lms/djangoapps/instructor/tests/test_spoc_gradebook.py
@@ -14,7 +14,7 @@ from courseware.tests.factories import StudentModuleFactory
 USER_COUNT = 11
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGradebook(SharedModuleStoreTestCase):
     """
     Test functionality of the spoc gradebook. Sets up a course with assignments and
@@ -82,7 +82,7 @@ class TestGradebook(SharedModuleStoreTestCase):
         self.assertEquals(self.response.status_code, 200)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestDefaultGradingPolicy(TestGradebook):
     """
     Tests that the grading policy is properly applied for all users in the course
@@ -108,7 +108,7 @@ class TestDefaultGradingPolicy(TestGradebook):
         self.assertEquals(293, self.response.content.count('grade_None'))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestLetterCutoffPolicy(TestGradebook):
     """
     Tests advanced grading policy (with letter grade cutoffs). Includes tests of
diff --git a/lms/djangoapps/instructor/tests/test_tools.py b/lms/djangoapps/instructor/tests/test_tools.py
index 5c58f53f9ca..da8fe04d483 100644
--- a/lms/djangoapps/instructor/tests/test_tools.py
+++ b/lms/djangoapps/instructor/tests/test_tools.py
@@ -24,7 +24,7 @@ from ..views import tools
 DATE_FIELD = Date()
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestDashboardError(unittest.TestCase):
     """
     Test DashboardError exceptions.
@@ -35,7 +35,7 @@ class TestDashboardError(unittest.TestCase):
         self.assertEqual(response, {'error': 'Oh noes!'})
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestHandleDashboardError(unittest.TestCase):
     """
     Test handle_dashboard_error decorator.
@@ -64,7 +64,7 @@ class TestHandleDashboardError(unittest.TestCase):
         self.assertEqual(view(None, None), "Oh yes!")
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestRequireStudentIdentifier(unittest.TestCase):
     """
     Test require_student_from_identifier()
@@ -87,7 +87,7 @@ class TestRequireStudentIdentifier(unittest.TestCase):
             tools.require_student_from_identifier("invalid")
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestParseDatetime(unittest.TestCase):
     """
     Test date parsing.
@@ -102,7 +102,7 @@ class TestParseDatetime(unittest.TestCase):
             tools.parse_datetime('foo')
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestFindUnit(SharedModuleStoreTestCase):
     """
     Test the find_unit function.
@@ -132,7 +132,7 @@ class TestFindUnit(SharedModuleStoreTestCase):
             tools.find_unit(self.course, url)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestGetUnitsWithDueDate(ModuleStoreTestCase):
     """
     Test the get_units_with_due_date function.
@@ -168,7 +168,7 @@ class TestGetUnitsWithDueDate(ModuleStoreTestCase):
             urls((self.week1, self.week2)))
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestTitleOrUrl(unittest.TestCase):
     """
     Test the title_or_url funciton.
@@ -183,7 +183,7 @@ class TestTitleOrUrl(unittest.TestCase):
         self.assertEquals(tools.title_or_url(unit), 'test:hello')
 
 
-@attr('shard_1')
+@attr(shard=1)
 @override_settings(
     FIELD_OVERRIDE_PROVIDERS=(
         'courseware.student_field_overrides.IndividualStudentOverrideProvider',),
@@ -263,7 +263,7 @@ class TestSetDueDateExtension(ModuleStoreTestCase):
         self.assertEqual(self.week1.due, self.due)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestDataDumps(ModuleStoreTestCase):
     """
     Test data dumps for reporting.
diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
index 7371a7e00b1..fc50c41ecf4 100644
--- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
+++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
@@ -43,7 +43,7 @@ def intercept_renderer(path, context):
     return response
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssTestMixin):
     """
diff --git a/lms/djangoapps/instructor_analytics/tests/test_basic.py b/lms/djangoapps/instructor_analytics/tests/test_basic.py
index 82a00c832a6..6b66bdcda75 100644
--- a/lms/djangoapps/instructor_analytics/tests/test_basic.py
+++ b/lms/djangoapps/instructor_analytics/tests/test_basic.py
@@ -32,7 +32,7 @@ from edx_proctoring.api import create_exam
 from edx_proctoring.models import ProctoredExamStudentAttempt
 
 
-@attr('shard_3')
+@attr(shard=3)
 class TestAnalyticsBasic(ModuleStoreTestCase):
     """ Test basic analytics functions. """
 
diff --git a/lms/djangoapps/instructor_task/tests/test_api.py b/lms/djangoapps/instructor_task/tests/test_api.py
index 51ec2b5dff2..73cb33dddeb 100644
--- a/lms/djangoapps/instructor_task/tests/test_api.py
+++ b/lms/djangoapps/instructor_task/tests/test_api.py
@@ -83,7 +83,7 @@ class InstructorTaskReportTest(InstructorTaskTestCase):
         self.assertEquals(set(task_ids), set())
 
 
-@attr('shard_3')
+@attr(shard=3)
 class InstructorTaskModuleSubmitTest(InstructorTaskModuleTestCase):
     """Tests API methods that involve the submission of module-based background tasks."""
 
@@ -174,7 +174,7 @@ class InstructorTaskModuleSubmitTest(InstructorTaskModuleTestCase):
         self._test_submit_task(submit_delete_problem_state_for_all_students)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True))
 class InstructorTaskCourseSubmitTest(TestReportMixin, InstructorTaskCourseTestCase):
     """Tests API methods that involve the submission of course-based background tasks."""
diff --git a/lms/djangoapps/instructor_task/tests/test_integration.py b/lms/djangoapps/instructor_task/tests/test_integration.py
index 431ebcceac0..afd241a9cc8 100644
--- a/lms/djangoapps/instructor_task/tests/test_integration.py
+++ b/lms/djangoapps/instructor_task/tests/test_integration.py
@@ -64,7 +64,7 @@ class TestIntegrationTask(InstructorTaskModuleTestCase):
         self.assertEqual(status['message'], expected_message)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class TestRescoringTask(TestIntegrationTask):
     """
     Integration-style tests for rescoring problems in a background task.
diff --git a/lms/djangoapps/instructor_task/tests/test_tasks.py b/lms/djangoapps/instructor_task/tests/test_tasks.py
index 90433e29978..2a75d5cf929 100644
--- a/lms/djangoapps/instructor_task/tests/test_tasks.py
+++ b/lms/djangoapps/instructor_task/tests/test_tasks.py
@@ -214,7 +214,7 @@ class TestInstructorTasks(InstructorTaskModuleTestCase):
         self.assertEquals(output['traceback'][-3:], "...")
 
 
-@attr('shard_3')
+@attr(shard=3)
 class TestRescoreInstructorTask(TestInstructorTasks):
     """Tests problem-rescoring instructor task."""
 
@@ -317,7 +317,7 @@ class TestRescoreInstructorTask(TestInstructorTasks):
         self.assertGreater(output.get('duration_ms'), 0)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class TestResetAttemptsInstructorTask(TestInstructorTasks):
     """Tests instructor task that resets problem attempts."""
 
@@ -416,7 +416,7 @@ class TestResetAttemptsInstructorTask(TestInstructorTasks):
         self._test_reset_with_student(True)
 
 
-@attr('shard_3')
+@attr(shard=3)
 class TestDeleteStateInstructorTask(TestInstructorTasks):
     """Tests instructor task that deletes problem state."""
 
diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py
index 64d4211e9f4..0ad5996d8c1 100644
--- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py
+++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py
@@ -668,7 +668,7 @@ class TestProblemGradeReport(TestReportMixin, InstructorTaskModuleTestCase):
         ])
 
 
-@attr('shard_3')
+@attr(shard=3)
 class TestProblemReportSplitTestContent(TestReportMixin, TestConditionalContent, InstructorTaskModuleTestCase):
     """
     Test the problem report on a course that has split tests.
@@ -1627,7 +1627,7 @@ class TestGradeReportEnrollmentAndCertificateInfo(TestReportMixin, InstructorTas
         self._verify_csv_data(user.username, expected_output)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 @override_settings(CERT_QUEUE='test-queue')
 class TestCertificateGeneration(InstructorTaskModuleTestCase):
diff --git a/lms/djangoapps/lti_provider/tests/test_views.py b/lms/djangoapps/lti_provider/tests/test_views.py
index 3a536c692ba..b66d6bc4a7c 100644
--- a/lms/djangoapps/lti_provider/tests/test_views.py
+++ b/lms/djangoapps/lti_provider/tests/test_views.py
@@ -163,7 +163,7 @@ class LtiLaunchTest(LtiTestMixin, TestCase):
         self.assertEqual(consumer.instance_guid, u'consumer instance guid')
 
 
-@attr('shard_3')
+@attr(shard=3)
 class LtiLaunchTestRender(LtiTestMixin, RenderXBlockTestMixin, ModuleStoreTestCase):
     """
     Tests for the rendering returned by lti_launch view.
diff --git a/lms/djangoapps/mobile_api/course_info/tests.py b/lms/djangoapps/mobile_api/course_info/tests.py
index a7a4944ea9c..39b44e91c9a 100644
--- a/lms/djangoapps/mobile_api/course_info/tests.py
+++ b/lms/djangoapps/mobile_api/course_info/tests.py
@@ -17,7 +17,7 @@ from ..testutils import (
 )
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class TestUpdates(MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin, MilestonesTestCaseMixin):
     """
@@ -85,7 +85,7 @@ class TestUpdates(MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTest
             self.assertIn("Update" + str(num), update_data['content'])
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class TestHandouts(MobileAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin, MilestonesTestCaseMixin):
     """
diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py
index 1c7770de183..6e86e72adf0 100644
--- a/lms/djangoapps/mobile_api/users/tests.py
+++ b/lms/djangoapps/mobile_api/users/tests.py
@@ -40,7 +40,7 @@ from mobile_api.testutils import (
 from .serializers import CourseEnrollmentSerializer
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestUserDetailApi(MobileAPITestCase, MobileAuthUserTestMixin):
     """
     Tests for /api/mobile/v0.5/users/<user_name>...
@@ -55,7 +55,7 @@ class TestUserDetailApi(MobileAPITestCase, MobileAuthUserTestMixin):
         self.assertEqual(response.data['email'], self.user.email)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
     """
     Tests for /api/mobile/v0.5/my_user_info
@@ -71,7 +71,7 @@ class TestUserInfoApi(MobileAPITestCase, MobileAuthTestMixin):
         self.assertIn(self.username, response['location'])
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @override_settings(MKTG_URLS={'ROOT': 'dummy-root'})
 class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTestMixin,
@@ -276,7 +276,7 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
         self.assertIn('/api/discussion/v1/courses/{}'.format(self.course.id), response_discussion_url)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CourseStatusAPITestCase(MobileAPITestCase):
     """
     Base test class for /api/mobile/v0.5/users/<user_name>/course_status_info/{course_id}
@@ -311,7 +311,7 @@ class CourseStatusAPITestCase(MobileAPITestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestCourseStatusGET(CourseStatusAPITestCase, MobileAuthUserTestMixin,
                           MobileCourseAccessTestMixin, MilestonesTestCaseMixin):
     """
@@ -331,7 +331,7 @@ class TestCourseStatusGET(CourseStatusAPITestCase, MobileAuthUserTestMixin,
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin,
                             MobileCourseAccessTestMixin, MilestonesTestCaseMixin):
     """
@@ -435,7 +435,7 @@ class TestCourseStatusPATCH(CourseStatusAPITestCase, MobileAuthUserTestMixin,
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True})
 @override_settings(MKTG_URLS={'ROOT': 'dummy-root'})
 class TestCourseEnrollmentSerializer(MobileAPITestCase, MilestonesTestCaseMixin):
diff --git a/lms/djangoapps/mobile_api/video_outlines/tests.py b/lms/djangoapps/mobile_api/video_outlines/tests.py
index ef749b81838..b13e173766e 100644
--- a/lms/djangoapps/mobile_api/video_outlines/tests.py
+++ b/lms/djangoapps/mobile_api/video_outlines/tests.py
@@ -199,7 +199,7 @@ class TestVideoAPIMixin(object):
         return sub_block_a, sub_block_b
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestNonStandardCourseStructure(MobileAPITestCase, TestVideoAPIMixin, MilestonesTestCaseMixin):
     """
     Tests /api/mobile/v0.5/video_outlines/courses/{course_id} with no course set
@@ -409,7 +409,7 @@ class TestNonStandardCourseStructure(MobileAPITestCase, TestVideoAPIMixin, Miles
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestVideoSummaryList(TestVideoAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin,
                            TestVideoAPIMixin, MilestonesTestCaseMixin):
@@ -866,7 +866,7 @@ class TestVideoSummaryList(TestVideoAPITestCase, MobileAuthTestMixin, MobileCour
             )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestTranscriptsDetail(TestVideoAPITestCase, MobileAuthTestMixin, MobileCourseAccessTestMixin,
                             TestVideoAPIMixin, MilestonesTestCaseMixin):
     """
diff --git a/lms/djangoapps/shoppingcart/tests/test_models.py b/lms/djangoapps/shoppingcart/tests/test_models.py
index 9091a486a85..a21973d18fd 100644
--- a/lms/djangoapps/shoppingcart/tests/test_models.py
+++ b/lms/djangoapps/shoppingcart/tests/test_models.py
@@ -47,7 +47,7 @@ from shoppingcart.exceptions import (
 from opaque_keys.edx.locator import CourseLocator
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class OrderTest(ModuleStoreTestCase):
     """
@@ -484,7 +484,7 @@ class OrderItemTest(TestCase):
         self.assertEqual(item.get_list_price(), item.list_price)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
 class PaidCourseRegistrationTest(ModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/shoppingcart/tests/test_views.py b/lms/djangoapps/shoppingcart/tests/test_views.py
index a77b9f325e8..dc940fdac29 100644
--- a/lms/djangoapps/shoppingcart/tests/test_views.py
+++ b/lms/djangoapps/shoppingcart/tests/test_views.py
@@ -65,7 +65,7 @@ render_mock = Mock(side_effect=mock_render_to_response)
 postpay_mock = Mock()
 
 
-@attr('shard_3')
+@attr(shard=3)
 @patch.dict('django.conf.settings.FEATURES', {'ENABLE_PAID_COURSE_REGISTRATION': True})
 @ddt.ddt
 class ShoppingCartViewsTests(SharedModuleStoreTestCase, XssTestMixin):
diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py
index 0941ba81704..9595de6646f 100644
--- a/lms/djangoapps/student_account/test/test_views.py
+++ b/lms/djangoapps/student_account/test/test_views.py
@@ -221,7 +221,7 @@ class StudentAccountUpdateTest(CacheIsolationTestCase, UrlResetMixin):
         return self.client.post(path=reverse('password_change_request'), data=data)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMixin, ModuleStoreTestCase):
     """ Tests for the student account views that update the user's account information. """
diff --git a/lms/djangoapps/support/tests/test_views.py b/lms/djangoapps/support/tests/test_views.py
index 9a9e773a923..d30c69363e3 100644
--- a/lms/djangoapps/support/tests/test_views.py
+++ b/lms/djangoapps/support/tests/test_views.py
@@ -41,7 +41,7 @@ class SupportViewTestCase(ModuleStoreTestCase):
         self.assertTrue(success, msg="Could not log in")
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class SupportViewAccessTests(SupportViewTestCase):
     """
diff --git a/lms/djangoapps/teams/tests/test_views.py b/lms/djangoapps/teams/tests/test_views.py
index bb2402aca87..14927221970 100644
--- a/lms/djangoapps/teams/tests/test_views.py
+++ b/lms/djangoapps/teams/tests/test_views.py
@@ -31,7 +31,7 @@ from django_comment_common.models import Role, FORUM_ROLE_COMMUNITY_TA
 from django_comment_common.utils import seed_permissions_roles
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestDashboard(SharedModuleStoreTestCase):
     """Tests for the Teams dashboard."""
     test_password = "test"
diff --git a/lms/djangoapps/verified_track_content/tests/test_views.py b/lms/djangoapps/verified_track_content/tests/test_views.py
index 25f7e732536..6e3017d817b 100644
--- a/lms/djangoapps/verified_track_content/tests/test_views.py
+++ b/lms/djangoapps/verified_track_content/tests/test_views.py
@@ -19,7 +19,7 @@ from verified_track_content.models import VerifiedTrackCohortedCourse
 from verified_track_content.views import cohorting_settings
 
 
-@attr('shard_2')
+@attr(shard=2)
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
 class CohortingSettingsTestCase(SharedModuleStoreTestCase):
     """
diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py
index 092c7bcd2b9..3d9833cd8c7 100644
--- a/lms/djangoapps/verify_student/tests/test_views.py
+++ b/lms/djangoapps/verify_student/tests/test_views.py
@@ -69,7 +69,7 @@ render_mock = Mock(side_effect=mock_render_to_response)
 PAYMENT_DATA_KEYS = {'payment_processor_name', 'payment_page_url', 'payment_form_data'}
 
 
-@attr('shard_2')
+@attr(shard=2)
 class StartView(TestCase):
     """
     This view is for the first time student is
@@ -90,7 +90,7 @@ class StartView(TestCase):
         self.assertHttpForbidden(self.client.get(self.start_url()))
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
     """
@@ -1211,7 +1211,7 @@ class CheckoutTestMixin(object):
         self.assertEqual(data, {'foo': 'bar'})
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch('lms.djangoapps.verify_student.views.checkout_with_shoppingcart', return_value=TEST_PAYMENT_DATA, autospec=True)
 class TestCreateOrderShoppingCart(CheckoutTestMixin, ModuleStoreTestCase):
     """ Test view behavior when the shoppingcart is used. """
@@ -1225,7 +1225,7 @@ class TestCreateOrderShoppingCart(CheckoutTestMixin, ModuleStoreTestCase):
         return dict(zip(('request', 'user', 'course_key', 'course_mode', 'amount'), patched_create_order.call_args[0]))
 
 
-@attr('shard_2')
+@attr(shard=2)
 @override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY)
 @patch(
     'lms.djangoapps.verify_student.views.checkout_with_ecommerce_service',
@@ -1244,7 +1244,7 @@ class TestCreateOrderEcommerceService(CheckoutTestMixin, ModuleStoreTestCase):
         return dict(zip(('user', 'course_key', 'course_mode', 'processor'), patched_create_order.call_args[0]))
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestCheckoutWithEcommerceService(ModuleStoreTestCase):
     """
     Ensures correct behavior in the function `checkout_with_ecommerce_service`.
@@ -1290,7 +1290,7 @@ class TestCheckoutWithEcommerceService(ModuleStoreTestCase):
         self.assertEqual(actual_payment_data, expected_payment_data)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestCreateOrderView(ModuleStoreTestCase):
     """
     Tests for the create_order view of verified course enrollment process.
@@ -1394,7 +1394,7 @@ class TestCreateOrderView(ModuleStoreTestCase):
         return response
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @patch.dict(settings.FEATURES, {'AUTOMATIC_VERIFY_STUDENT_IDENTITY_FOR_TESTING': True})
 class TestSubmitPhotosForVerification(TestCase):
@@ -1636,7 +1636,7 @@ class TestSubmitPhotosForVerification(TestCase):
         return json.loads(last_request.body)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
     """
     Tests for the results_callback view.
@@ -1998,7 +1998,7 @@ class TestPhotoVerificationResultsCallback(ModuleStoreTestCase):
         VerificationStatus.add_verification_status(checkpoint, self.user, "submitted")
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestReverifyView(TestCase):
     """
     Tests for the reverification view.
@@ -2093,7 +2093,7 @@ class TestReverifyView(TestCase):
         self.assertContains(response, "reverify-blocked")
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestInCourseReverifyView(ModuleStoreTestCase):
     """
     Tests for the incourse reverification views.
@@ -2293,7 +2293,7 @@ class TestInCourseReverifyView(ModuleStoreTestCase):
         return self.client.post(url, data)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
     """
     Test email sending on re-verification
@@ -2498,7 +2498,7 @@ class TestEmailMessageWithCustomICRVBlock(ModuleStoreTestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestEmailMessageWithDefaultICRVBlock(ModuleStoreTestCase):
     """
     Test for In-course Re-verification
diff --git a/lms/lib/xblock/test/test_mixin.py b/lms/lib/xblock/test/test_mixin.py
index d8b236927ce..69832b80d37 100644
--- a/lms/lib/xblock/test/test_mixin.py
+++ b/lms/lib/xblock/test/test_mixin.py
@@ -152,7 +152,7 @@ class OpenAssessmentBlockMixinTestCase(ModuleStoreTestCase):
         self.assertTrue(self.open_assessment.has_score)
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class XBlockGetParentTest(LmsXBlockMixinTestCase):
     """
@@ -245,7 +245,7 @@ def ddt_named(parent, child):
     return args
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class XBlockMergedGroupAccessTest(LmsXBlockMixinTestCase):
     """
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_api.py b/openedx/core/djangoapps/bookmarks/tests/test_api.py
index 0625841f266..975b3bae091 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_api.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_api.py
@@ -36,7 +36,7 @@ class BookmarkApiEventTestMixin(object):
         self.assertFalse(mock_tracker.called)  # pylint: disable=maybe-no-member
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
 class BookmarksAPITests(BookmarkApiEventTestMixin, BookmarksTestsBase):
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_models.py b/openedx/core/djangoapps/bookmarks/tests/test_models.py
index fd5b9b62bb6..6afb63e79f1 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_models.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_models.py
@@ -226,7 +226,7 @@ class BookmarksTestsBase(ModuleStoreTestCase):
             self.assertEqual(bookmark_data['path'], bookmark.path)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
 class BookmarkModelTests(BookmarksTestsBase):
@@ -412,7 +412,7 @@ class BookmarkModelTests(BookmarksTestsBase):
             self.assertEqual(bookmark.path, [])
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class XBlockCacheModelTest(ModuleStoreTestCase):
     """
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_services.py b/openedx/core/djangoapps/bookmarks/tests/test_services.py
index d06ba7059e0..5dbaea99b29 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_services.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_services.py
@@ -12,7 +12,7 @@ from ..services import BookmarksService
 from .test_models import BookmarksTestsBase
 
 
-@attr('shard_2')
+@attr(shard=2)
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
 class BookmarksServiceTests(BookmarksTestsBase):
     """
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_tasks.py b/openedx/core/djangoapps/bookmarks/tests/test_tasks.py
index e6c3c25c166..00691e4d35c 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_tasks.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_tasks.py
@@ -14,7 +14,7 @@ from ..tasks import _calculate_course_xblocks_data, _update_xblocks_cache
 from .test_models import BookmarksTestsBase
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class XBlockCacheTaskTests(BookmarksTestsBase):
     """
diff --git a/openedx/core/djangoapps/bookmarks/tests/test_views.py b/openedx/core/djangoapps/bookmarks/tests/test_views.py
index 76acc66be35..c3f0d7dc3d0 100644
--- a/openedx/core/djangoapps/bookmarks/tests/test_views.py
+++ b/openedx/core/djangoapps/bookmarks/tests/test_views.py
@@ -64,7 +64,7 @@ class BookmarksViewsTestsBase(BookmarksTestsBase, BookmarkApiEventTestMixin):
         return response
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
 class BookmarksListViewTests(BookmarksViewsTestsBase):
@@ -369,7 +369,7 @@ class BookmarksListViewTests(BookmarksViewsTestsBase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
 class BookmarksDetailViewTests(BookmarksViewsTestsBase):
diff --git a/openedx/core/djangoapps/ccxcon/tests/test_api.py b/openedx/core/djangoapps/ccxcon/tests/test_api.py
index 29abd04d875..e62f99c2e73 100644
--- a/openedx/core/djangoapps/ccxcon/tests/test_api.py
+++ b/openedx/core/djangoapps/ccxcon/tests/test_api.py
@@ -38,7 +38,7 @@ def fetch_token_mock(*args, **kwargs):  # pylint: disable=unused-argument
     return
 
 
-@attr('shard_1')
+@attr(shard=1)
 class APIsTestCase(SharedModuleStoreTestCase):
     """
     Unit tests for the API module functions
diff --git a/openedx/core/djangoapps/ccxcon/tests/test_signals.py b/openedx/core/djangoapps/ccxcon/tests/test_signals.py
index 5de4c74d68e..9eb2ff87e7e 100644
--- a/openedx/core/djangoapps/ccxcon/tests/test_signals.py
+++ b/openedx/core/djangoapps/ccxcon/tests/test_signals.py
@@ -10,7 +10,7 @@ from opaque_keys.edx.keys import CourseKey
 from xmodule.modulestore.django import modulestore, SignalHandler
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CCXConSignalTestCase(TestCase):
     """
     The only tests currently implemented are for verifying that
diff --git a/openedx/core/djangoapps/ccxcon/tests/test_tasks.py b/openedx/core/djangoapps/ccxcon/tests/test_tasks.py
index de42bf20795..12cafa99cd4 100644
--- a/openedx/core/djangoapps/ccxcon/tests/test_tasks.py
+++ b/openedx/core/djangoapps/ccxcon/tests/test_tasks.py
@@ -11,7 +11,7 @@ from opaque_keys.edx.keys import CourseKey
 from openedx.core.djangoapps.ccxcon import api, tasks
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CCXConTaskTestCase(TestCase):
     """
     Tests for CCXCon tasks.
diff --git a/openedx/core/djangoapps/content/course_overviews/management/commands/tests/test_generate_course_overview.py b/openedx/core/djangoapps/content/course_overviews/management/commands/tests/test_generate_course_overview.py
index 248c034816e..27b582cf4cb 100644
--- a/openedx/core/djangoapps/content/course_overviews/management/commands/tests/test_generate_course_overview.py
+++ b/openedx/core/djangoapps/content/course_overviews/management/commands/tests/test_generate_course_overview.py
@@ -8,7 +8,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestGenerateCourseOverview(ModuleStoreTestCase):
     """
     Tests course overview management command.
diff --git a/openedx/core/djangoapps/content/course_overviews/tests.py b/openedx/core/djangoapps/content/course_overviews/tests.py
index 6cd9a306a8c..aa3770b89bd 100644
--- a/openedx/core/djangoapps/content/course_overviews/tests.py
+++ b/openedx/core/djangoapps/content/course_overviews/tests.py
@@ -37,7 +37,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls
 from .models import CourseOverview, CourseOverviewImageSet, CourseOverviewImageConfig
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class CourseOverviewTestCase(ModuleStoreTestCase):
     """
@@ -518,7 +518,7 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
             )
 
 
-@attr('shard_3')
+@attr(shard=3)
 @ddt.ddt
 class CourseOverviewImageSetTestCase(ModuleStoreTestCase):
     """
diff --git a/openedx/core/djangoapps/content/course_structures/tests.py b/openedx/core/djangoapps/content/course_structures/tests.py
index 78e673dd5bd..e640d7d7cc7 100644
--- a/openedx/core/djangoapps/content/course_structures/tests.py
+++ b/openedx/core/djangoapps/content/course_structures/tests.py
@@ -23,7 +23,7 @@ class SignalDisconnectTestMixin(object):
         SignalHandler.course_published.disconnect(listen_for_course_publish)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CourseStructureTaskTests(ModuleStoreTestCase):
     """
     Test cases covering Course Structure task-related workflows
diff --git a/openedx/core/djangoapps/course_groups/management/commands/tests/test_post_cohort_membership_fix.py b/openedx/core/djangoapps/course_groups/management/commands/tests/test_post_cohort_membership_fix.py
index bd6ca8efbc3..20b4db84e2d 100644
--- a/openedx/core/djangoapps/course_groups/management/commands/tests/test_post_cohort_membership_fix.py
+++ b/openedx/core/djangoapps/course_groups/management/commands/tests/test_post_cohort_membership_fix.py
@@ -14,7 +14,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestPostMigrationFix(ModuleStoreTestCase):
     """
     Base class for testing post-migration fix commands
diff --git a/openedx/core/djangoapps/course_groups/tests/test_cohorts.py b/openedx/core/djangoapps/course_groups/tests/test_cohorts.py
index 0df8acedfd1..ca44a2858d5 100644
--- a/openedx/core/djangoapps/course_groups/tests/test_cohorts.py
+++ b/openedx/core/djangoapps/course_groups/tests/test_cohorts.py
@@ -27,7 +27,7 @@ from ..tests.helpers import (
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch("openedx.core.djangoapps.course_groups.cohorts.tracker", autospec=True)
 class TestCohortSignals(TestCase):
     """
@@ -133,7 +133,7 @@ class TestCohortSignals(TestCase):
         self.assertFalse(mock_tracker.emit.called)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestCohorts(ModuleStoreTestCase):
     """
@@ -727,7 +727,7 @@ class TestCohorts(ModuleStoreTestCase):
             )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestCohortsAndPartitionGroups(ModuleStoreTestCase):
     """
diff --git a/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py b/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
index 68b5672ea64..949d1cf5eea 100644
--- a/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
+++ b/openedx/core/djangoapps/course_groups/tests/test_partition_scheme.py
@@ -26,7 +26,7 @@ from ..cohorts import add_user_to_cohort, remove_user_from_cohort, get_course_co
 from .helpers import CohortFactory, config_course_cohorts
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestCohortPartitionScheme(ModuleStoreTestCase):
     """
     Test the logic for linking a user to a partition group based on their cohort.
@@ -260,7 +260,7 @@ class TestCohortPartitionScheme(ModuleStoreTestCase):
             self.assertRegexpMatches(mock_log.warn.call_args[0][0], 'partition mismatch')
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestExtension(django.test.TestCase):
     """
     Ensure that the scheme extension is correctly plugged in (via entry point
@@ -273,7 +273,7 @@ class TestExtension(django.test.TestCase):
             UserPartition.get_scheme('other')
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestGetCohortedUserPartition(ModuleStoreTestCase):
     """
     Test that `get_cohorted_user_partition` returns the first user_partition with scheme `CohortPartitionScheme`.
@@ -331,7 +331,7 @@ class TestGetCohortedUserPartition(ModuleStoreTestCase):
         self.assertIsNone(get_cohorted_user_partition(self.course))
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestMasqueradedGroup(StaffMasqueradeTestCase):
     """
     Check for staff being able to masquerade as belonging to a group.
diff --git a/openedx/core/djangoapps/course_groups/tests/test_views.py b/openedx/core/djangoapps/course_groups/tests/test_views.py
index 227cb23f35d..4afecd87df5 100644
--- a/openedx/core/djangoapps/course_groups/tests/test_views.py
+++ b/openedx/core/djangoapps/course_groups/tests/test_views.py
@@ -36,7 +36,7 @@ from .helpers import (
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CohortViewsTestCase(ModuleStoreTestCase):
     """
     Base class which sets up a course and staff/non-staff users.
@@ -176,7 +176,7 @@ class CohortViewsTestCase(ModuleStoreTestCase):
         return json.loads(response.content)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
     """
     Tests the `course_cohort_settings_handler` view.
@@ -326,7 +326,7 @@ class CourseCohortSettingsHandlerTestCase(CohortViewsTestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CohortHandlerTestCase(CohortViewsTestCase):
     """
     Tests the `cohort_handler` view.
@@ -679,7 +679,7 @@ class CohortHandlerTestCase(CohortViewsTestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class UsersInCohortTestCase(CohortViewsTestCase):
     """
     Tests the `users_in_cohort` view.
@@ -812,7 +812,7 @@ class UsersInCohortTestCase(CohortViewsTestCase):
         self.request_users_in_cohort(cohort, self.course, -1, should_return_bad_request=True)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class AddUsersToCohortTestCase(CohortViewsTestCase):
     """
     Tests the `add_users_to_cohort` view.
@@ -1111,7 +1111,7 @@ class AddUsersToCohortTestCase(CohortViewsTestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class RemoveUserFromCohortTestCase(CohortViewsTestCase):
     """
     Tests the `remove_user_from_cohort` view.
@@ -1205,7 +1205,7 @@ class RemoveUserFromCohortTestCase(CohortViewsTestCase):
         self.verify_removed_user_from_cohort(user.username, response_dict, cohort)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Tests only valid in LMS')
 class CourseCohortDiscussionTopicsTestCase(CohortViewsTestCase):
     """
diff --git a/openedx/core/djangoapps/credentials/tests/test_models.py b/openedx/core/djangoapps/credentials/tests/test_models.py
index 697482200a1..e934659f390 100644
--- a/openedx/core/djangoapps/credentials/tests/test_models.py
+++ b/openedx/core/djangoapps/credentials/tests/test_models.py
@@ -9,7 +9,7 @@ from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfi
 
 
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-@attr('shard_2')
+@attr(shard=2)
 class TestCredentialsApiConfig(CredentialsApiConfigMixin, TestCase):
     """Tests covering the CredentialsApiConfig model."""
     def test_url_construction(self):
diff --git a/openedx/core/djangoapps/credentials/tests/test_utils.py b/openedx/core/djangoapps/credentials/tests/test_utils.py
index e563b7f0311..87b1c2a26e4 100644
--- a/openedx/core/djangoapps/credentials/tests/test_utils.py
+++ b/openedx/core/djangoapps/credentials/tests/test_utils.py
@@ -23,7 +23,7 @@ from student.tests.factories import UserFactory
 
 
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-@attr('shard_2')
+@attr(shard=2)
 class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin, CredentialsDataMixin,
                                ProgramsDataMixin, CacheIsolationTestCase):
     """ Tests covering the retrieval of user credentials from the Credentials
diff --git a/openedx/core/djangoapps/credit/tests/test_api.py b/openedx/core/djangoapps/credit/tests/test_api.py
index ab82cc92727..e90121d8db8 100644
--- a/openedx/core/djangoapps/credit/tests/test_api.py
+++ b/openedx/core/djangoapps/credit/tests/test_api.py
@@ -187,7 +187,7 @@ class CreditApiTestBase(ModuleStoreTestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
 @ddt.ddt
 class CreditRequirementApiTests(CreditApiTestBase):
@@ -769,7 +769,7 @@ class CreditRequirementApiTests(CreditApiTestBase):
         self.assertIn(providers_email_message, text_content_first)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class CreditProviderIntegrationApiTests(CreditApiTestBase):
     """
@@ -1098,7 +1098,7 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
         self.assertEqual(statuses[0]["status"], expected_status)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
 @override_settings(
     ECOMMERCE_API_URL=TEST_API_URL,
diff --git a/openedx/core/djangoapps/credit/tests/test_models.py b/openedx/core/djangoapps/credit/tests/test_models.py
index f731915b84f..45e3968b000 100644
--- a/openedx/core/djangoapps/credit/tests/test_models.py
+++ b/openedx/core/djangoapps/credit/tests/test_models.py
@@ -11,7 +11,7 @@ from opaque_keys.edx.keys import CourseKey
 from openedx.core.djangoapps.credit.models import CreditCourse, CreditRequirement
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class CreditEligibilityModelTests(TestCase):
     """
diff --git a/openedx/core/djangoapps/credit/tests/test_partition.py b/openedx/core/djangoapps/credit/tests/test_partition.py
index 2397f207645..c1d893ef395 100644
--- a/openedx/core/djangoapps/credit/tests/test_partition.py
+++ b/openedx/core/djangoapps/credit/tests/test_partition.py
@@ -22,7 +22,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class ReverificationPartitionTest(ModuleStoreTestCase):
diff --git a/openedx/core/djangoapps/credit/tests/test_serializers.py b/openedx/core/djangoapps/credit/tests/test_serializers.py
index 06aed067173..b3eccb007cb 100644
--- a/openedx/core/djangoapps/credit/tests/test_serializers.py
+++ b/openedx/core/djangoapps/credit/tests/test_serializers.py
@@ -11,7 +11,7 @@ from openedx.core.djangoapps.credit.tests.factories import CreditProviderFactory
 from student.tests.factories import UserFactory
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CreditProviderSerializerTests(TestCase):
     """ CreditProviderSerializer tests. """
 
@@ -32,7 +32,7 @@ class CreditProviderSerializerTests(TestCase):
         self.assertDictEqual(serializer.data, expected)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CreditEligibilitySerializerTests(TestCase):
     """ CreditEligibilitySerializer tests. """
 
diff --git a/openedx/core/djangoapps/credit/tests/test_services.py b/openedx/core/djangoapps/credit/tests/test_services.py
index aeaf047c679..0575261a2ad 100644
--- a/openedx/core/djangoapps/credit/tests/test_services.py
+++ b/openedx/core/djangoapps/credit/tests/test_services.py
@@ -14,7 +14,7 @@ from openedx.core.djangoapps.credit.api.eligibility import set_credit_requiremen
 from student.models import CourseEnrollment, UserProfile
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CreditServiceTests(ModuleStoreTestCase):
     """
     Tests for the Credit xBlock service
diff --git a/openedx/core/djangoapps/credit/tests/test_signals.py b/openedx/core/djangoapps/credit/tests/test_signals.py
index 1121b27eeda..376b8d87698 100644
--- a/openedx/core/djangoapps/credit/tests/test_signals.py
+++ b/openedx/core/djangoapps/credit/tests/test_signals.py
@@ -21,7 +21,7 @@ from openedx.core.djangoapps.credit.models import CreditCourse, CreditProvider
 from openedx.core.djangoapps.credit.signals import listen_for_grade_calculation
 
 
-@attr('shard_2')
+@attr(shard=2)
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in LMS')
 @ddt.ddt
 class TestMinGradedRequirementStatus(ModuleStoreTestCase):
diff --git a/openedx/core/djangoapps/credit/tests/test_signature.py b/openedx/core/djangoapps/credit/tests/test_signature.py
index 88099aaaea5..e8e5ae6a820 100644
--- a/openedx/core/djangoapps/credit/tests/test_signature.py
+++ b/openedx/core/djangoapps/credit/tests/test_signature.py
@@ -12,7 +12,7 @@ from django.test.utils import override_settings
 from openedx.core.djangoapps.credit import signature
 
 
-@attr('shard_2')
+@attr(shard=2)
 @override_settings(CREDIT_PROVIDER_SECRET_KEYS={
     "asu": u'abcd1234'
 })
diff --git a/openedx/core/djangoapps/credit/tests/test_tasks.py b/openedx/core/djangoapps/credit/tests/test_tasks.py
index 531b7a294a9..a2d3ee7808a 100644
--- a/openedx/core/djangoapps/credit/tests/test_tasks.py
+++ b/openedx/core/djangoapps/credit/tests/test_tasks.py
@@ -18,7 +18,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, chec
 from edx_proctoring.api import create_exam
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestTaskExecution(ModuleStoreTestCase):
     """Set of tests to ensure that the task code will do the right thing when
     executed directly.
diff --git a/openedx/core/djangoapps/credit/tests/test_verification_access.py b/openedx/core/djangoapps/credit/tests/test_verification_access.py
index 4eb33b8312f..4d8779d2c5f 100644
--- a/openedx/core/djangoapps/credit/tests/test_verification_access.py
+++ b/openedx/core/djangoapps/credit/tests/test_verification_access.py
@@ -28,7 +28,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, chec
 from xmodule.partitions.partitions import Group, UserPartition
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CreateVerificationPartitionTest(ModuleStoreTestCase):
     """
     Tests for applying verification access rules.
@@ -232,7 +232,7 @@ class CreateVerificationPartitionTest(ModuleStoreTestCase):
             return None
 
 
-@attr('shard_2')
+@attr(shard=2)
 class WriteOnPublishTest(ModuleStoreTestCase):
     """
     Verify that updates to the course descriptor's
diff --git a/openedx/core/djangoapps/credit/tests/test_views.py b/openedx/core/djangoapps/credit/tests/test_views.py
index b73277ca69a..5bb6f7d75d2 100644
--- a/openedx/core/djangoapps/credit/tests/test_views.py
+++ b/openedx/core/djangoapps/credit/tests/test_views.py
@@ -99,7 +99,7 @@ class ReadOnlyMixin(object):
         self.assertEqual(response.status_code, 405)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class CreditCourseViewSetTests(UserMixin, TestCase):
     """ Tests for the CreditCourse endpoints.
@@ -261,7 +261,7 @@ class CreditCourseViewSetTests(UserMixin, TestCase):
         self.assertTrue(credit_course.enabled)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class CreditProviderViewSetTests(ApiTestCaseMixin, ReadOnlyMixin, AuthMixin, UserMixin, TestCase):
@@ -306,7 +306,7 @@ class CreditProviderViewSetTests(ApiTestCaseMixin, ReadOnlyMixin, AuthMixin, Use
         self.assertEqual(response.data, CreditProviderSerializer(self.bayside).data)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class CreditProviderRequestCreateViewTests(ApiTestCaseMixin, UserMixin, TestCase):
     """ Tests for CreditProviderRequestCreateView. """
@@ -455,7 +455,7 @@ class CreditProviderRequestCreateViewTests(ApiTestCaseMixin, UserMixin, TestCase
         self.assertEqual(response.status_code, 400)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class CreditProviderCallbackViewTests(UserMixin, TestCase):
@@ -609,7 +609,7 @@ class CreditProviderCallbackViewTests(UserMixin, TestCase):
             self.assertEqual(response.status_code, 403)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class CreditEligibilityViewTests(AuthMixin, UserMixin, ReadOnlyMixin, TestCase):
diff --git a/openedx/core/djangoapps/models/tests/test_course_details.py b/openedx/core/djangoapps/models/tests/test_course_details.py
index 661ddfe4565..17cb3455f19 100644
--- a/openedx/core/djangoapps/models/tests/test_course_details.py
+++ b/openedx/core/djangoapps/models/tests/test_course_details.py
@@ -15,7 +15,7 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
 from openedx.core.djangoapps.models.course_details import CourseDetails, ABOUT_ATTRIBUTES
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class CourseDetailsTestCase(ModuleStoreTestCase):
     """
diff --git a/openedx/core/djangoapps/profile_images/tests/test_images.py b/openedx/core/djangoapps/profile_images/tests/test_images.py
index 8291a7b2dd7..b0134b731ee 100644
--- a/openedx/core/djangoapps/profile_images/tests/test_images.py
+++ b/openedx/core/djangoapps/profile_images/tests/test_images.py
@@ -28,7 +28,7 @@ from ..images import (
 from .helpers import make_image_file, make_uploaded_file
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
 class TestValidateUploadedImage(TestCase):
@@ -124,7 +124,7 @@ class TestValidateUploadedImage(TestCase):
             self.assertEqual(ctx.exception.message, file_upload_bad_mimetype)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
 class TestGenerateProfileImages(TestCase):
@@ -208,7 +208,7 @@ class TestGenerateProfileImages(TestCase):
                 yield name, image
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
 class TestRemoveProfileImages(TestCase):
     """
diff --git a/openedx/core/djangoapps/profile_images/tests/test_views.py b/openedx/core/djangoapps/profile_images/tests/test_views.py
index 812f2d74aba..c54934d4bff 100644
--- a/openedx/core/djangoapps/profile_images/tests/test_views.py
+++ b/openedx/core/djangoapps/profile_images/tests/test_views.py
@@ -152,7 +152,7 @@ class ProfileImageEndpointMixin(UserSettingsEventTestMixin):
         self.assert_no_events_were_emitted()
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
 @mock.patch('openedx.core.djangoapps.profile_images.views.log')
 class ProfileImageViewGeneralTestCase(ProfileImageEndpointMixin, APITestCase):
@@ -172,7 +172,7 @@ class ProfileImageViewGeneralTestCase(ProfileImageEndpointMixin, APITestCase):
         self.assert_no_events_were_emitted()
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
 @mock.patch('openedx.core.djangoapps.profile_images.views.log')
@@ -383,7 +383,7 @@ class ProfileImageViewPostTestCase(ProfileImageEndpointMixin, APITestCase):
         self.assert_no_events_were_emitted()
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
 @mock.patch('openedx.core.djangoapps.profile_images.views.log')
 class ProfileImageViewDeleteTestCase(ProfileImageEndpointMixin, APITestCase):
@@ -514,7 +514,7 @@ class DeprecatedProfileImageTestMixin(ProfileImageEndpointMixin):
         self.assert_no_events_were_emitted()
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
 @mock.patch('openedx.core.djangoapps.profile_images.views.log')
 class DeprecatedProfileImageUploadTestCase(DeprecatedProfileImageTestMixin, APITestCase):
@@ -527,7 +527,7 @@ class DeprecatedProfileImageUploadTestCase(DeprecatedProfileImageTestMixin, APIT
     _replacement_method = 'openedx.core.djangoapps.profile_images.views.ProfileImageView.post'
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Profile Image API is only supported in LMS')
 @mock.patch('openedx.core.djangoapps.profile_images.views.log')
 class DeprecatedProfileImageRemoveTestCase(DeprecatedProfileImageTestMixin, APITestCase):
diff --git a/openedx/core/djangoapps/programs/tests/test_models.py b/openedx/core/djangoapps/programs/tests/test_models.py
index 71386950c76..5c318c2cdce 100644
--- a/openedx/core/djangoapps/programs/tests/test_models.py
+++ b/openedx/core/djangoapps/programs/tests/test_models.py
@@ -7,7 +7,7 @@ from nose.plugins.attrib import attr
 from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 # ConfigurationModels use the cache. Make every cache get a miss.
 @mock.patch('config_models.models.cache.get', return_value=None)
diff --git a/openedx/core/djangoapps/programs/tests/test_signals.py b/openedx/core/djangoapps/programs/tests/test_signals.py
index 5560c3e671a..d3540e95011 100644
--- a/openedx/core/djangoapps/programs/tests/test_signals.py
+++ b/openedx/core/djangoapps/programs/tests/test_signals.py
@@ -15,7 +15,7 @@ from openedx.core.djangoapps.programs.signals import handle_course_cert_awarded
 TEST_USERNAME = 'test-user'
 
 
-@attr('shard_2')
+@attr(shard=2)
 @mock.patch('openedx.core.djangoapps.programs.tasks.v1.tasks.award_program_certificates.delay')
 @mock.patch(
     'openedx.core.djangoapps.programs.models.ProgramsApiConfig.is_certification_enabled',
diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py
index 9649368236b..15049b5c079 100644
--- a/openedx/core/djangoapps/programs/tests/test_utils.py
+++ b/openedx/core/djangoapps/programs/tests/test_utils.py
@@ -41,7 +41,7 @@ MARKETING_URL = 'https://www.example.com/marketing/path'
 
 
 @ddt.ddt
-@attr('shard_2')
+@attr(shard=2)
 @httpretty.activate
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, CredentialsDataMixin,
@@ -262,7 +262,7 @@ class GetCompletedCoursesTestCase(TestCase):
         ])
 
 
-@attr('shard_2')
+@attr(shard=2)
 @httpretty.activate
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class TestProgramProgressMeter(ProgramsApiConfigMixin, TestCase):
diff --git a/openedx/core/djangoapps/safe_sessions/tests/test_middleware.py b/openedx/core/djangoapps/safe_sessions/tests/test_middleware.py
index 50e5859ba46..cfc1a91cb11 100644
--- a/openedx/core/djangoapps/safe_sessions/tests/test_middleware.py
+++ b/openedx/core/djangoapps/safe_sessions/tests/test_middleware.py
@@ -31,7 +31,7 @@ def create_mock_request():
     return request
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestSafeSessionProcessRequest(TestSafeSessionsLogMixin, TestCase):
     """
     Test class for SafeSessionMiddleware.process_request
@@ -132,7 +132,7 @@ class TestSafeSessionProcessRequest(TestSafeSessionsLogMixin, TestCase):
         self.assert_user_in_session()
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestSafeSessionProcessResponse(TestSafeSessionsLogMixin, TestCase):
     """
@@ -235,7 +235,7 @@ class TestSafeSessionProcessResponse(TestSafeSessionsLogMixin, TestCase):
         self.assert_response_with_delete_cookie()
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestSafeSessionMiddleware(TestSafeSessionsLogMixin, TestCase):
     """
diff --git a/openedx/core/djangoapps/safe_sessions/tests/test_safe_cookie_data.py b/openedx/core/djangoapps/safe_sessions/tests/test_safe_cookie_data.py
index d12dfc43d08..016d80521a1 100644
--- a/openedx/core/djangoapps/safe_sessions/tests/test_safe_cookie_data.py
+++ b/openedx/core/djangoapps/safe_sessions/tests/test_safe_cookie_data.py
@@ -14,7 +14,7 @@ from ..middleware import SafeCookieData, SafeCookieError
 from .test_utils import TestSafeSessionsLogMixin
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestSafeCookieData(TestSafeSessionsLogMixin, TestCase):
     """
diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
index 6acd35df97f..348f636db29 100644
--- a/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
+++ b/openedx/core/djangoapps/user_api/accounts/tests/test_api.py
@@ -34,7 +34,7 @@ def mock_render_to_string(template_name, context):
     return str((template_name, sorted(context.iteritems())))
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Account APIs are only supported in LMS')
 class TestAccountApi(UserSettingsEventTestMixin, TestCase):
     """
@@ -232,7 +232,7 @@ class TestAccountApi(UserSettingsEventTestMixin, TestCase):
         verify_event_emitted([], [{"code": "en"}, {"code": "fr"}])
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch('openedx.core.djangoapps.user_api.accounts.image_helpers._PROFILE_IMAGE_SIZES', [50, 10])
 @patch.dict(
     'openedx.core.djangoapps.user_api.accounts.image_helpers.PROFILE_IMAGE_SIZES_MAP',
@@ -286,7 +286,7 @@ class AccountSettingsOnCreationTest(TestCase):
         })
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class AccountCreationActivationAndPasswordChangeTest(TestCase):
     """
diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py b/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py
index 5c8510b341b..6197d1eb620 100644
--- a/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py
+++ b/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py
@@ -18,7 +18,7 @@ TEST_SIZES = {'full': 50, 'small': 10}
 TEST_PROFILE_IMAGE_UPLOAD_DT = datetime.datetime(2002, 1, 9, 15, 43, 01, tzinfo=UTC)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @patch.dict('openedx.core.djangoapps.user_api.accounts.image_helpers.PROFILE_IMAGE_SIZES_MAP', TEST_SIZES, clear=True)
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class ProfileImageUrlTestCase(TestCase):
diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py
index 834afc4047a..720b8553953 100644
--- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py
+++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py
@@ -121,7 +121,7 @@ class UserAPITestCase(APITestCase):
     {'full': 50, 'small': 10},
     clear=True
 )
-@attr('shard_2')
+@attr(shard=2)
 class TestAccountAPI(CacheIsolationTestCase, UserAPITestCase):
     """
     Unit tests for the Account API.
@@ -728,7 +728,7 @@ class TestAccountAPI(CacheIsolationTestCase, UserAPITestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class TestAccountAPITransactions(TransactionTestCase):
     """
diff --git a/openedx/core/djangoapps/user_api/course_tag/tests/test_api.py b/openedx/core/djangoapps/user_api/course_tag/tests/test_api.py
index 83a75469e83..353a5a80313 100644
--- a/openedx/core/djangoapps/user_api/course_tag/tests/test_api.py
+++ b/openedx/core/djangoapps/user_api/course_tag/tests/test_api.py
@@ -9,7 +9,7 @@ from openedx.core.djangoapps.user_api.course_tag import api as course_tag_api
 from opaque_keys.edx.locations import SlashSeparatedCourseKey
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestCourseTagAPI(TestCase):
     """
     Test the user service
diff --git a/openedx/core/djangoapps/user_api/management/tests/test_email_opt_in_list.py b/openedx/core/djangoapps/user_api/management/tests/test_email_opt_in_list.py
index ab6b71c5f16..a9eac55811e 100644
--- a/openedx/core/djangoapps/user_api/management/tests/test_email_opt_in_list.py
+++ b/openedx/core/djangoapps/user_api/management/tests/test_email_opt_in_list.py
@@ -23,7 +23,7 @@ from openedx.core.djangoapps.user_api.models import UserOrgTag
 from openedx.core.djangoapps.user_api.management.commands import email_opt_in_list
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 @skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
 class EmailOptInListTest(ModuleStoreTestCase):
diff --git a/openedx/core/djangoapps/user_api/preferences/tests/test_api.py b/openedx/core/djangoapps/user_api/preferences/tests/test_api.py
index 1fc1277a3f3..c294b285a13 100644
--- a/openedx/core/djangoapps/user_api/preferences/tests/test_api.py
+++ b/openedx/core/djangoapps/user_api/preferences/tests/test_api.py
@@ -40,7 +40,7 @@ from ...preferences.api import (
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Account APIs are only supported in LMS')
 class TestPreferenceAPI(TestCase):
     """
@@ -329,7 +329,7 @@ class TestPreferenceAPI(TestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class UpdateEmailOptInTests(ModuleStoreTestCase):
     """
diff --git a/openedx/core/djangolib/tests/test_js_utils.py b/openedx/core/djangolib/tests/test_js_utils.py
index d99e8b1a88f..1de4a91df4e 100644
--- a/openedx/core/djangolib/tests/test_js_utils.py
+++ b/openedx/core/djangolib/tests/test_js_utils.py
@@ -14,7 +14,7 @@ from openedx.core.djangolib.js_utils import (
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestJSUtils(TestCase):
     """
     Test JS utils
diff --git a/openedx/core/djangolib/tests/test_markup.py b/openedx/core/djangolib/tests/test_markup.py
index f410a91c9ef..861d7ca5368 100644
--- a/openedx/core/djangolib/tests/test_markup.py
+++ b/openedx/core/djangolib/tests/test_markup.py
@@ -13,7 +13,7 @@ from mako.template import Template
 from openedx.core.djangolib.markup import HTML, Text
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class FormatHtmlTest(unittest.TestCase):
     """Test that we can format plain strings and HTML into them properly."""
diff --git a/openedx/core/lib/api/tests/test_authentication.py b/openedx/core/lib/api/tests/test_authentication.py
index 0b29898a9af..ad3f2f8ffb5 100644
--- a/openedx/core/lib/api/tests/test_authentication.py
+++ b/openedx/core/lib/api/tests/test_authentication.py
@@ -71,7 +71,7 @@ urlpatterns = patterns(
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class OAuth2Tests(TestCase):
     """OAuth 2.0 authentication"""
diff --git a/openedx/core/lib/api/tests/test_exceptions.py b/openedx/core/lib/api/tests/test_exceptions.py
index e354d06400f..b87e94c73ab 100644
--- a/openedx/core/lib/api/tests/test_exceptions.py
+++ b/openedx/core/lib/api/tests/test_exceptions.py
@@ -9,7 +9,7 @@ from rest_framework import exceptions as drf_exceptions
 from .. import exceptions
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestDictExceptionsAllowDictDetails(TestCase):
     """
@@ -46,7 +46,7 @@ class TestDictExceptionsAllowDictDetails(TestCase):
         self.assertEqual(exc.available_renderers, ['application/json'])
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestDictExceptionSubclassing(TestCase):
     """
diff --git a/openedx/core/lib/api/tests/test_paginators.py b/openedx/core/lib/api/tests/test_paginators.py
index dfdc3aaba9e..0111c9ef205 100644
--- a/openedx/core/lib/api/tests/test_paginators.py
+++ b/openedx/core/lib/api/tests/test_paginators.py
@@ -13,7 +13,7 @@ from rest_framework import serializers
 from openedx.core.lib.api.paginators import NamespacedPageNumberPagination, paginate_search_results
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class PaginateSearchResultsTestCase(TestCase):
     """Test cases for paginate_search_results method"""
@@ -124,7 +124,7 @@ class PaginateSearchResultsTestCase(TestCase):
             paginate_search_results(self.mock_model, self.search_results, self.default_size, page_num)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class NamespacedPaginationTestCase(TestCase):
     """
     Test behavior of `NamespacedPageNumberPagination`
diff --git a/openedx/core/lib/api/tests/test_parsers.py b/openedx/core/lib/api/tests/test_parsers.py
index fb6722bc462..fb0b4b4cdd8 100644
--- a/openedx/core/lib/api/tests/test_parsers.py
+++ b/openedx/core/lib/api/tests/test_parsers.py
@@ -12,7 +12,7 @@ from rest_framework.test import APITestCase, APIRequestFactory
 from openedx.core.lib.api import parsers
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestTypedFileUploadParser(APITestCase):
     """
     Tests that verify the behavior of TypedFileUploadParser
diff --git a/openedx/core/lib/api/tests/test_permissions.py b/openedx/core/lib/api/tests/test_permissions.py
index bc121d32f5e..d0142c73f86 100644
--- a/openedx/core/lib/api/tests/test_permissions.py
+++ b/openedx/core/lib/api/tests/test_permissions.py
@@ -30,7 +30,7 @@ class TestCcxObject(TestObject):
         self.coach = user
 
 
-@attr('shard_2')
+@attr(shard=2)
 class IsCourseStaffInstructorTests(TestCase):
     """ Test for IsCourseStaffInstructor permission class. """
 
@@ -64,7 +64,7 @@ class IsCourseStaffInstructorTests(TestCase):
         self.assertFalse(self.permission.has_object_permission(self.request, None, self.obj))
 
 
-@attr('shard_2')
+@attr(shard=2)
 class IsMasterCourseStaffInstructorTests(TestCase):
     """ Test for IsMasterCourseStaffInstructorTests permission class. """
 
@@ -109,7 +109,7 @@ class IsMasterCourseStaffInstructorTests(TestCase):
             self.permission.has_permission(post_request, None)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class IsStaffOrOwnerTests(TestCase):
     """ Tests for IsStaffOrOwner permission class. """
diff --git a/openedx/core/lib/block_structure/tests/test_block_structure.py b/openedx/core/lib/block_structure/tests/test_block_structure.py
index 19b5bb91bf8..d56820ad09a 100644
--- a/openedx/core/lib/block_structure/tests/test_block_structure.py
+++ b/openedx/core/lib/block_structure/tests/test_block_structure.py
@@ -16,7 +16,7 @@ from ..exceptions import TransformerException
 from .helpers import MockXBlock, MockTransformer, ChildrenMapTestMixin
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestBlockStructure(TestCase, ChildrenMapTestMixin):
     """
@@ -45,7 +45,7 @@ class TestBlockStructure(TestCase, ChildrenMapTestMixin):
         self.assertNotIn(len(children_map) + 1, block_structure)
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestBlockStructureData(TestCase, ChildrenMapTestMixin):
     """
diff --git a/openedx/core/lib/block_structure/tests/test_cache.py b/openedx/core/lib/block_structure/tests/test_cache.py
index fce8094cd97..64221cc8588 100644
--- a/openedx/core/lib/block_structure/tests/test_cache.py
+++ b/openedx/core/lib/block_structure/tests/test_cache.py
@@ -8,7 +8,7 @@ from ..cache import BlockStructureCache
 from .helpers import ChildrenMapTestMixin, MockCache, MockTransformer
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestBlockStructureCache(ChildrenMapTestMixin, TestCase):
     """
     Tests for BlockStructureCache
diff --git a/openedx/core/lib/block_structure/tests/test_factory.py b/openedx/core/lib/block_structure/tests/test_factory.py
index 1530f658814..6298ecd6e8d 100644
--- a/openedx/core/lib/block_structure/tests/test_factory.py
+++ b/openedx/core/lib/block_structure/tests/test_factory.py
@@ -12,7 +12,7 @@ from .helpers import (
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestBlockStructureFactory(TestCase, ChildrenMapTestMixin):
     """
     Tests for BlockStructureFactory
diff --git a/openedx/core/lib/block_structure/tests/test_manager.py b/openedx/core/lib/block_structure/tests/test_manager.py
index 9ee7f762bc5..ccdab596602 100644
--- a/openedx/core/lib/block_structure/tests/test_manager.py
+++ b/openedx/core/lib/block_structure/tests/test_manager.py
@@ -85,7 +85,7 @@ class TestTransformer1(MockTransformer):
         return data_key + 't1.val1.' + unicode(block_key)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestBlockStructureManager(TestCase, ChildrenMapTestMixin):
     """
     Test class for BlockStructureManager.
diff --git a/openedx/core/lib/block_structure/tests/test_transformer_registry.py b/openedx/core/lib/block_structure/tests/test_transformer_registry.py
index 5e6db93cff4..59e5340c14d 100644
--- a/openedx/core/lib/block_structure/tests/test_transformer_registry.py
+++ b/openedx/core/lib/block_structure/tests/test_transformer_registry.py
@@ -31,7 +31,7 @@ class UnregisteredTestTransformer3(MockTransformer):
     pass
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TransformerRegistryTestCase(TestCase):
     """
diff --git a/openedx/core/lib/block_structure/tests/test_transformers.py b/openedx/core/lib/block_structure/tests/test_transformers.py
index 5f5964b975e..5f712ee343b 100644
--- a/openedx/core/lib/block_structure/tests/test_transformers.py
+++ b/openedx/core/lib/block_structure/tests/test_transformers.py
@@ -13,7 +13,7 @@ from .helpers import (
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestBlockStructureTransformers(ChildrenMapTestMixin, TestCase):
     """
     Test class for testing BlockStructureTransformers
diff --git a/openedx/core/lib/gating/tests/test_api.py b/openedx/core/lib/gating/tests/test_api.py
index 35826329a2d..6764abe78a3 100644
--- a/openedx/core/lib/gating/tests/test_api.py
+++ b/openedx/core/lib/gating/tests/test_api.py
@@ -14,7 +14,7 @@ from openedx.core.lib.gating.exceptions import GatingValidationError
 from student.tests.factories import UserFactory
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt
 @patch.dict('django.conf.settings.FEATURES', {'MILESTONES_APP': True})
 class TestGatingApi(ModuleStoreTestCase, MilestonesTestCaseMixin):
diff --git a/openedx/core/lib/tests/test_course_tab_api.py b/openedx/core/lib/tests/test_course_tab_api.py
index ffd42328a41..54b7639f44c 100644
--- a/openedx/core/lib/tests/test_course_tab_api.py
+++ b/openedx/core/lib/tests/test_course_tab_api.py
@@ -9,7 +9,7 @@ from openedx.core.lib.api.plugins import PluginError
 from openedx.core.lib.course_tabs import CourseTabPluginManager
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestPluginApi(TestCase):
     """
     Unit tests for the plugin API
diff --git a/openedx/core/lib/tests/test_course_tabs.py b/openedx/core/lib/tests/test_course_tabs.py
index c11619369b5..a1929706d53 100644
--- a/openedx/core/lib/tests/test_course_tabs.py
+++ b/openedx/core/lib/tests/test_course_tabs.py
@@ -9,7 +9,7 @@ import xmodule.tabs as xmodule_tabs
 from openedx.core.lib.course_tabs import CourseTabPluginManager
 
 
-@attr('shard_2')
+@attr(shard=2)
 class CourseTabPluginManagerTestCase(TestCase):
     """Test cases for CourseTabPluginManager class"""
 
@@ -39,7 +39,7 @@ class CourseTabPluginManagerTestCase(TestCase):
         )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class KeyCheckerTestCase(TestCase):
     """Test cases for KeyChecker class"""
 
@@ -58,7 +58,7 @@ class KeyCheckerTestCase(TestCase):
             xmodule_tabs.key_checker(self.invalid_keys)(self.dict_value)
 
 
-@attr('shard_2')
+@attr(shard=2)
 class NeedNameTestCase(TestCase):
     """Test cases for NeedName validator"""
 
diff --git a/openedx/core/lib/tests/test_courses.py b/openedx/core/lib/tests/test_courses.py
index 45a8b47cdba..277fa1472c3 100644
--- a/openedx/core/lib/tests/test_courses.py
+++ b/openedx/core/lib/tests/test_courses.py
@@ -13,7 +13,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from ..courses import course_image_url
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class CourseImageTestCase(ModuleStoreTestCase):
     """Tests for course image URLs."""
diff --git a/openedx/core/lib/tests/test_edx_api_utils.py b/openedx/core/lib/tests/test_edx_api_utils.py
index a81d300b121..9f012233035 100644
--- a/openedx/core/lib/tests/test_edx_api_utils.py
+++ b/openedx/core/lib/tests/test_edx_api_utils.py
@@ -22,7 +22,7 @@ TEST_API_URL = 'http://www-internal.example.com/api'
 TEST_API_SIGNING_KEY = 'edx'
 
 
-@attr('shard_2')
+@attr(shard=2)
 @httpretty.activate
 class TestGetEdxApiData(ProgramsApiConfigMixin, CacheIsolationTestCase):
     """Tests for edX API data retrieval utility."""
diff --git a/openedx/core/lib/tests/test_graph_traversals.py b/openedx/core/lib/tests/test_graph_traversals.py
index 3e9ac96ee2d..a1867d477f4 100644
--- a/openedx/core/lib/tests/test_graph_traversals.py
+++ b/openedx/core/lib/tests/test_graph_traversals.py
@@ -11,7 +11,7 @@ from ..graph_traversals import (
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 class TestGraphTraversals(TestCase):
     """
     Test Class for graph traversal generator functions.
diff --git a/openedx/core/lib/tests/test_token_utils.py b/openedx/core/lib/tests/test_token_utils.py
index e027e44354c..0841a938106 100644
--- a/openedx/core/lib/tests/test_token_utils.py
+++ b/openedx/core/lib/tests/test_token_utils.py
@@ -9,7 +9,7 @@ from openedx.core.lib.token_utils import JwtBuilder
 from student.tests.factories import UserFactory, UserProfileFactory
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestJwtBuilder(mixins.AccessTokenMixin, TestCase):
     """
diff --git a/openedx/core/lib/tests/test_xblock_utils.py b/openedx/core/lib/tests/test_xblock_utils.py
index 39441919775..4a6180f55a0 100644
--- a/openedx/core/lib/tests/test_xblock_utils.py
+++ b/openedx/core/lib/tests/test_xblock_utils.py
@@ -26,7 +26,7 @@ from openedx.core.lib.xblock_utils import (
 )
 
 
-@attr('shard_2')
+@attr(shard=2)
 @ddt.ddt
 class TestXblockUtils(SharedModuleStoreTestCase):
     """
diff --git a/openedx/tests/xblock_integration/test_crowdsource_hinter.py b/openedx/tests/xblock_integration/test_crowdsource_hinter.py
index 28deb268a2c..eaf9a247536 100644
--- a/openedx/tests/xblock_integration/test_crowdsource_hinter.py
+++ b/openedx/tests/xblock_integration/test_crowdsource_hinter.py
@@ -137,7 +137,7 @@ class TestCrowdsourceHinter(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
         self.assert_request_status_code(200, self.course_url)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestHinterFunctions(TestCrowdsourceHinter):
     """
     Check that the essential functions of the hinter work as expected.
diff --git a/openedx/tests/xblock_integration/test_recommender.py b/openedx/tests/xblock_integration/test_recommender.py
index 6a3e614af99..cda8ca823eb 100644
--- a/openedx/tests/xblock_integration/test_recommender.py
+++ b/openedx/tests/xblock_integration/test_recommender.py
@@ -197,7 +197,7 @@ class TestRecommender(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
         self.assert_request_status_code(200, self.course_url)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestRecommenderCreateFromEmpty(TestRecommender):
     """
     Check whether we can add resources to an empty database correctly
@@ -224,7 +224,7 @@ class TestRecommenderCreateFromEmpty(TestRecommender):
                 self.assert_request_status_code(200, self.course_url)
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestRecommenderResourceBase(TestRecommender):
     """Base helper class for tests with resources."""
     def setUp(self):
@@ -257,7 +257,7 @@ class TestRecommenderResourceBase(TestRecommender):
         return resource
 
 
-@attr('shard_1')
+@attr(shard=1)
 class TestRecommenderWithResources(TestRecommenderResourceBase):
     """
     Check whether we can add/edit/flag/export resources correctly
@@ -422,7 +422,7 @@ class TestRecommenderWithResources(TestRecommenderResourceBase):
         self.assert_request_status_code(200, self.course_url)
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt
 class TestRecommenderVoteWithResources(TestRecommenderResourceBase):
     """
@@ -536,7 +536,7 @@ class TestRecommenderVoteWithResources(TestRecommenderResourceBase):
         self.check_event_response_by_key('handle_vote', resource, 'newVotes', test_case['new_votes'])
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt
 class TestRecommenderStaffFeedbackWithResources(TestRecommenderResourceBase):
     """
@@ -631,7 +631,7 @@ class TestRecommenderStaffFeedbackWithResources(TestRecommenderResourceBase):
         self.check_event_response_by_http_status(test_case['handler'], resource, test_case['status'])
 
 
-@attr('shard_1')
+@attr(shard=1)
 @ddt
 class TestRecommenderFileUploading(TestRecommender):
     """
diff --git a/scripts/generic-ci-tests.sh b/scripts/generic-ci-tests.sh
index 1d5fc916ce0..fdb64521019 100755
--- a/scripts/generic-ci-tests.sh
+++ b/scripts/generic-ci-tests.sh
@@ -29,10 +29,9 @@ set -e
 #   `SHARD` is a number indicating which subset of the tests to build.
 #
 #       For "bok-choy" and "lms-unit", the tests are put into shard groups
-#       using the nose'attr' decorator (e.g. "@attr('shard_1')"). Anything with
-#       the 'shard_n' attribute will run in the nth shard. If there isn't a
-#       shard explicitly assigned, the test will run in the last shard (the one
-#       with the highest number).
+#       using the nose 'attr' decorator (e.g. "@attr(shard=1)"). Anything with
+#       the 'shard=n' attribute will run in the nth shard. If there isn't a
+#       shard explicitly assigned, the test will run in the last shard.
 #
 #   Jenkins-specific configuration details:
 #
@@ -105,17 +104,11 @@ case "$TEST_SUITE" in
             "all")
                 paver test_system -s lms $PAVER_ARGS
                 ;;
-            "1")
-                paver test_system -s lms --attr='shard_1' $PAVER_ARGS
+            [1-3])
+                paver test_system -s lms --attr="shard=$SHARD" $PAVER_ARGS
                 ;;
-            "2")
-                paver test_system -s lms --attr='shard_2' $PAVER_ARGS
-                ;;
-            "3")
-                paver test_system -s lms --attr='shard_3' $PAVER_ARGS
-                ;;
-            "4")
-                paver test_system -s lms --attr='shard_1=False,shard_2=False,shard_3=False' $PAVER_ARGS
+            4|"noshard")
+                paver test_system -s lms --attr='!shard' $PAVER_ARGS
                 ;;
             *)
                 # If no shard is specified, rather than running all tests, create an empty xunit file. This is a
@@ -183,40 +176,12 @@ case "$TEST_SUITE" in
                 paver test_bokchoy $PAVER_ARGS
                 ;;
 
-            "1")
-                paver test_bokchoy --attr='shard_1' $PAVER_ARGS
-                ;;
-
-            "2")
-                paver test_bokchoy --attr='shard_2' $PAVER_ARGS
-                ;;
-
-            "3")
-                paver test_bokchoy --attr='shard_3' $PAVER_ARGS
-                ;;
-
-            "4")
-                paver test_bokchoy --attr='shard_4' $PAVER_ARGS
-                ;;
-
-            "5")
-                paver test_bokchoy --attr='shard_5' $PAVER_ARGS
-                ;;
-
-            "6")
-                paver test_bokchoy --attr='shard_6' $PAVER_ARGS
-                ;;
-
-            "7")
-                paver test_bokchoy --attr='shard_7' $PAVER_ARGS
-                ;;
-
-            "8")
-                paver test_bokchoy --attr='shard_8' $PAVER_ARGS
+            [1-8])
+                paver test_bokchoy --attr="shard=$SHARD" $PAVER_ARGS
                 ;;
 
-            "9")
-                paver test_bokchoy --attr='shard_1=False,shard_2=False,shard_3=False,shard_4=False,shard_5=False,shard_6=False,shard_7=False,shard_8=False,a11y=False' $PAVER_ARGS
+            9|"noshard")
+                paver test_bokchoy --attr='!shard,a11y=False' $PAVER_ARGS
                 ;;
 
             # Default case because if we later define another bok-choy shard on Jenkins
-- 
GitLab