Add ExperimentWaffleFlag
This is a helpful class when running an experiment, to help bucket users and keep track of which enrollments to consider as part of the experiment. AA-53
This commit is contained in:
122
lms/djangoapps/experiments/tests/test_flags.py
Normal file
122
lms/djangoapps/experiments/tests/test_flags.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""
|
||||
Tests for experimentation feature flags
|
||||
"""
|
||||
|
||||
import pytz
|
||||
|
||||
import ddt
|
||||
from crum import set_current_request
|
||||
from dateutil import parser
|
||||
from django.test.client import RequestFactory
|
||||
from edx_django_utils.cache import RequestCache
|
||||
from mock import patch
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from experiments.factories import ExperimentKeyValueFactory
|
||||
from experiments.flags import ExperimentWaffleFlag
|
||||
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class ExperimentWaffleFlagTests(SharedModuleStoreTestCase):
|
||||
""" Tests for the ExperimentWaffleFlag class """
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.user = UserFactory()
|
||||
|
||||
self.request = RequestFactory().request()
|
||||
self.request.session = {}
|
||||
self.request.site = SiteFactory()
|
||||
self.request.user = self.user
|
||||
self.addCleanup(set_current_request, None)
|
||||
set_current_request(self.request)
|
||||
|
||||
self.flag = ExperimentWaffleFlag('experiments', 'test', num_buckets=2, experiment_id=0)
|
||||
self.key = CourseKey.from_string('a/b/c')
|
||||
|
||||
bucket_patch = patch('experiments.flags.stable_bucketing_hash_group', return_value=1)
|
||||
self.addCleanup(bucket_patch.stop)
|
||||
bucket_patch.start()
|
||||
|
||||
self.addCleanup(RequestCache.clear_all_namespaces)
|
||||
|
||||
def get_bucket(self, track=False, active=True):
|
||||
with self.flag.override(active=active):
|
||||
return self.flag.get_bucket(course_key=self.key, track=track)
|
||||
|
||||
def test_basic_happy_path(self):
|
||||
self.assertEqual(self.get_bucket(), 1)
|
||||
|
||||
def test_no_request(self):
|
||||
set_current_request(None)
|
||||
self.assertEqual(self.get_bucket(), 0)
|
||||
|
||||
def test_not_enabled(self):
|
||||
self.assertEqual(self.get_bucket(active=False), 0)
|
||||
|
||||
@ddt.data(
|
||||
('2012-01-06', None, 1), # no enrollment (we allow normal bucketing in this case)
|
||||
('2012-01-06', '2012-01-05', 0), # enrolled before experiment
|
||||
('2012-01-06', '2012-01-07', 1), # enrolled after experiment
|
||||
(None, '2012-01-07', 1), # no experiment date
|
||||
('not-a-date', '2012-01-07', 0), # bad experiment date
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_enrollment_start(self, experiment_start, enrollment_created, expected_bucket):
|
||||
if enrollment_created:
|
||||
enrollment = CourseEnrollmentFactory(user=self.user, course_id='a/b/c')
|
||||
enrollment.created = parser.parse(enrollment_created).replace(tzinfo=pytz.UTC)
|
||||
enrollment.save()
|
||||
if experiment_start:
|
||||
ExperimentKeyValueFactory(experiment_id=0, key='enrollment_start', value=experiment_start)
|
||||
self.assertEqual(self.get_bucket(), expected_bucket)
|
||||
|
||||
@ddt.data(
|
||||
(True, 0),
|
||||
(False, 1),
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_bucket_override(self, active, expected_bucket):
|
||||
bucket_flag = CourseWaffleFlag('experiments', 'test.0')
|
||||
with bucket_flag.override(active=active):
|
||||
self.assertEqual(self.get_bucket(), expected_bucket)
|
||||
|
||||
def test_tracking(self):
|
||||
# Run twice, with same request
|
||||
with patch('experiments.flags.segment') as segment_mock:
|
||||
self.assertEqual(self.get_bucket(track=True), 1)
|
||||
RequestCache.clear_all_namespaces() # we want to force get_bucket to check session, not early exit
|
||||
self.assertEqual(self.get_bucket(track=True), 1)
|
||||
|
||||
# Now test that we only sent the signal once, and with the correct properties
|
||||
self.assertEqual(segment_mock.track.call_count, 1)
|
||||
self.assertEqual(segment_mock.track.call_args, ((), {
|
||||
'user_id': self.user.id,
|
||||
'event_name': 'edx.bi.experiment.user.bucketed',
|
||||
'properties': {
|
||||
'site': self.request.site.domain,
|
||||
'app_label': 'experiments',
|
||||
'experiment': 'test',
|
||||
'bucket': 1,
|
||||
'course_id': 'a/b/c',
|
||||
'is_staff': self.user.is_staff,
|
||||
},
|
||||
}))
|
||||
|
||||
def test_caching(self):
|
||||
self.assertEqual(self.get_bucket(active=True), 1)
|
||||
self.assertEqual(self.get_bucket(active=False), 1) # still returns 1!
|
||||
|
||||
def test_is_enabled(self):
|
||||
with patch('experiments.flags.ExperimentWaffleFlag.get_bucket', return_value=1):
|
||||
self.assertEqual(self.flag.is_enabled_without_course_context(), True)
|
||||
self.assertEqual(self.flag.is_enabled(self.key), True)
|
||||
self.assertEqual(self.flag.is_enabled(), True)
|
||||
with patch('experiments.flags.ExperimentWaffleFlag.get_bucket', return_value=0):
|
||||
self.assertEqual(self.flag.is_enabled_without_course_context(), False)
|
||||
self.assertEqual(self.flag.is_enabled(self.key), False)
|
||||
self.assertEqual(self.flag.is_enabled(), False)
|
||||
Reference in New Issue
Block a user