def setUp(self): self.super() self.controller = SprintController(self.env) self.team = self.teh.create_team('Test team') # Preventing a RuleValidationException (Owner not Team Member) self.teh.create_member(name='tester', team=self.team) self.sprint = self.teh.create_sprint("Test Sprint", team=self.team) self.metrics = TeamMetrics(self.env, self.sprint, self.team) self.metrics[Key.RT_USP_RATIO] = 1.5 self.metrics.save() self.bmm = BacklogModelManager(self.env) self.smm = SprintModelManager(self.env) self.sprint_backlog, self.story1, self.task1, self.task2 = \ self._build_sprint_backlog_with_tasks(self.sprint)
def list_sprint_names(self): """Returns a list of all the sprint names""" smm = SprintModelManager(self.env) return [s.name for s in smm.select()]
def delete_sprint(self, name): """Deletes the given Sprint from the environment""" smm = SprintModelManager(self.env) s = smm.get(name=name) smm.delete(s)
class SprintControllerTestForRemainingTimes(AgiloTestCase): # Most of the test cases were previously in sprint_test # (agilo.scrum.sprint.tests) However, when we moved the # functionality to a command, these test cases needed to be ported # so that they test the command instead of the direct model # implementation. Several things are still artifacts from that old # test setup. def setUp(self): self.super() self.controller = SprintController(self.env) self.team = self.teh.create_team('Test team') # Preventing a RuleValidationException (Owner not Team Member) self.teh.create_member(name='tester', team=self.team) self.sprint = self.teh.create_sprint("Test Sprint", team=self.team) self.metrics = TeamMetrics(self.env, self.sprint, self.team) self.metrics[Key.RT_USP_RATIO] = 1.5 self.metrics.save() self.bmm = BacklogModelManager(self.env) self.smm = SprintModelManager(self.env) self.sprint_backlog, self.story1, self.task1, self.task2 = \ self._build_sprint_backlog_with_tasks(self.sprint) def _build_sprint_backlog_with_tasks(self, sprint): story_props = {Key.OWNER: 'tester', Key.SPRINT: sprint.name, Key.STORY_POINTS: "8"} story = self.teh.create_ticket(Type.USER_STORY, story_props) task1 = self.teh.create_ticket(Type.TASK, {Key.OWNER: 'tester', Key.SPRINT: sprint.name, Key.REMAINING_TIME: "8"}) story.link_to(task1) task2 = self.teh.create_ticket(Type.TASK, {Key.OWNER: 'tester', Key.SPRINT: sprint.name, Key.REMAINING_TIME: "4"}) story.link_to(task2) sprint_backlog = self.bmm.get(name="Sprint Backlog", scope=sprint.name) self.assert_equals(len(sprint_backlog), 3) return (sprint_backlog, story, task1, task2) def get_total_remaining_time(self, sprint_name, day, commitment=None): cmd_class = SprintController.GetTotalRemainingTimeCommand cmd = cmd_class(self.env, sprint=sprint_name, day=day, commitment=commitment) return self.controller.process_command(cmd) def test_can_calculate_remaining_time_for_a_specific_day(self): # Set remaining time for tasks at the end of the sprint sprint = self.sprint end = sprint.end rt1 = RemainingTime(self.env, self.task1) rt2 = RemainingTime(self.env, self.task2) rt1.set_remaining_time(2, day=end) rt2.set_remaining_time(1, day=end) self.assert_equals(2, RemainingTime(self.env, self.task1).get_remaining_time(end)) self.assert_equals(1, RemainingTime(self.env, self.task2).get_remaining_time(end)) self.assert_equals(3, self.get_total_remaining_time(sprint.name, end)) def _create_remaining_time_series(self, ticket, start, time_series): rt = RemainingTime(self.env, ticket) for i, remaining_time in enumerate(time_series): day = start + (i * timedelta(days=1)) rt.set_remaining_time(remaining_time, day=day) def test_can_calculate_total_remaining_time_for_start_of_sprint(self): start = self.sprint.start self._create_remaining_time_series(self.task1, start,[12, 7.5, 3, 2.5, 0]) self._create_remaining_time_series(self.task2, start, [8, 9, 4.5, 0, 3]) total_remaining_time = self.get_total_remaining_time(self.sprint.name, start) self.assert_equals(12+8, total_remaining_time) def test_can_calculate_remaining_time_series_for_sprint(self): start = datetime(2009, 5, 11, tzinfo=utc) self.sprint.start = start self.sprint.end = datetime(2009, 5, 15, 18, 00, tzinfo=utc) self.smm.save(self.sprint) # check there is not time set right now series = self.get_remaining_times(self.sprint.name) self.assert_equals(0, sum(series)) self._create_remaining_time_series(self.task1, start,[12, 7.5, 3, 2.5, 0]) self._create_remaining_time_series(self.task2, start, [8, 9, 4.5, 0, 3]) series = self.get_remaining_times(self.sprint.name) self.assert_equals([12+8, 7.5+9, 3+4.5, 2.5+0, 0+3], series) def get_remaining_times(self, sprint_name=None, cut_to_today=False, commitment=None): if sprint_name is None: sprint_name = self.sprint.name cmd_class = SprintController.GetRemainingTimesCommand cmd = cmd_class(self.env, sprint=sprint_name, cut_to_today=cut_to_today, commitment=commitment) return self.controller.process_command(cmd) def test_compute_remaining_time_for_sprint_even_if_story_has_no_remaining_time(self): self.task1[Key.REMAINING_TIME] = 0 self.task2[Key.REMAINING_TIME] = 0 self.task1.save_changes('foo', 'bar') self.task2.save_changes('foo', 'bar') # This raised an exception before because TOTAL_REMAINING_TIME was None self.get_remaining_times() def test_use_estimated_remaining_time(self): story_props = {Key.OWNER: 'tester', Key.SPRINT: self.sprint.name, Key.STORY_POINTS: "5"} story2 = self.teh.create_ticket(Type.USER_STORY, story_props) self.assert_length(4, self.sprint_backlog) self.assert_equals(8, int(self.story1[Key.STORY_POINTS])) self.assert_equals(8 * 1.5, self.story1[Key.ESTIMATED_REMAINING_TIME]) self.assert_equals(5 * 1.5, story2[Key.ESTIMATED_REMAINING_TIME]) remaining_time = self.get_total_remaining_time(self.sprint.name, now(tz=utc)) self.assert_equals((8+4) + 5 * 1.5, remaining_time) def _close_ticket_as_fixed(self, task): task[Key.STATUS] = Status.CLOSED task[Key.RESOLUTION] = Status.RES_FIXED task.save_changes(None, None) def test_remaining_time_correct_even_for_closed_stories(self): self.sprint.start = datetime.today() - timedelta(days=5) self.sprint.save() # Store some remaining time for yesterday yesterday_midnight = datetime.combine(yesterday(tz=utc), time(tzinfo=utc)) RemainingTime(self.env, self.task1).set_remaining_time(3, yesterday_midnight) RemainingTime(self.env, self.task2).set_remaining_time(1, yesterday_midnight) self._close_ticket_as_fixed(self.task1) self._close_ticket_as_fixed(self.task2) self._close_ticket_as_fixed(self.story1) remaining_times = self.get_remaining_times(self.sprint.name, cut_to_today=True) # We have to use relative positioning from the end because we don't know # if the sprint will be extended due to a holiday. self.assert_equals([4, 0], remaining_times[-2:]) # Check that the same holds true for retrieving a single day remaining_time = self.get_total_remaining_time(self.sprint.name, yesterday_midnight) self.assert_equals(4, remaining_time) def yesterday_midnight(self): return midnight(yesterday(tz=utc), tz=utc) def _create_historic_remaining_times(self): def set_remaining_time(task, day, remaining_time): remaining = RemainingTime(self.env, task) remaining.set_remaining_time(remaining_time, day=day) # 6 days before today so we are sure that the sprint started at least # three days ago even if it was moved. self.sprint.start = now(tz=utc) - timedelta(days=6) self.smm.save(self.sprint) # We already burned some data on the first day of the sprint set_remaining_time(self.task1, self.sprint.start, 6) set_remaining_time(self.task2, self.sprint.start, 3) # Yesterday we burned some time already set_remaining_time(self.task1, self.yesterday_midnight(), 5) def test_remaining_time_of_first_sprint_day_equals_commitment(self): self._create_historic_remaining_times() # But now the remaining time went up again (fields are unchanged!) self.assert_equals(8+4, self.story1[Key.TOTAL_REMAINING_TIME]) remaining_times = self.get_remaining_times(self.sprint.name, cut_to_today=True) # if no commitment is passed to the function, just return the remaining # time self.assert_equals(6+3, remaining_times[0]) remaining_times = self.get_remaining_times(self.sprint.name, cut_to_today=True, commitment=42) self.assert_equals(42, remaining_times[0]) self.assert_equals([5+3, 8+4], remaining_times[-2:]) def test_total_remaining_time_of_first_sprint_day_equals_commitment(self): self._create_historic_remaining_times() sprint = self.sprint self.assert_equals(5+3, self.get_total_remaining_time(sprint.name, self.yesterday_midnight())) self.assert_equals(42, self.get_total_remaining_time(sprint.name, sprint.start, commitment=42))
class SprintControllerTestForRemainingTimes(AgiloTestCase): # Most of the test cases were previously in sprint_test # (agilo.scrum.sprint.tests) However, when we moved the # functionality to a command, these test cases needed to be ported # so that they test the command instead of the direct model # implementation. Several things are still artifacts from that old # test setup. def setUp(self): self.super() self.controller = SprintController(self.env) self.team = self.teh.create_team('Test team') # Preventing a RuleValidationException (Owner not Team Member) self.teh.create_member(name='tester', team=self.team) self.sprint = self.teh.create_sprint("Test Sprint", team=self.team) self.metrics = TeamMetrics(self.env, self.sprint, self.team) self.metrics[Key.RT_USP_RATIO] = 1.5 self.metrics.save() self.bmm = BacklogModelManager(self.env) self.smm = SprintModelManager(self.env) self.sprint_backlog, self.story1, self.task1, self.task2 = \ self._build_sprint_backlog_with_tasks(self.sprint) def _build_sprint_backlog_with_tasks(self, sprint): story_props = { Key.OWNER: 'tester', Key.SPRINT: sprint.name, Key.STORY_POINTS: "8" } story = self.teh.create_ticket(Type.USER_STORY, story_props) task1 = self.teh.create_ticket(Type.TASK, { Key.OWNER: 'tester', Key.SPRINT: sprint.name, Key.REMAINING_TIME: "8" }) story.link_to(task1) task2 = self.teh.create_ticket(Type.TASK, { Key.OWNER: 'tester', Key.SPRINT: sprint.name, Key.REMAINING_TIME: "4" }) story.link_to(task2) sprint_backlog = self.bmm.get(name="Sprint Backlog", scope=sprint.name) self.assert_equals(len(sprint_backlog), 3) return (sprint_backlog, story, task1, task2) def get_total_remaining_time(self, sprint_name, day, commitment=None): cmd_class = SprintController.GetTotalRemainingTimeCommand cmd = cmd_class(self.env, sprint=sprint_name, day=day, commitment=commitment) return self.controller.process_command(cmd) def test_can_calculate_remaining_time_for_a_specific_day(self): # Set remaining time for tasks at the end of the sprint sprint = self.sprint end = sprint.end rt1 = RemainingTime(self.env, self.task1) rt2 = RemainingTime(self.env, self.task2) rt1.set_remaining_time(2, day=end) rt2.set_remaining_time(1, day=end) self.assert_equals( 2, RemainingTime(self.env, self.task1).get_remaining_time(end)) self.assert_equals( 1, RemainingTime(self.env, self.task2).get_remaining_time(end)) self.assert_equals(3, self.get_total_remaining_time(sprint.name, end)) def _create_remaining_time_series(self, ticket, start, time_series): rt = RemainingTime(self.env, ticket) for i, remaining_time in enumerate(time_series): day = start + (i * timedelta(days=1)) rt.set_remaining_time(remaining_time, day=day) def test_can_calculate_total_remaining_time_for_start_of_sprint(self): start = self.sprint.start self._create_remaining_time_series(self.task1, start, [12, 7.5, 3, 2.5, 0]) self._create_remaining_time_series(self.task2, start, [8, 9, 4.5, 0, 3]) total_remaining_time = self.get_total_remaining_time( self.sprint.name, start) self.assert_equals(12 + 8, total_remaining_time) def test_can_calculate_remaining_time_series_for_sprint(self): start = datetime(2009, 5, 11, tzinfo=utc) self.sprint.start = start self.sprint.end = datetime(2009, 5, 15, 18, 00, tzinfo=utc) self.smm.save(self.sprint) # check there is not time set right now series = self.get_remaining_times(self.sprint.name) self.assert_equals(0, sum(series)) self._create_remaining_time_series(self.task1, start, [12, 7.5, 3, 2.5, 0]) self._create_remaining_time_series(self.task2, start, [8, 9, 4.5, 0, 3]) series = self.get_remaining_times(self.sprint.name) self.assert_equals([12 + 8, 7.5 + 9, 3 + 4.5, 2.5 + 0, 0 + 3], series) def get_remaining_times(self, sprint_name=None, cut_to_today=False, commitment=None): if sprint_name is None: sprint_name = self.sprint.name cmd_class = SprintController.GetRemainingTimesCommand cmd = cmd_class(self.env, sprint=sprint_name, cut_to_today=cut_to_today, commitment=commitment) return self.controller.process_command(cmd) def test_compute_remaining_time_for_sprint_even_if_story_has_no_remaining_time( self): self.task1[Key.REMAINING_TIME] = 0 self.task2[Key.REMAINING_TIME] = 0 self.task1.save_changes('foo', 'bar') self.task2.save_changes('foo', 'bar') # This raised an exception before because TOTAL_REMAINING_TIME was None self.get_remaining_times() def test_use_estimated_remaining_time(self): story_props = { Key.OWNER: 'tester', Key.SPRINT: self.sprint.name, Key.STORY_POINTS: "5" } story2 = self.teh.create_ticket(Type.USER_STORY, story_props) self.assert_length(4, self.sprint_backlog) self.assert_equals(8, int(self.story1[Key.STORY_POINTS])) self.assert_equals(8 * 1.5, self.story1[Key.ESTIMATED_REMAINING_TIME]) self.assert_equals(5 * 1.5, story2[Key.ESTIMATED_REMAINING_TIME]) remaining_time = self.get_total_remaining_time(self.sprint.name, now(tz=utc)) self.assert_equals((8 + 4) + 5 * 1.5, remaining_time) def _close_ticket_as_fixed(self, task): task[Key.STATUS] = Status.CLOSED task[Key.RESOLUTION] = Status.RES_FIXED task.save_changes(None, None) def test_remaining_time_correct_even_for_closed_stories(self): self.sprint.start = datetime.today() - timedelta(days=5) self.sprint.save() # Store some remaining time for yesterday yesterday_midnight = datetime.combine(yesterday(tz=utc), time(tzinfo=utc)) RemainingTime(self.env, self.task1).set_remaining_time(3, yesterday_midnight) RemainingTime(self.env, self.task2).set_remaining_time(1, yesterday_midnight) self._close_ticket_as_fixed(self.task1) self._close_ticket_as_fixed(self.task2) self._close_ticket_as_fixed(self.story1) remaining_times = self.get_remaining_times(self.sprint.name, cut_to_today=True) # We have to use relative positioning from the end because we don't know # if the sprint will be extended due to a holiday. self.assert_equals([4, 0], remaining_times[-2:]) # Check that the same holds true for retrieving a single day remaining_time = self.get_total_remaining_time(self.sprint.name, yesterday_midnight) self.assert_equals(4, remaining_time) def yesterday_midnight(self): return midnight(yesterday(tz=utc), tz=utc) def _create_historic_remaining_times(self): def set_remaining_time(task, day, remaining_time): remaining = RemainingTime(self.env, task) remaining.set_remaining_time(remaining_time, day=day) # 6 days before today so we are sure that the sprint started at least # three days ago even if it was moved. self.sprint.start = now(tz=utc) - timedelta(days=6) self.smm.save(self.sprint) # We already burned some data on the first day of the sprint set_remaining_time(self.task1, self.sprint.start, 6) set_remaining_time(self.task2, self.sprint.start, 3) # Yesterday we burned some time already set_remaining_time(self.task1, self.yesterday_midnight(), 5) def test_remaining_time_of_first_sprint_day_equals_commitment(self): self._create_historic_remaining_times() # But now the remaining time went up again (fields are unchanged!) self.assert_equals(8 + 4, self.story1[Key.TOTAL_REMAINING_TIME]) remaining_times = self.get_remaining_times(self.sprint.name, cut_to_today=True) # if no commitment is passed to the function, just return the remaining # time self.assert_equals(6 + 3, remaining_times[0]) remaining_times = self.get_remaining_times(self.sprint.name, cut_to_today=True, commitment=42) self.assert_equals(42, remaining_times[0]) self.assert_equals([5 + 3, 8 + 4], remaining_times[-2:]) def test_total_remaining_time_of_first_sprint_day_equals_commitment(self): self._create_historic_remaining_times() sprint = self.sprint self.assert_equals( 5 + 3, self.get_total_remaining_time(sprint.name, self.yesterday_midnight())) self.assert_equals( 42, self.get_total_remaining_time(sprint.name, sprint.start, commitment=42))