Skip to content

Commit

Permalink
Adding unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
aliabbasrizvi committed Dec 15, 2017
1 parent e6ee495 commit f99e24f
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 13 deletions.
3 changes: 2 additions & 1 deletion optimizely/decision_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ def __init__(self, config, user_profile_service):
self.config = config
self.logger = config.logger

def _get_bucketing_id(self, user_id, attributes):
@staticmethod
def _get_bucketing_id(user_id, attributes):
""" Helper method to determine bucketing ID for the user.
Args:
Expand Down
106 changes: 94 additions & 12 deletions tests/test_decision_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ def setUp(self):
# Set UserProfileService for the purposes of testing
self.decision_service.user_profile_service = user_profile.UserProfileService()

def test_get_bucketing_id__no_bucketing_id_attribute(self):
""" Test that _get_bucketing_id returns correct bucketing ID when there is no bucketing ID attribute. """

# No attributes
self.assertEqual('test_user', decision_service.DecisionService._get_bucketing_id('test_user', None))

# With attributes, but no bucketing ID
self.assertEqual('test_user', decision_service.DecisionService._get_bucketing_id('test_user',
{'random_key': 'random_value'}))

def test_get_bucketing_id__bucketing_id_attribute(self):
""" Test that _get_bucketing_id returns correct bucketing ID when there is bucketing ID attribute. """

self.assertEqual('user_bucket_value',
decision_service.DecisionService._get_bucketing_id('test_user',
{'$opt_bucketing_id': 'user_bucket_value'}))

def test_get_forced_variation__user_in_forced_variation(self):
""" Test that expected variation is returned if user is forced in a variation. """

Expand Down Expand Up @@ -95,6 +112,22 @@ def test_get_variation__experiment_not_running(self):
self.assertEqual(0, mock_lookup.call_count)
self.assertEqual(0, mock_save.call_count)

def test_get_variation__bucketing_id_provided(self):
""" Test that get_variation calls bucket with correct bucketing ID if provided. """

experiment = self.project_config.get_experiment_from_key('test_experiment')
with mock.patch('optimizely.decision_service.DecisionService.get_forced_variation', return_value=None), \
mock.patch('optimizely.decision_service.DecisionService.get_stored_variation', return_value=None), \
mock.patch('optimizely.helpers.audience.is_user_in_experiment', return_value=True), \
mock.patch('optimizely.bucketer.Bucketer.bucket') as mock_bucket:
self.decision_service.get_variation(experiment,
'test_user',
{'random_key': 'random_value',
'$opt_bucketing_id': 'user_bucket_value'})

# Assert that bucket is called with appropriate bucketing ID
mock_bucket.assert_called_once_with(experiment, 'test_user', 'user_bucket_value')

def test_get_variation__user_forced_in_variation(self):
""" Test that get_variation returns forced variation if user is forced in a variation. """

Expand Down Expand Up @@ -350,6 +383,55 @@ def test_get_variation_for_rollout__returns_none_if_no_experiments(self, mock_lo
# Assert no log messages were generated
self.assertEqual(0, mock_logging.call_count)

def test_get_variation_for_rollout__returns_decision_if_user_in_rollout(self, mock_logging):
""" Test that get_variation_for_rollout returns Decision with experiment/variation
if user meets targeting conditions for a rollout rule. """

rollout = self.project_config.get_rollout_from_id('211111')

with mock.patch('optimizely.helpers.audience.is_user_in_experiment', return_value=True),\
mock.patch('optimizely.bucketer.Bucketer.bucket',
return_value=self.project_config.get_variation_from_id('211127', '211129')) as mock_bucket:
self.assertEqual(decision_service.Decision(self.project_config.get_experiment_from_id('211127'),
self.project_config.get_variation_from_id('211127', '211129'),
decision_service.DECISION_SOURCE_ROLLOUT),
self.decision_service.get_variation_for_rollout(rollout, 'test_user'))

# Check all log messages
self.assertEqual(
[mock.call(enums.LogLevels.DEBUG, 'User "test_user" meets conditions for targeting rule 1.'),
mock.call(enums.LogLevels.DEBUG, 'User "test_user" is in variation 211129 of experiment 211127.')
], mock_logging.call_args_list)

# Check that bucket is called with correct parameters
mock_bucket.assert_called_once_with(self.project_config.get_experiment_from_id('211127'), 'test_user', 'test_user')

def test_get_variation_for_rollout__calls_bucket_with_bucketing_id(self, mock_logging):
""" Test that get_variation_for_rollout calls Bucketer.bucket with bucketing ID when provided. """

rollout = self.project_config.get_rollout_from_id('211111')

with mock.patch('optimizely.helpers.audience.is_user_in_experiment', return_value=True),\
mock.patch('optimizely.bucketer.Bucketer.bucket',
return_value=self.project_config.get_variation_from_id('211127', '211129')) as mock_bucket:
self.assertEqual(decision_service.Decision(self.project_config.get_experiment_from_id('211127'),
self.project_config.get_variation_from_id('211127', '211129'),
decision_service.DECISION_SOURCE_ROLLOUT),
self.decision_service.get_variation_for_rollout(rollout,
'test_user',
{'$opt_bucketing_id': 'user_bucket_value'}))

# Check all log messages
self.assertEqual(
[mock.call(enums.LogLevels.DEBUG, 'User "test_user" meets conditions for targeting rule 1.'),
mock.call(enums.LogLevels.DEBUG, 'User "test_user" is in variation 211129 of experiment 211127.')
], mock_logging.call_args_list)

# Check that bucket is called with correct parameters
mock_bucket.assert_called_once_with(self.project_config.get_experiment_from_id('211127'),
'test_user',
'user_bucket_value')

def test_get_variation_for_rollout__skips_to_everyone_else_rule(self, mock_logging):
""" Test that if a user is in an audience, but does not qualify
for the experiment, then it skips to the Everyone Else rule. """
Expand All @@ -376,7 +458,7 @@ def test_get_variation_for_rollout__skips_to_everyone_else_rule(self, mock_loggi
], mock_logging.call_args_list)

def test_get_variation_for_rollout__returns_none_for_user_not_in_rollout(self, mock_logging):
""" Test that get_variation_for_rollouts returns None for the user not in the associated rollout. """
""" Test that get_variation_for_rollout returns None for the user not in the associated rollout. """

rollout = self.project_config.get_rollout_from_id('211111')

Expand Down Expand Up @@ -410,15 +492,15 @@ def test_get_variation_for_feature__returns_variation_for_feature_in_experiment(
self.assertEqual(decision_service.Decision(expected_experiment,
expected_variation,
decision_service.DECISION_SOURCE_EXPERIMENT),
self.decision_service.get_variation_for_feature(feature, 'user1'))
self.decision_service.get_variation_for_feature(feature, 'test_user'))

mock_decision.assert_called_once_with(
self.project_config.get_experiment_from_key('test_experiment'), 'user1', None
self.project_config.get_experiment_from_key('test_experiment'), 'test_user', None
)

# Check log message
mock_logging.assert_called_once_with(enums.LogLevels.DEBUG,
'User "user1" is in variation variation of experiment test_experiment.')
'User "test_user" is in variation variation of experiment test_experiment.')

def test_get_variation_for_feature__returns_variation_for_feature_in_rollout(self, mock_logging):
""" Test that get_variation_for_feature returns the variation of
Expand Down Expand Up @@ -452,7 +534,7 @@ def test_get_variation_for_feature__returns_variation_if_user_not_in_experiment_
self.assertEqual(decision_service.Decision(expected_experiment,
expected_variation,
decision_service.DECISION_SOURCE_ROLLOUT),
self.decision_service.get_variation_for_feature(feature, 'user1'))
self.decision_service.get_variation_for_feature(feature, 'test_user'))

self.assertEqual(2, mock_audience_check.call_count)
mock_audience_check.assert_any_call(self.project_config,
Expand All @@ -476,10 +558,10 @@ def test_get_variation_for_feature__returns_variation_for_feature_in_group(self,
self.assertEqual(decision_service.Decision(expected_experiment,
expected_variation,
decision_service.DECISION_SOURCE_EXPERIMENT),
self.decision_service.get_variation_for_feature(feature, 'user1'))
self.decision_service.get_variation_for_feature(feature, 'test_user'))

mock_get_experiment_in_group.assert_called_once_with(self.project_config.get_group('19228'), 'user1')
mock_decision.assert_called_once_with(self.project_config.get_experiment_from_key('group_exp_1'), 'user1', None)
mock_get_experiment_in_group.assert_called_once_with(self.project_config.get_group('19228'), 'test_user')
mock_decision.assert_called_once_with(self.project_config.get_experiment_from_key('group_exp_1'), 'test_user', None)

def test_get_variation_for_feature__returns_none_for_user_not_in_group(self, _):
""" Test that get_variation_for_feature returns None for
Expand All @@ -491,9 +573,9 @@ def test_get_variation_for_feature__returns_none_for_user_not_in_group(self, _):
return_value=None) as mock_get_experiment_in_group, \
mock.patch('optimizely.decision_service.DecisionService.get_variation') as mock_decision:
self.assertEqual(decision_service.Decision(None, None, decision_service.DECISION_SOURCE_EXPERIMENT),
self.decision_service.get_variation_for_feature(feature, 'user1'))
self.decision_service.get_variation_for_feature(feature, 'test_user'))

mock_get_experiment_in_group.assert_called_once_with(self.project_config.get_group('19228'), 'user1')
mock_get_experiment_in_group.assert_called_once_with(self.project_config.get_group('19228'), 'test_user')
self.assertFalse(mock_decision.called)

def test_get_variation_for_feature__returns_none_for_user_not_in_experiment(self, _):
Expand All @@ -506,10 +588,10 @@ def test_get_variation_for_feature__returns_none_for_user_not_in_experiment(self
self.assertEqual(decision_service.Decision(expected_experiment,
None,
decision_service.DECISION_SOURCE_EXPERIMENT),
self.decision_service.get_variation_for_feature(feature, 'user1'))
self.decision_service.get_variation_for_feature(feature, 'test_user'))

mock_decision.assert_called_once_with(
self.project_config.get_experiment_from_key('test_experiment'), 'user1', None
self.project_config.get_experiment_from_key('test_experiment'), 'test_user', None
)

def test_get_variation_for_feature__returns_none_for_user_in_group_experiment_not_associated_with_feature(self, _):
Expand Down
100 changes: 100 additions & 0 deletions tests/test_optimizely.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,56 @@ def test_activate__with_attributes__audience_match__forced_bucketing(self):
self._validate_event_object(mock_dispatch_event.call_args[0][0], 'https://logx.optimizely.com/v1/events',
expected_params, 'POST', {'Content-Type': 'application/json'})

def test_activate__with_attributes__audience_match__bucketing_id_provided(self):
""" Test that activate calls dispatch_event with right params and returns expected variation
when attributes (including bucketing ID) are provided and audience conditions are met. """

with mock.patch(
'optimizely.decision_service.DecisionService.get_variation',
return_value=self.project_config.get_variation_from_id('test_experiment', '111129')) \
as mock_get_variation, \
mock.patch('time.time', return_value=42), \
mock.patch('uuid.uuid4', return_value='a68cf1ad-0393-4e18-af87-efe8f01a7c9c'), \
mock.patch('optimizely.event_dispatcher.EventDispatcher.dispatch_event') as mock_dispatch_event:
self.assertEqual('variation', self.optimizely.activate('test_experiment', 'test_user',
{'test_attribute': 'test_value',
'$opt_bucketing_id': 'user_bucket_value'}))
expected_params = {
'account_id': '12001',
'project_id': '111001',
'visitors': [{
'visitor_id': 'test_user',
'attributes': [{
'type': 'custom',
'value': 'test_value',
'entity_id': '111094',
'key': 'test_attribute'
}],
'snapshots': [{
'decisions': [{
'variation_id': '111129',
'experiment_id': '111127',
'campaign_id': '111182'
}],
'events': [{
'timestamp': 42000,
'entity_id': '111182',
'uuid': 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c',
'key': 'campaign_activated',
}]
}]
}],
'client_version': version.__version__,
'client_name': 'python-sdk',
'anonymize_ip': False
}
mock_get_variation.assert_called_once_with(self.project_config.get_experiment_from_key('test_experiment'),
'test_user', {'test_attribute': 'test_value',
'$opt_bucketing_id': 'user_bucket_value'})
self.assertEqual(1, mock_dispatch_event.call_count)
self._validate_event_object(mock_dispatch_event.call_args[0][0], 'https://logx.optimizely.com/v1/events',
expected_params, 'POST', {'Content-Type': 'application/json'})

def test_activate__with_attributes__no_audience_match(self):
""" Test that activate returns None when audience conditions do not match. """

Expand Down Expand Up @@ -705,6 +755,56 @@ def test_track__with_attributes(self):
self._validate_event_object(mock_dispatch_event.call_args[0][0], 'https://logx.optimizely.com/v1/events',
expected_params, 'POST', {'Content-Type': 'application/json'})

def test_track__with_attributes__bucketing_id_provided(self):
""" Test that track calls dispatch_event with right params when
attributes (including bucketing ID) are provided. """

with mock.patch('optimizely.decision_service.DecisionService.get_variation',
return_value=self.project_config.get_variation_from_id(
'test_experiment', '111128'
)) as mock_get_variation, \
mock.patch('time.time', return_value=42), \
mock.patch('uuid.uuid4', return_value='a68cf1ad-0393-4e18-af87-efe8f01a7c9c'), \
mock.patch('optimizely.event_dispatcher.EventDispatcher.dispatch_event') as mock_dispatch_event:
self.optimizely.track('test_event', 'test_user', attributes={'test_attribute': 'test_value',
'$opt_bucketing_id': 'user_bucket_value'})

expected_params = {
'account_id': '12001',
'project_id': '111001',
'visitors': [{
'visitor_id': 'test_user',
'attributes': [{
'type': 'custom',
'value': 'test_value',
'entity_id': '111094',
'key': 'test_attribute'
}],
'snapshots': [{
'decisions': [{
'variation_id': '111128',
'experiment_id': '111127',
'campaign_id': '111182'
}],
'events': [{
'timestamp': 42000,
'entity_id': '111095',
'uuid': 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c',
'key': 'test_event',
}]
}]
}],
'client_version': version.__version__,
'client_name': 'python-sdk',
'anonymize_ip': False
}
mock_get_variation.assert_called_once_with(self.project_config.get_experiment_from_key('test_experiment'),
'test_user', {'test_attribute': 'test_value',
'$opt_bucketing_id': 'user_bucket_value'})
self.assertEqual(1, mock_dispatch_event.call_count)
self._validate_event_object(mock_dispatch_event.call_args[0][0], 'https://logx.optimizely.com/v1/events',
expected_params, 'POST', {'Content-Type': 'application/json'})

def test_track__with_attributes__no_audience_match(self):
""" Test that track does not call dispatch_event when audience conditions do not match. """

Expand Down

0 comments on commit f99e24f

Please sign in to comment.