CSC/ECE 517 Fall 2019 - E1955.Write unit tests for student task.rb
This page provides a description of the Expertiza based OSS project.
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.
This project in particular intends that the students collaborate with each other and work on making enhancements to the code base by applying the concepts of Rails,RSpec, DRY code,Test driven development etc. This provides an opportunity for students to contribute to an open source project and learn further about software deployment etc.
About RSpec
RSpec is a unit test framework for the Ruby programming language. RSpec is different than traditional xUnit frameworks like JUnit because RSpec is a Behavior driven development tool. What this means is that, tests written in RSpec focus on the "behavior" of an application being tested. RSpec does not put emphasis on, how the application works but instead on how it behaves, in other words, what the application actually does.Each RSpec file contains one or more tests. Each test ensures if a particular feature of our website is working properly.The output of an RSpec run will tell you exactly what features aren't working. The benefit is that tested code is unlikely to break unnoticed.The tests are run every time someone makes, or updates, a Pull Request.
DB Schema
Below are the tables that are related ti student task functionality DB Schema
Description of the current project
The following is an Expertiza based OSS project which deals primarily with the student_task.rb and student_task_spec.rb. It focuses on writing RSpec unit tests for the code affected or added. The goal of this project is to attempt to add sufficient unit tests to this part of the application and increase its path coverage to above 90 percent. The following tasks were accomplished in this project:
- Complete the insufficient unit tests for student_task.rb.
- Increase path coverage from only 31.91% with 30 lines covered to above 92.55% with 87 lines covered.
- Cover edge cases.
- High branch coverage to be achieved.
There are different tasks that a student can perform.
- He can see all the assignments that he has been assigned to.
- He can see details of each assignment like marks, his team, send invitation to student to join a team
- Review other students' work
The following functions were tested:
- complete?
This function returns true if the participant stage deadline is "complete" or else return false.
- content_submitted_in_current_stage?
This function returns true if the participant has submitted hyperlinks in the current stage and the stage is in "submission"
- hyperlinks
This function returns all the hyperlinks submitted by the participant team
- in_work_stage?
A assignment is said to be in work stage if its stage is any of "submission", "review" or "metareview"
- incomplete?
This function returns true if the participant stage deadline is not "complete" or else return false.
- metareviews_given? * reviews_given?
A participant can review other people's work and add comments. Also a user can review a review, called "metareview" that is given to him. A review comment is mapped to assignments or other reviews by the table response_tags via the reviewed_object_id. This method checks if the participant has given any metareview and return true otherwise.
- metareviews_given_in_current_stage?
This function checks if the assignment is in "metareview" stage and metareviews.
- not_started?
This function checks if the assignment is in any of "submission", "review" or "metareview" stage
- relative_deadline
- reviews_given_in_current_stage?
- revision?
- from_participant(participant)
This function return the StudentTask from the participant with assignment, topic, current_stage
- from_participant_id(id)
This function return the StudentTask from the participant with assignment, topic, current_stage using the participant id
- from_user(user)
- get_author_feedback_data(participant_id, timeline_list)
- get_due_date_data(assignment, timeline_list)
- get_peer_review_data(participant_id, timeline_list)
- get_submission_data(assignment_id, team_id, timeline_list)
- get_timeline_data(assignment, participant, team)
- teamed_students(user, ip_address = nil)
This function return the list of all the students that the logged on user has teamed up until now.
- started?
Checks if the current stage is "incomplete" and content_submitted_in_current_stage? reviews_given_in_current_stage? || metareviews_given_in_current_stage? and return true and false
- topic_name
Return the name of the topic
Test Plan
- Mock Objects
The following mock objects were created/built before the commencement of automated testing for student_task.rb (model) code.
let(:participant) { build(:participant, id: 1, user_id: user.id, parent_id: assignment.id) } let(:participant2) { build(:participant, id: 2, user_id: user2.id, parent_id: assignment.id) } let(:participant3) { build(:participant, id: 3, user_id: user3.id, parent_id: assignment2.id) } let(:user) { create(:student) } let(:user2) { create(:student, name: "qwertyui", id: 5) } let(:user3) { create(:student, name: "qwertyui1234", id: 6) } let(:course) { build(:course) } let(:assignment) { build(:assignment, name: 'assignment 1') } let(:assignment2) { create(:assignment, name: 'assignment 2', is_calibrated: true) } let(:team) { create(:assignment_team, id: 1, name: 'team 1', parent_id: assignment.id, users: [user, user2]) } let(:team2) { create(:assignment_team, id: 2, name: 'team 2', parent_id: assignment2.id, users: [user3]) } let(:team_user) { create(:team_user, id: 3, team_id: team.id, user_id: user.id) } let(:team_user2) { create(:team_user, id: 4, team_id: team.id, user_id: user2.id) } let(:team2_user3) { create(:team_user, id: 5, team_id: team2.id, user_id: user3.id) } let(:course_team) { create(:course_team, id: 3, name: 'course team 1', parent_id: course.id) } let(:cource_team_user) { create(:team_user, id: 6, team_id: course_team.id, user_id: user.id) } let(:cource_team_user2) { create(:team_user, id: 7, team_id: course_team.id, user_id: user2.id) } let(:topic) { build(:topic) } let(:topic2) { create(:topic, topic_name: "TestReview") } let(:due_date) { build(:assignment_due_date, deadline_type_id: 1) } let(:deadline_type) { build(:deadline_type, id: 1) } let(:review_response_map) { build(:review_response_map, assignment: assignment, reviewer: participant, reviewee: team2) } let(:metareview_response_map) { build(:meta_review_response_map, reviewed_object_id: 1) } let(:response) { build(:response, id: 1, map_id: 1, response_map: review_response_map) } let(:response2) { build(:response, id: 2, map_id: 1, response_map: review_response_map) } let(:submission_record) {build(:submission_record, id:1, team_id: 1, assignment_id: 1) } #added for submission_record method call test. let(:student_task) do StudentTask.new( participant: participant, assignment: participant.assignment, topic: participant.topic, current_stage: participant.current_stage, stage_deadline: (Time.parse(participant.stage_deadline) rescue Time.now + 1.year) ) end
Below are some of the functions tested
- Function: teamed_students - Write a test case to find teamed up students.
This method is an instance method, having a user passed to it. It return the list of students that the current user has teamed up. It does not consider the teammates that are from calibration assignments The test cases described check for the following scenarios: 1. When not in any team returns empty 2. When assigned in a cource_team returns empty 3. When assigned in a assignment_team return the list of teammates
describe "#teamed_students" do context 'when not in any team' do it 'returns empty' do expect(StudentTask.teamed_students(user3)).to eq({}) end end context 'when assigned in a cource_team ' do it 'returns empty' do allow(user).to receive(:teams).and_return([course_team]) expect(StudentTask.teamed_students(user)).to eq({}) end end context 'when assigned in a assignment_team ' do it 'returns empty' do allow(user).to receive(:teams).and_return([team]) allow(AssignmentParticipant).to receive(:find_by).with(user_id: 1, parent_id: assignment.id).and_return(participant) allow(AssignmentParticipant).to receive(:find_by).with(user_id: 5, parent_id: assignment.id).and_return(participant2) allow(Assignment).to receive(:find_by).with(id: team.parent_id).and_return(assignment) # allow(Team).to receive(:find).with(team.id).and_return(team) expect(StudentTask.teamed_students(user)).to eq({assignment.course_id => [user2.fullname]}) end end end
- Function: get_due_date_data - Write a test case to get the due dates of the assignment.
This method is an instance method, having an assignment and timeline_list list passed to it. The test cases described check for the following scenarios: 1. When an assignment passed to the method has no assignment attributes. 2. When an assignment passed to the method has some attributes but not the attribute due_at 3. When an assignment passed to the method has some attributes along with due_at
due_at attribute is checked, upon which the timeline_list is populated with the updated_at and label values.
describe "#get_due_date_data" do context 'when called with assignment having empty due dates' do it "return empty time_list array" do timeline_list = [] StudentTask.get_due_date_data(assignment, timeline_list) expect(timeline_list).to eq([]) end end context 'when called with assignment having due date' do context 'and due_at value nil' do it "return empty time_list array" do allow(due_date).to receive(:deadline_type).and_return(deadline_type) timeline_list = [] due_date.due_at=nil; assignment.due_dates = [due_date] StudentTask.get_due_date_data(assignment, timeline_list) expect(timeline_list).to eq([]) end end context 'and due_at value not nil' do it "return time_list array" do allow(due_date).to receive(:deadline_type).and_return(deadline_type) timeline_list = [] assignment.due_dates = [due_date] StudentTask.get_due_date_data(assignment, timeline_list) expect(timeline_list).to eq([{ :label=>(due_date.deadline_type.name + ' Deadline').humanize, :updated_at=>due_date.due_at.strftime('%a, %d %b %Y %H:%M') }]) end end end end
- Function: get_peer_review_data - Write a test case to get the peer review data.
This method is an instance method, having an review and timeline_list list passed to it. The test cases described check for the following scenarios: 1. When no review response is mapped. 2. When review response is mapped.
describe "#get_peer_review_data" do context 'when no review response mapped' do it 'returns empty' do timeline_list=[] StudentTask.get_peer_review_data(user2,timeline_list) expect(timeline_list).to eq([]) end end context 'when mapped to review response map' do it 'returns timeline array' do timeline_list=[] allow(ReviewResponseMap).to receive_message_chain(:where, :find_each).with(reviewer_id: 1).with(no_args).and_yield(review_response_map) allow(review_response_map).to receive(:id).and_return(1) allow(Response).to receive_message_chain(:where, :last).with(map_id: 1).with(no_args).and_return(response) allow(response).to receive(:round).and_return(1) allow(response).to receive(:updated_at).and_return(Time.new(2019)) timevalue = Time.new(2019).strftime('%a, %d %b %Y %H:%M') expect(StudentTask.get_peer_review_data(1,timeline_list)).to eq([{:id=>1, :label=>"Round 1 peer review", :updated_at=>timevalue}]) end end end
- Function: get_author_feedback_data - Write a test case to get the feedback response
This method is an instance method, having feedback and timeline_list list passed to it. The test cases described check for the following scenarios: 1. when no feedback response is mapped. 2. when feedback response is mapped.
describe "#get_author_feedback_data" do context 'when no feedback response mapped' do it 'returns empty' do timeline_list=[] StudentTask.get_author_feedback_data(user2,timeline_list) expect(timeline_list).to eq([]) end end context 'when mapped to feedback response map' do it 'returns timeline array' do timeline_list=[] allow(FeedbackResponseMap).to receive_message_chain(:where, :find_each).with(reviewer_id: 1).with(no_args).and_yield(review_response_map) allow(review_response_map).to receive(:id).and_return(1) allow(Response).to receive_message_chain(:where, :last).with(map_id: 1).with(no_args).and_return(response) allow(response).to receive(:updated_at).and_return(Time.now) timevalue = Time.now.strftime('%a, %d %b %Y %H:%M') expect(StudentTask.get_author_feedback_data(1,timeline_list)).to eq([{:id=>1, :label=>"Author feedback", :updated_at=>timevalue}]) end end end
- Function: get_submission_data - Write a test case to import participants
This method is an instance method, having a submission and timeline_list list passed to it. The test cases described check for the following scenarios: 1. When no submission data is mapped. 2. When submission data is mapped and hyperlink is not submitted or hyperlink is removed. 3. When submission data is mapped and operation is submit_hyperlink. 4. When submission data mapped and operation is Remove Hyperlink.
describe '#get_submission_data' do context 'when no submission data mapped' do it 'returns nil' do timeline_list=[] expect(StudentTask.get_submission_data(1,1,timeline_list)).to eq(nil) end end context 'when submission data mapped and not submit hyperlink or Remove hyperlink' do it 'returns timeline_list' do timeline_list=[] allow(SubmissionRecord).to receive_message_chain(:where, :find_each).with(team_id: 1, assignment_id: 1).with(no_args).and_yield(submission_record) allow(submission_record).to receive(:operation).and_return('testing_label') allow(submission_record).to receive(:updated_at).and_return(Time.new(2019)) timevalue = Time.new(2019).strftime('%a, %d %b %Y %H:%M') expect(StudentTask.get_submission_data(1,1,timeline_list)).to eq([{:label=>"Testing label", :updated_at=>timevalue}]) end end context 'when submission data mapped and operation is submit_hyperlink' do it 'returns timeline_list with link' do timeline_list=[] allow(SubmissionRecord).to receive_message_chain(:where, :find_each).with(team_id: 1, assignment_id: 1).with(no_args).and_yield(submission_record) allow(submission_record).to receive(:operation).and_return('Submit Hyperlink') allow(submission_record).to receive(:updated_at).and_return(Time.new(2019)) timevalue = Time.new(2019).strftime('%a, %d %b %Y %H:%M') expect(StudentTask.get_submission_data(1,1,timeline_list)).to eq([{:label=>"Submit hyperlink", :updated_at=>timevalue, :link=>"www.wolfware.edu"}]) end end context 'when submission data mapped and operation is Remove Hyperlink' do it 'returns timeline_list with link' do timeline_list=[] allow(SubmissionRecord).to receive_message_chain(:where, :find_each).with(team_id: 1, assignment_id: 1).with(no_args).and_yield(submission_record) allow(submission_record).to receive(:operation).and_return('Remove Hyperlink') timevalue = Time.new(2019).strftime('%a, %d %b %Y %H:%M') allow(submission_record).to receive(:updated_at).and_return(Time.new(2019)) expect(StudentTask.get_submission_data(1,1,timeline_list)).to eq([{:label=>"Remove hyperlink", :updated_at=>timevalue, :link=>"www.wolfware.edu"}]) end end end
Team
Carmen Bentley,Mentor
- Tushar Kundra,tkundra
- Ram Chavali,ramlohith
- Ayushi Rungta,Rungta1001
Running Tests
What we need to do is to set up the environment and complete the 'student_task.rb' and 'student_task_spec.rb' to increase the path coverage from only 43.0% with 43 lines covered and 57 lines missed to above 90%.
You can run RSpec tests by executing the command:
rspec spec/models/student_task_spec.rb
After that, you can see the detailed coverage information by opening this file in your Expertiza folder.
coverage/index.html
Our work
The code we created can be found below. We have also linked the video of our tests running with coverage to showcase the work we have done.
Running the Project Locally
The project could be run locally by cloning the Github repository Expertiza and then running the following commands sequentially.
bundle install rake db:create:all rake db:migrate rails s
Issues/Erros During Testing
- While creating the test for "reviews_given?", given below, it seems the function is returning string value instead of boolean value as "j.class.to_s[/Review/]" gives a string when the class of "j" starts with "Review"
def reviews_given? response_maps.inject(nil) {|i, j| i || (j.response && j.class.to_s[/Review/]) } end