CSC/ECE 517 Spring 2022 - E2209: Testing for analytic helper.rb, join team requests helper.rb

From Expertiza_Wiki
Jump to navigation Jump to search


About Expertiza

Expertiza is an open-source project based on Ruby on Rails framework. Expertiza allows the instructor to create new assignments and customize new or existing assignments. It also allows the instructor to create a list of topics the students can sign up for. Students can form teams in Expertiza to work on various projects and assignments. Students can also peer review other students' submissions. Expertiza supports submission across various document types, including the URLs and wiki pages.

Project Description

We were given the following problem statement as a starting point for our project: Refer to the general testing guidelines above, but start by using the spec/helpers/join_team_request_helper_spec.rb file and the test that has already been written as a jumping-off point. We had to do the testing as per the description of the project but some aspects of the project was changed after an unexpected discovery in the middle of the project leading to an increase in the scope of the project which is discussed in the later part.

Files Involved

- app/helpers/join_team_request_helper.rb

- spec/helpers/join_team_request_helper_spec.rb

- app/helpers/analytic_helper.rb

- spec/helpers/analytic_helper_spec.rb

Test Plan

After setting up our development environment, we decided to start the testing process for our given helper classes. As there were no existing tests in place for these classes, we needed to write the tests for all the methods from scratch. We wrote test cases to cover all the 7 methods in the join_team_request_helper.rb and analytic_helper.rb and added 26 tests in total.

How to Run Tests

From the root directory of the project, run the following command to run tests for join_team_request_helper:

rspec spec/helpers/join_team_request_helper_spec.rb

As will be later discussed, our project eventually came to involve the removal of the analytic_helper.rb file due to the code being unused in the entire expertiza codebase. To run the tests that we had created for this file but are not included in our final pull request, please refer to the "test_normalize" and "test-analytic_helper-distribution" of our GitHub repository before running the following command:

rspec spec/helpers/analytic_helper_spec.rb
Testing join_team_request_helper.rb

This was the first portion of the project that our team decided to work on. The JoinTeamRequestHelper class is very simple, containing only a single method, display_request_status(). This method is used in two locations within the project and has the purpose of displaying a better formatted string for the status of a JoinTeamRequest. The status of a JoinTeamRequest uses the following formatting:

- ‘P’ indicates a Pending status.

- ‘D’ indicates a Denied status.

- ‘A’ indicates an Accepted status.

Any other status is unexpected, and should just be returned as it is to the caller. The code for this method is as follows:

def display_request_status(join_team_request)
 status = case join_team_request.status
          when 'P'
            'Pending: A request has been made to join this team.'
          when 'D'
            'Denied: The team has denied your request.'
          when 'A'
            "Accepted: The team has accepted your request.\nYou should receive an invitation in \"Your Team\" page."
          else
            join_team_request.status
          end
 status
end

To test this method, we wanted to check the output given a JoinTeamRequest covering all the possible status values. To do so, we created a new JoinTeamRequest for each test case and assigned the status to ‘P’, ‘D’, and ‘A’ as appropriate and verified that the correct message was returned.

A sample of these tests is as follows:

# Accepted Status
    context 'when the request is accepted' do
      before(:each) do
        join_team_request1.status = 'A'
      end

      it 'returns the associated status' do
        expect(helper.display_request_status(join_team_request1)).to eq("Accepted: The team has accepted your request.\nYou should receive an invitation in \"Your Team\" page.")
      end
    end

The tests for this method can be found in /spec/helpers/join_team_request_helper_spec.rb.

Testing analytic_helper.rb

We were given the task to test analytic_helper.rb as part of our assignment. This class had modules defined inside such as LineChartHelper, BarChartHelper, ScatterPlotHelper, PieChartHelper, and the final one being the AnalyticHelper. All the modules excluding the AnalyticHelper did not have a single method in them. The AnalyticHelper had four methods which were get_chart_data(), normalize(), distribution(), distribution_categories().

We were working on the testing of normalize(), distribution() and distribution_categories().

Testing normalize()

The normalize() method was a simple method which normalized a sequence of numbers passed through an array to values in the range [0,1]. The original code for this method can be seen below:

def normalize(array)
    normalized_array = []
    max = array.max
    array.each do |element|
      normalized_array << element.to_f / max
    end
    normalized_array
  end

Upon analysis of this code, it seemed that it would be necessary to test the method with several different types of input. These types of input are as follows:

1) A sorted array of positive integers.

2) A sorted array of both positive and negative integers.

3) An unsorted array of positive integers.

4) An unsorted array of both positive and negative integers.

5) A sorted array of positive floating point numbers.

6) A sorted array of both positive and negative floating point numbers.

7) An unsorted array of positive floating point numbers.

8) An unsorted array of both positive and negative floating point numbers.

Upon initial implementation of these tests, it was found that the method did not perform as expected for several use cases, especially present in those tests with negative numbers. The cause was found to be that the method was not properly calculating a normalized result for each input number. The method was modified to use the following formula for normalization:

Normalized Value = (Value - Min) / (Max - Min)

The use of this new formula allowed the method to properly scale values to fit the range [0,1] as intended. However, we identified several special cases that needed to be addressed in testing as well:

1) An array of length 1, containing only a single number.

2) An array of arbitrary length, but containing only copies of a single number.

3) An empty array.

4) A nil input.

5) An array containing values that are non-numeric.

6) An array containing nil values.

After adding tests for these special cases, further adjustments needed to be made to the method to ensure that it behaved appropriately. For input arrays containing only a single number, the code was modified such that these values get normalized to be output as the value 1, rather than the default calculation which would cause a divide by zero error under the new formula. The method was also modified to check that the input is not empty or nil before performing normalization calculations. Finally, the method was modified to raise a TypeError when an array is passed containing non-numeric or nil values. The final code for normalize() can be seen below:

def normalize(array)
    # Scales all values in the array using the following formula:
    # scaled_value = (value - min) / (max - min)
    # This gives us all values in the original array normalized on the range [0,1]

    # Handle special cases: nil, empty array, array of non-numbers
    return nil if array.nil?
    return [] if array.length == 0
    array.each do |element|
      raise TypeError.new('Array to normalize contained a non-numeric value.') if !element.is_a?(Numeric)
    end

    # Our initially empty return value
    normalized_array = []

    # Get appropriate values for scaling each value
    min = array.min
    scaled_max = array.max - min

    # Handle cases where there is only one number in the array
    if scaled_max == 0
      min = 0
      scaled_max = array.max
    end

    # Go through each element in the array and normalize it
    array.each do |element|
      scaled_element = element - min
      normalized_array << scaled_element.to_f / scaled_max
    end

    # Return constructed array
    normalized_array
  end

As will be mentioned later, these tests will not appear in the pull request to the main Expertiza repository due to the AnalyticHelper class being unused in the rest of the project.

Testing distribution() and distribution_categories()

The distribution and distribution_categories were simple methods that took three parameters: array, num_intervals, and x_min. The passed array is the list of integers that are to be divided into the distribution. The passed value of num_intevals is the number of distribution intervals to be created. The value of x_min represents the minimum value from which the interval size is calculated. The distribution function returns a list of the count of integers that lie in the divided intervals, while the distribution_categories function returns a list of the string categories upon which the array is distributed.

def distribution(array, num_intervals, x_min = array.min)
  distribution = []
  interval_size = ((array.max - x_min).to_f / num_intervals).ceil
  intervals = (1..num_intervals).to_a.collect { |val| val * interval_size }
  intervals.each do |interval_max|
    distribution << array.select { |a| a < interval_max }.count
    array.reject! { |a| a < interval_max }
  end
  distribution
end

Upon analysis of this code, it seemed that it would be necessary to test the method with several different types of input. These types of input are as follows:

For distribution:

1) distributes an array to the given num of intervals

2) distributes an unsorted array to the given num of intervals

3) distributes an array with negative integers to the given num of intervals

4) distributes an unsorted array with negative integers to the given num of intervals

def distribution_categories(array, num_intervals, x_min = array.min)
  categories = []
  interval_size = ((array.max - x_min).to_f / num_intervals).ceil
  intervals = (1..num_intervals).to_a.collect { |val| val * interval_size }
  interval_min = 0
  intervals.each do |interval_max|
    categories << (interval_min..interval_max).to_s
    interval_min = interval_max
  end
  categories
end

Upon analysis of this code, it seemed that it would be necessary to test the method with several different types of input. These types of input are as follows:

For distribution_categories:

1) provides with distribution categories of an array to the given num of intervals

2) provides distribution categories of an unsorted array to the given num of intervals

3) provides with distribution categories of an array with negative integers to the given num of intervals

4) provides with distribution categories of an unsorted array with negative integers to the given num of intervals

Upon further investigation into the usage of these methods, we found that the two methods were not called anywhere in the entire expertiza codebase. We searched through the call hierarchy for the method and further found out that the AnalyticHelper module was not used in the entire codebase. We went through the commit history of expertiza and found out that these functions are not ever used in the project since their original creation in April 2013. Here is the link to the most recent commit referencing their usage:

Commit

Hence, these tests and changes will not appear in the pull request to the main Expertiza repository due to the AnalyticHelper class being unused in the rest of the project and being removed.

Testing get_chart_data()

This method took four parameters as an input which were chart_type, object_type, object_id_list, and data_type_list. The chart_type described the type of chart which we wanted to create such as bar, pie etc. The object_type was an internal data that described what was the chart created for like courses, assignments, teams etc. The third parameter was the list of IDs which we traverse through and created charts for all the assignments provided there. The fourth parameter passed the type of chart we wanted to create. There were some generic types for which the charts were getting created.

When approaching how to test this method, we first looked to where it was being used in the rest of the project. We searched through the call hierarchy for the method and found out that the method was not getting called from anywhere, and further that this module was not used in the entire codebase. We went through the commit history of the base expertiza repository and found out that the last commit for analytic_helper was made on October 25th, 2014. Here is the link to the commit:

Commit

Following this discovery, we went to Dr. Gehringer with the problem. He suggested that since the helper was not being used anywhere in the code base, it would be safe to remove the helper. As a result, we removed analytic_helper.rb from our pull request.

Improvement

On following our discovery of the unused AnalyticHelper class and the discussion with Dr. Gehringer, the testing part of the AnalyticHelper class was instead removed as a task from our project and replaced with the removal of the unused code and gems from the expertiza code base.

Removing Unused Code for Charts and Graphs

Following our discovery that the code for AnalyticHelper is unused in the current implementation of the project, we discussed with Dr. Gehringer how to proceed with the project. He directed us to work on removing unused code relating to charts and graphs within the project in place of further testing on AnalyticHelper, as this testing effort would be wasted on features not used in practice.

So, we started searching for other charts and graphs related modules, classes and gems in the expertiza. We discovered that the ChartHelper module was used only in the AnalyticHelper class. As we were to remove AnalyticHelper for being unused in other portions of the project, we determined that it would be safe and appropriate to remove ChartHelper as well. All the other helpers related to charts and graphs are actively used in expertiza.

We found three gems that were related to charts namely googlecharts, gchartrb and chartjs-ror. All the gems except googlecharts were in use, so we removed the googlecharts gem from the Gemfile for the project.

Conclusion

As a team, we divided tasks among team members and have used the practice of pair programming to complete these tasks. As a team, we analyzed and understood the methods and modules which were part of our project. Since we were not able to test analytic_helper.rb, we sought out other dead code related to graphs and charts in expertiza and have removed those helpers and gems. The tests that we had intially created for analytic_helper.rb prior to our change in direction for the project can be seen in open pull requests on our Forked Github Repository (Linked below), but they have not been merged into our main branch as we do not want to push this code to the base expertiza repository.

Links

- Forked Github Repository: Forked Repo

- Video of Tests Running Successfully: Video

- Open Pull Request to Expertiza Repo: Pull Request

- Deployment Link for Project: Deployment