예제 #1
0
        /// <summary>
        /// Compares an expected daily student summary object to the actual daily student summary object
        /// </summary>
        /// <remarks>
        /// For reasons of time constraints, this method assumes that neither object is null and that none of
        /// the properties are null.
        /// </remarks>
        /// <param name="expectedSummary">The expected daily student summary object</param>
        /// <param name="actualSummary">The actual daily student summary object</param>
        public static void CompareDailyStudentSummaries(DailyStudentSummary expectedSummary, DailyStudentSummary actualSummary)
        {
            Assert.That(actualSummary.Subjects, Is.EquivalentTo(expectedSummary.Subjects));

            Assert.That(actualSummary.SummaryRows.Count, Is.EqualTo(expectedSummary.SummaryRows.Count));

            //We need to have the rows in a consistent order so that we can compare the same rows to each other
            var expectedSortedRows = expectedSummary.SummaryRows.OrderBy(row => row.UserId);
            var actualSortedRows   = actualSummary.SummaryRows.OrderBy(row => row.UserId);

            expectedSortedRows.Zip(actualSortedRows, Tuple.Create)
            .ToList()
            .ForEach(rowTuple => CompareDailyStudentSummaries(rowTuple.Item1, rowTuple.Item2));
        }
        public DailyStudentSummary GetDailyStudentSummary(DateTime summaryDateTime)
        {
            DailyStudentSummary dailySummary = new DailyStudentSummary();

            //Retrieve the queryable from the answer DB
            IQueryable <Answer> answerQueryable = answerDB.GetAnswerQueryable();

            //Calculate the summary date/time range
            DateTime minDateTime = summaryDateTime.Date;
            DateTime maxDateTime = summaryDateTime;

            //Create a queryable that only contains answers within the date range
            IQueryable <Answer> relevantAnswers = answerQueryable
                                                  .Where(answer => answer.SubmitDateTime >= minDateTime &&
                                                         answer.SubmitDateTime <= maxDateTime)
                                                  .AsQueryable();

            //Calculate the unique subjects within the date range
            dailySummary.Subjects = relevantAnswers
                                    .Where(answer => answer.SubmitDateTime >= minDateTime)
                                    .Where(answer => answer.SubmitDateTime <= maxDateTime)
                                    .Select(answer => answer.Subject)
                                    .Distinct()
                                    .ToList();

            //Retrieve the students and their average subject progress scores
            dailySummary.SummaryRows = relevantAnswers
                                       //Group the answers into users
                                       .GroupBy(answer => answer.UserId)
                                       //Create a student summary row for each user group
                                       .Select(userAnswerGroup => new StudentSummaryRow()
            {
                UserId = userAnswerGroup.Key,
                Name   = userAnswerGroup.Key.ToString(),
                AverageSubjectProgress = userAnswerGroup
                                         //Group the user answers by subject
                                         .GroupBy(answer => answer.Subject)
                                         //Convert each subject group into a collection of tuples containing the subject name and the
                                         //average progress score
                                         .Select(subjectGroup => Tuple.Create(subjectGroup.Key,
                                                                              subjectGroup.Select(answer => (decimal)answer.Progress).Average()))
                                         //Convert that collection to a dictionary with the subject as the key and the average progress score
                                         //as the value
                                         .ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2)
            })
                                       .ToList();

            return(dailySummary);
        }
            /// <summary>
            /// Runs a student summary test with a particular test of test data
            /// </summary>
            /// <param name="testData">The set of test data to use when running the test</param>
            private void RunStudentSummaryTestWithData(List <Answer> testData)
            {
                //Define the target date/time when the summary is created
                DateTime targetDateTime = DateTime.Parse("2018-01-30T12:39:00");

                //Calculate the expected daily summary
                DailyStudentSummary expectedSummary = CalculateExpectedSummaryData(testData, targetDateTime);

                //Create the mock answer DB
                Mock <IAnswerDB> mockAnswerDB = CreateMockAnswerDB(testData);

                //Create an instance of the answer repository
                IAnswerRepository answerRepository = new AnswerRepository(mockAnswerDB.Object);

                //Retrieve the daily student summary
                DailyStudentSummary actualSummary = answerRepository.GetDailyStudentSummary(targetDateTime);

                //Compare the actual summary to the expected summary to see if they match
                DataComparers.CompareDailyStudentSummaries(actualSummary, expectedSummary);
            }
            /// <summary>
            /// Calculates the expected summary data based on a set of test data and a particular date/time
            /// when the summary is generated
            /// </summary>
            /// <remarks>
            /// I'm deliberately calculating the expected data in a different manner than how the method
            /// will be implemented so that I'm not repeating the same mistakes. I consider this to
            /// be the less optimal method. Using grouping would be much more readable.
            /// </remarks>
            /// <param name="testData">The test data</param>
            /// <param name="summaryGenDateTime">The date/time the daily summary was generated</param>
            /// <returns>The expected daily summary</returns>
            private DailyStudentSummary CalculateExpectedSummaryData(List <Answer> testData, DateTime summaryGenDateTime)
            {
                DailyStudentSummary expectedSummary = new DailyStudentSummary();

                //Calculate the summary date/time range
                DateTime minDateTime = summaryGenDateTime.Date;
                DateTime maxDateTime = summaryGenDateTime;

                //Create a queryable that only contains answers within the date range
                IQueryable <Answer> relevantAnswers = testData
                                                      .Where(answer => answer.SubmitDateTime >= minDateTime &&
                                                             answer.SubmitDateTime <= maxDateTime)
                                                      .AsQueryable();

                //Calculate the unique subjects within the date range
                expectedSummary.Subjects = relevantAnswers
                                           .Where(answer => answer.SubmitDateTime >= minDateTime &&
                                                  answer.SubmitDateTime <= maxDateTime)
                                           .Select(answer => answer.Subject)
                                           .Distinct()
                                           .ToList();

                //Get the unique students that have data within the date range
                List <int> expectedStudents = relevantAnswers
                                              .Where(answer => answer.SubmitDateTime >= minDateTime &&
                                                     answer.SubmitDateTime <= maxDateTime)
                                              .Select(answer => answer.UserId)
                                              .Distinct()
                                              .ToList();

                //Map each student to a summary row
                expectedSummary.SummaryRows = expectedStudents
                                              .Select(currentUserId =>
                {
                    StudentSummaryRow studentSummary = new StudentSummaryRow()
                    {
                        UserId = currentUserId,
                        Name   = currentUserId.ToString()
                    };

                    //Map each subject to an average progress value. Some values may be null,
                    //since not all students may have done exercises in the same subjects
                    studentSummary.AverageSubjectProgress = expectedSummary.Subjects
                                                            .Where(subject => relevantAnswers.Any(answer => answer.Subject == subject &&
                                                                                                  answer.UserId == currentUserId))
                                                            .Select(targetSubject =>
                    {
                        decimal averageProgress = relevantAnswers
                                                  .Where(answer => answer.Subject == targetSubject)
                                                  .Where(answer => answer.UserId == currentUserId)
                                                  .Select(answer => (decimal)answer.Progress)
                                                  .Average();

                        return(Tuple.Create(targetSubject, averageProgress));
                    })
                                                            .ToDictionary(tuple => tuple.Item1, tuple => tuple.Item2);

                    return(studentSummary);
                })
                                              .ToList();

                return(expectedSummary);
            }