CSC/ECE 517 Fall 2018 - Project E1852 Write unit tests for participant.rb: Difference between revisions
(→email) |
|||
Line 118: | Line 118: | ||
====email==== | ====email==== | ||
This case tests that an email about assignment registration will be sent to the current user when email method is called | This case tests that an email about assignment registration will be sent to the current user when email method is called. | ||
<pre> | <pre> | ||
describe "#email" do | describe "#email" do |
Revision as of 01:19, 3 November 2018
This wiki page is for the description of changes made under E1852 OSS Assignment for Fall 2018, CSC/ECE 517.
Introduction
The objective for this project is to write unit tests using Rspec for participant.rb model, which is used to prepare data for participants enrolled in each course/assignment. By editing the participant_spct.rb, all class and instance methods are tested, and the path coverage is above 90% with all 97 lines covered.
Expertiza
Expertiza is an open source educational web application developed on Ruby on Rails framework. Using Expertiza, students can submit and peer-review learning objects such as articles, code, and websites. The source code is available on Github and it allows students to improve and maintain.
Behavior-DrivenDevelopment
In software engineering, behavior-driven development (BDD) is a software development process that emerged from test-driven development(TDD). The behavior-driven development combines the general techniques and principles of TDD with ideas from domain-driven design and object-oriented analysis and design to provide software development and management teams with shared tools and a shared process to collaborate on software development.
Rspec
RSpec is a 'Domain Specific Language' (DSL) testing tool written in Ruby to test Ruby code. It is a behavior-driven development (BDD) framework which is extensively used in the production applications. The basic idea behind this concept is that of Test Driven Development (TDD) where the tests are written first and the development is based on writing just enough code that will fulfill those tests followed by refactoring. It contains its own mocking framework that is fully integrated into the framework based upon JMock. The simplicity in the RSpec syntax makes it one of the popular testing tools for Ruby applications. The RSpec tool can be used by installing the rspec gem which consists of 3 other gems namely rspec-score, rspec-expectation and rspec-mock.
Problem Statement
The initial unit tests’ path coverage is only 36.08% with 35 lines covered and 62 lines missed for participant.rb, which are not enough. The unit test should be improved by making the path coverage of more than 90% and achieve the highest possible branch coverage.
Files Involved
A test file and two model files were modified for this project:
spec/models/participant_spec.rb
app/models/participant.rb
app/models/assignment_participant.rb
Steps
Expertiza Environment Setup
1. Install Virtual Box software form Oracle in PC
2. Download the Ubuntu-Expertiza image
3. Run the .ova file in Virtual Box
4. Run following commands in terminal
git clone http://github.com/user_name/expertiza
cd expertiza
./setup.sh
bundle install
rake db:migrate
rails s
5. Open up the browser and put localhost:3000 in the address bar. The expertiza login page should appear
Understand the Functionality
participant_spec.rb
participant_spec.rb is the file that test should be written in.
participant.rb
participant.rb is used to preparing data for participants enrolled in each course/assignment. It allows the user to review some information relate to the current course/assignment and execute operations such as sorting participant by their names and removing a current participant from the team. It also helps determine users' permission or authorization.
assignment_participant.rb
assignment_participant.rb is a model file related to participant.rb. AssignmentParticipant is a subclass of Participant class, and it overrides some methods of its superclass, which affects the process of writing test code.
Write Test Code
mock instance
All of the mock instances are listed at the beginning of the test file.
let(:topic1) { build(:topic, topic_name: '') } let(:topic2) { build(:topic, topic_name: "topic_name") } let(:student) { build(:student, name: "John Smith", email: "sjohn@ncsu.edu", fullname: "Mr.John Smith") } let(:student1) { build(:student, name: "Alice") } let(:student2) { build(:student, name: "Bob") } let(:assignment) { build(:assignment, name: "assignment") } let(:participant) { build(:participant, user: student, assignment: assignment, can_review: false, handle: "nb") } let(:participant1) { build(:participant, user: student1) } let(:participant2) { build(:participant, user: student2) } let(:assignment_team) { build(:assignment_team, name: "ChinaNo1") } let(:team_user) { build(:team_user, team: assignment_team, user: student) } let(:response) { build(:response, response_map: response_map) } let(:response_map) { build(:review_response_map, assignment: assignment, reviewer: participant1, reviewee: assignment_team) } let(:questions) { build(:question) } let(:questionnaire) { build(:questionnaire) }
team
This case tests the team name of the current user shows up when team method is called
describe "#team" do it "returns the first team of current user" do allow(TeamsUser).to receive(:find_by).with(user: student).and_return(team_user) expect(participant.team.name).to eq "ChinaNo1" end end
responses
This case tests that all of the responses of the current user received show up when responses method is called
describe "#responses" do it "returns all responses this participant received" do allow(participant).to receive(:response).and_return(response) expect(participant.responses).to eq [] end end
able_to_review
This case tests that the permission of whether the current user can review others' work should be returned when able_to_reveiw method is called
describe "#able_to_review" do it "returns whether the current participant has permission to review others' work" do expect(participant.able_to_review).to be false end end
This case tests that an email about assignment registration will be sent to the current user when email method is called.
describe "#email" do it "sends an assignment registration email to current user" do allow(User).to receive(:find_by).with(id: nil).and_return(student) allow(Assignment).to receive(:find_by).with(id: nil).and_return(assignment) expect(participant.email(12_345, 'homepage').subject).to eq "You have been registered as a participant in the Assignment assignment" expect(participant.email(12_345, 'homepage').to[0]).to eq "expertiza.development@gmail.com" expect(participant.email(12_345, 'homepage').from[0]).to eq "expertiza.development@gmail.com" end end
topic_name
Two cases have been designed for testing this method. When the topic_name method is called, the dash (-) should show up when there is no such topic or the topic name is empty, or the topic name should appear.
context "when the topic is nil or the topic name is empty" do it "returns em dash (—)" do expect(participant.topic_name).to eq "<center>—</center>" end end
context "when the topic is not nil and the topic name is not empty" do it "returns the topic name" do allow(participant).to receive(:topic).and_return(topic2) expect(participant.topic_name).to eq "topic_name" end end
sort_by_name
describe ".sort_by_name" do it "returns sorted participants based on their user names" do expect(Participant.sort_by_name([participant1, participant2, participant]).collect {|p| p.user.name }).to eq ["Alice", "Bob", "John Smith"] end end
scores
context "when the round is nil" do it "uses questionnaire symbol as a hash key and populates the score hash" do allow(AssignmentQuestionnaire).to receive_message_chain(:find_by, :used_in_round).with(assignment_id: 1, questionnaire_id: 2)\ .with(no_args).and_return(nil) allow(assignment).to receive(:questionnaires).and_return([questionnaire]) allow(questionnaire).to receive(:get_assessments_for).with(participant).and_return(response) allow(Answer).to receive(:compute_scores).and_return(max: 10, min: 10, avg: 10) allow(assignment).to receive(:compute_total_score).with(any_args).and_return(10) expect(participant.scores(questions).inspect).to eq("{:participant=>#<AssignmentParticipant id: nil, can_submit: true, "\ "can_review: false, user_id: nil, parent_id: nil, submitted_at: nil, permission_granted: nil, penalty_accumulated: 0, grade: nil, "\ "type: \"AssignmentParticipant\", handle: \"nb\", time_stamp: nil, digital_signature: nil, duty: nil, can_take_quiz: true, Hamer: 1.0, "\ "Lauw: 0.0>, :review=>{:assessments=>#<Response id: nil, map_id: nil, additional_comment: nil, created_at: nil, updated_at: nil, version_num: nil, "\ "round: 1, is_submitted: false>, :scores=>{:max=>10, :min=>10, :avg=>10}}, :total_score=>10}") end end
context "when the round is not nil" do it "uses questionnaire symbol with round as hash key and populates the score hash" do allow(AssignmentQuestionnaire).to receive_message_chain(:find_by, :used_in_round).with(assignment_id: 1, questionnaire_id: 2)\ .with(no_args).and_return(3) allow(assignment).to receive(:questionnaires).and_return([questionnaire]) allow(questionnaire).to receive(:get_assessments_for).with(participant).and_return(response) allow(Answer).to receive(:compute_scores).and_return(max: 10, min: 10, avg: 10) allow(assignment).to receive(:compute_total_score).with(any_args).and_return(10) expect(participant.scores(questions).inspect).to eq("{:participant=>#<AssignmentParticipant id: nil, can_submit: true, "\ "can_review: false, user_id: nil, parent_id: nil, submitted_at: nil, permission_granted: nil, penalty_accumulated: 0, grade: nil, "\ "type: \"AssignmentParticipant\", handle: \"nb\", time_stamp: nil, digital_signature: nil, duty: nil, can_take_quiz: true, Hamer: 1.0, "\ "Lauw: 0.0>, :review3=>{:assessments=>#<Response id: nil, map_id: nil, additional_comment: nil, created_at: nil, updated_at: nil, version_num: nil, "\ "round: 1, is_submitted: false>, :scores=>{:max=>10, :min=>10, :avg=>10}}, :total_score=>10}") end end
get_permissions
context "when the current user is a participant" do it "returns a hash with value {can_submit: true, can_review: true, can_take_quiz: true}" do expect(Participant.get_permissions("participant")).to eq(can_submit: true, can_review: true, can_take_quiz: true) end end
context "when the current user is a reader" do it "returns a hash with value {can_submit: false, can_review: true, can_take_quiz: true}" do expect(Participant.get_permissions("reader")).to eq(can_submit: false, can_review: true, can_take_quiz: true) end end
context "when the current user is a submitter" do it "returns a hash with value {can_submit: true, can_review: false, can_take_quiz: false}" do expect(Participant.get_permissions("submitter")).to eq(can_submit: true, can_review: false, can_take_quiz: false) end end
context "when the current user is a reviewer" do it "returns a hash with value {can_submit: false, can_review: true, can_take_quiz: false}" do expect(Participant.get_permissions("reviewer")).to eq(can_submit: false, can_review: true, can_take_quiz: false) end end
get_authorization
context " when the current user is able to submit work, review others' work and take quizzes" do it "indicates the current user is a participant" do expect(Participant.get_authorization(true, true, true)).to eq "participant" end end
context " when the current user is unable to submit work but is able to review others' work and take quizzes" do it "indicates the current user is a reader" do expect(Participant.get_authorization(false, true, true)).to eq "reader" end end
context " when the current user is able to submit work but is unable to review others' work and take quizzes" do it "indicates the current user is a submitter" do expect(Participant.get_authorization(true, false, false)).to eq "submitter" end end
context " when the current user is unable to submit work and take quizzes but is able to review others' work" do it "indicates the current user is a reviewer" do expect(Participant.get_authorization(false, true, false)).to eq "reviewer" end end
handle
context "when the anonymized view is turn on" do it "always returns 'handle'" do allow(User).to receive(:anonymized_view?).with(nil).and_return(true) expect(participant.handle).to eq('handle') end end
context "when the anonymized view is turn off" do it "returns the handle of current participant" do allow(User).to receive(:anonymized_view?).with(nil).and_return(false) expect(participant.handle).to eq("nb") end end
delete
context "when force deleting the response maps related to current participant" do it "force deletes current participant, related response maps, teams, and team users" do allow(ResponseMap).to receive(:where).with("reviewee_id = ? or reviewer_id = ?", nil, nil).and_return([response_map]) expect(participant.delete(true).handle).to eq 'nb' end end
context "when not force deleting the response maps related to current participant" do context "when there are no related response maps to this participant and current participant did not join any teams" do it "force deletes current participant, related response maps, teams, and team users" do allow(ResponseMap).to receive(:where).with("reviewee_id = ? or reviewer_id = ?", nil, nil).and_return([]) allow(participant).to receive(:team).and_return(nil) expect(participant.delete(false).handle).to eq 'nb' end end
context "when there are some related response maps to this participant or current participant join one or more teams" do it "raises an exception saying 'Associations exist for this participant'" do allow(participant).to receive(:team).and_return(assignment_team) expect { participant.delete(false) }.to raise_error("Associations exist for this participant.") end end end
force_delete
context "when current participant has already joined a team" do context "when the current participant is the only member in that team" do it "deletes that team and current participant" do allow(participant).to receive(:team).and_return(assignment_team) allow(participant).to receive_message_chain(:team, :teams_users, :length).and_return(1) expect(participant.force_delete([response_map]).handle).to eq 'nb' end end
context "when the team has other team members" do it "removes the current participant from that team and deletes current participant" do allow(participant).to receive(:team).and_return(assignment_team) allow(participant).to receive_message_chain(:team, :teams_users, :length).and_return(3) allow(participant).to receive_message_chain(:team, :teams_users).and_return([student]) expect(participant.force_delete([response_map]).handle).to eq 'nb' end end end
context "when the current participant did not join a team" do it "deletes current participant directly" do allow(participant).to receive(:team).and_return(nil) expect(participant.force_delete([response_map]).handle).to eq 'nb' end end end
Test Result
With all class and instance methods tested, the path coverage is 99% with all 97 lines covered. Screenshot of the tests passing is shown below.
Full video of running the test can be found at https://youtu.be/PGyQAtbmI5w.
Reference
Behavior-DrivenDevelopment https://en.wikipedia.org/wiki/Behavior-driven_development
Expertiza https://expertiza.ncsu.edu/
Rspec Documentation https://relishapp.com/rspec
Github (Expertiza) https://github.com/expertiza/expertiza