OSS E1852.rb: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
 
(64 intermediate revisions by 2 users not shown)
Line 1: Line 1:
This wiki page is for the description of changes made under E1852 OSS assignment for Fall 2018, CSC/ECE 517
This wiki page is for the description of changes made under E1852 OSS assignment for Fall 2018, CSC/ECE 517


== Background ==
== Introduction & Background ==


Expertiza is an open source web-based peer review system developed and maintained by students and
[http://expertiza.ncsu.edu/ Expertiza] is an open source web-based peer review system developed and maintained by students and
faculty members at North Carolina State University. Features of Expertiza enable students to
faculty members at North Carolina State University. Features of Expertiza enable students to
work collaboratively in teams on course projects and assignments.
work collaboratively in teams on course projects and assignments.


== Problem ==
=== Project Purpose ===
The purpose and goal behind this project is to allow students the opportunity to refactor, implement, and test [https://en.wikipedia.org/wiki/Open-source_software/ OSS] - open source software. Our team chose to work on unit testing existing functionalities within Expertiza, as a way to identify possible pitfalls or possible improvements that could be applied to the existing code.


There are not enough unit tests for the Participant model of Expertiza. Current path coverage of participant.rb is only 36.08%.
=== Unit Testing ===
Unit Tests are implemented to ensure proper independence and desired functionality of methods in a model. Unit Testing is an essential component for the following strategies:
:*Test Driven Development(TDD), where unit tests are used to drive the development of a product.
:*Behavior Driven Development(BDD), which augments test driven development through the application of principles such as "Five Why's" and "Outside In" to identify and implement behaviors that are directly beneficial to the outcome of the product.  
In this project, RSpec testing models were used to satisfy the testing requirements of behavior driven development.


== Work to be done ==
== Participant.rb Overview ==
The Participant model is used to prepare data for participants enrolled in each course or specific assignment. Participants have many relational dependencies such as having associated reviews, responses, and assignment teams. Participants are also assigned to various topics and assignments depending on the involvement of the participant's user. Functionality of the model allows the return of the participant's current team, his or her current responses, identifying attributes such as name and user handle, as well as the participant's role permissions and authorizations. Additional components allow for the generation of notification emails and the calculation of a participant's grade on an assignment.


* Write unit test using Rspec
== Problem Statement ==


* Achieve path coverage more than 90%
''There are not enough unit tests for the Participant model of Expertiza. Current path coverage of participant.rb is only 36.08%.''


* Achieve as high branch coverage
=== Modified Files ===
:* spec/models/particpant_spec.rb
:* spec/factories/factories.rb


== Modified Files ==
=== Work to be Completed ===
:* Write unit tests using Rspec
:* Achieve a path coverage of more than 90%
:* Achieve a high branch coverage


* spec/models/particpant_spec.rb
=== Team Members ===
:* Manjunath Gaonkar
:* Zhikai Gao
:* Carmen Bentley


== Unit Test Description ==
=== Project Mentor ===
:* Zhewei Hu


Unit Tests are implemented to ensure proper independence and desired functionality of methods in a model. Unit Testing is an essential component for the following strategies:
== Test Plan ==
*Test Driven Development(TDD), where unit tests are used to drive the development of a product.
=== Plan of Work ===
*Behavior Driven Development(BDD), which augments test driven development through the application of principles such as "Five Why's" and "Outside In" to identify and implement behaviors that are directly beneficial to the outcome of the product.  
To achieve a goal of more than 90% test coverage, our team completed the following test plan:
In this project, we are using RSpec testing models to achieve behavior driven development.
:*Obtain the appropriate testing environment via a provided virtual box image and RSPEC testing framework.
:*Acquire a deeper understanding of the Participant model, its functionalities and dependencies.
:*Create factories and doubles to assist in the testing of model methods.
:*Mock message passing and expected outcomes.
:*Apply generated helper objects and mocks to achieve high test coverage of each method within the Participant model.
 
=== Testing Design Choices ===
Our approach to testing the Participant model involved simplistic use of helper objects and mocked message passing. Because functions proved independent, it was unnecessary to include any common test case setup in a before block. Due to the nature of testing the successful send of a user notification email, it was necessary, however, to include an after block that would reset any system notification. Thus, accurately reflecting the expected outcome when testing the email function. The design approach associated with the scores function required a more involved development of object relations such as assignment questions having answers that could be scored according to the scoring policies associated with the questionnaire type. Further association of scoring rounds was simulated to achieve full coverage.


== Participant Unit Test ==
===Created Factories===
===Factories===
The Participant model is a super class that encompasses various types of participants. The current factory file contains a default participant build that is associated with a subclass "Assignment Participant". In order to ensure the testing of the super class functions, we created a participant factory that is derived directly from the Participant model.
The following factories were generated to assist in the testing of the Participant model.


<pre>
<pre>
let(:user) {
  factory: participantSuper, class: Participant do
build( :student, id: 1, name: 'no name', fullname: 'no one') }
    can_submit true
  let(:team) {
    can_review true
build( :assignment_team, id: 1, name: 'myTeam' ) }
    assignment { Assignment.first || association(:assignment) }
  let(:team_user) {
    association :user, factory: :student
build( :team_user, id: 1, user: user, team: team) }
    submitted_at nil
 
    permission_granted nil
  let(:topic){ build( :topic ) }
    penalty_accumulated 0
 
    grade nil
  let(:participant) {
    handle "handle"
build(:participant,
    time_stamp nil
user: build( :student, name: "Jane", fullname: "Doe, Jane", id: 1 ) ) }
    digital_signature nil
  let(:participant2) {
    duty nil
build(:participant,
    can_take_quiz true
user: build( :student, name: "John", fullname: "Doe, John", id: 2 ) ) }
   end
   let(:participant3) {
</pre>
build(:participant, can_review: false,
user: build(:student, name: "King", fullname: "Titan, King", id: 3 ) ) }


===Variables===
The following variables were generated using the provided factory class to assist in the testing of the Participant model.
<pre>
  let(:team) { build(:assignment_team, id: 1, name: 'myTeam') }
  let(:user) { build(:student, id: 4, name: 'no name', fullname: 'no two') }
  let(:team_user) { build(:team_user, id: 1, user: user, team: team) }
  let(:topic) { build(:topic) }
  let(:participant) { build(:participant, user: build(:student, name: "Jane", fullname: "Doe, Jane", id: 1)) }
  let(:participant2) { build(:participant, user: build(:student, name: "John", fullname: "Doe, John", id: 2)) }
  let(:participant3) { build(:participant, can_review: false, user: build(:student, name: "King", fullname: "Titan, King", id: 3)) }
  let(:participant4) { build(:participantSuper, can_review: false, user: user) }
   let(:assignment) { build(:assignment, id: 1, name: 'no assgt') }
   let(:assignment) { build(:assignment, id: 1, name: 'no assgt') }
  let(:review_response_map) { build(:review_response_map, assignment: assignment, reviewer: participant, reviewee: team) }
  let(:answer) { Answer.new(answer: 1, comments: 'Answer text', question_id: 1) }
  let(:response) { build(:response, id: 1, map_id: 1, response_map: review_response_map, scores: [answer]) }
  let(:question1) { Criterion.new(id: 1, weight: 2, break_before: true) }
  let(:question2) { Criterion.new(id: 2, weight: 2, break_before: true) }
  let(:questionnaire1) { ReviewQuestionnaire.new(id: 1, questions: [question1], max_question_score: 5) }
  let(:questionnaire2) { ReviewQuestionnaire.new(id: 2, questions: [question2], max_question_score: 5) }


  after(:each) do
    ActionMailer::Base.deliveries.clear
  end
</pre>
</pre>


===Tested Methods===
==Tested Methods==


*'''team''': returns the team associated with a participant.
*''team'' : Returns the team associated with a participant.
:- mock simulates the find_by method in TeamsUser object and returns team_user
<pre>
<pre>
  describe '#team' do
    it 'returns the team of the participant' do
      allow(TeamsUser).to receive(:find_by).and_return(team_user)
      expect( participant4.team ).to eq(team)
    end
  end
</pre>
</pre>
* response : This method returns the response associated with the perticular participant. we use mock method to simulate return of the response from the participant response_map
*''response'' : Returns the response associated with a participant.
:- mock used to simulate the return of a response from the participant's response_map
<pre>
<pre>
describe '#response' do
  describe '#response' do
     it 'Returns the responses that are associated with this participant' do
     it 'Returns the participant responses' do
       allow( participant ).to receive( :responses ).and_return( response )
       allow(participant.response_maps).to receive(:map).and_return(response)
       expect( participant.responses ).to eq( response )
       expect(participant.responses).to eq(response)
     end
     end
   end
   end
</pre>
</pre>


*name: Returns the name of the participant  
*''name'' : Returns the name of a participant.
<pre>
<pre>
describe "#name" do
  describe "#name" do
     it "returns the name of the user" do
     it "returns the name of the user" do
       expect( participant.name ).to eq( "Jane" )
       expect(participant.name).to eq("Jane")
     end
     end
   end
   end
</pre>
</pre>


*fullname : Returns the full name of the participant  
*''fullname'' : Returns the full name of a participant.
<pre>
<pre>
describe "#fullname" do
  describe "#fullname" do
     it "returns the full name of the user" do
     it "returns the full name of the user" do
       expect( participant.fullname ).to eq( "Doe, Jane" )
       expect(participant.fullname).to eq("Doe, Jane")
     end
     end
   end
   end
</pre>
</pre>


*handle : Returns handle of the participant
*''handle'' : Returns the handle of a participant.
<pre>
<pre>
describe '#handle' do
  describe '#handle' do
     it 'returns the handle of the participant' do
     it 'returns the handle of the participant' do
       expect( participant.handle( nil ) ).to eq( "handle" )
       expect(participant.handle(nil)).to eq("handle")
     end
     end
  end
</pre>
</pre>


* delete: It deletes the participant if the participant doesn't have any association adn value of "force" is nil else it will raise an exception
*''delete'' : Deletes a participant according to the participant's current associations and the value passed to the variable 'force'.
:- mock to simulate the return of the participant team when asked
:- mock used in the testing of a forceful delete of a participant with a team association consisting of a single team user by simulating the return of team length to be one when asked
 
<pre>
<pre>
describe '#delete' do
  describe '#delete' do
     it 'deletes a participant if no associations exist and force is nil' do
     it 'deletes a participant if no associations exist and force is nil' do
       expect( participant.delete( nil ) ).to eq( participant )
       expect(participant.delete(nil)).to eq(participant)
     end
     end
     it 'deletes a participant if no associations exist and force is true' do
     it 'deletes a participant if no associations exist and force is true' do
       expect( participant.delete( true ) ).to eq( participant )
       expect(participant.delete(true)).to eq(participant)
     end
     end
     it 'deletes a participant if associations exist and force is true' do
     it 'delete a participant with associations and force is true and multiple team_users' do
       allow( participant ).to receive( :team ).and_return( team )
       allow(participant).to receive(:team).and_return(team)
       expect( participant.delete( true ) ).to eq( participant )
       expect(participant.delete(true)).to eq(participant)
     end
     end
     it 'deletes a participant if associations exist and force is true' do
     it 'delete participant with associations and force is true and single team_user' do
       allow( participant ).to receive( :team ).and_return( team )
       allow(participant).to receive(:team).and_return(team)
       allow( team ).to receive( :teams_users ).and_return( length: 1 )
       allow(team).to receive(:teams_users).and_return(length: 1)
       expect( participant.delete( true ) ).to eq( participant )
       expect(participant.delete(true)).to eq(participant)
     end
     end
     it 'raises execption when trying to delect participant where associations exists and force is nil' do
     it 'raises error, delete participant with associations and force is nil' do
       allow( participant ).to receive( :team ).and_return( team )
       allow(participant).to receive(:team).and_return(team)
       expect{ participant.delete( nil ) }.to raise_error.with_message("Associations exist for this participant.")
       expect{participant.delete(nil)}.to raise_error.with_message("Associations exist for this participant.")
     end
     end
   end
   end
</pre>
</pre>
*force_delete: This method is called inside the delete method . coverage for this is handled in delete method. hence there is not explicit cases are written.
*''force_delete'' : Method called inside #delete to remove the participant and all necessary associations. Testing of this method is covered through the testing of #delete. Therefore, there are not explicit test cases written.
*topic_name: Retunrs the topic name associated with the participant
*''topic_name'' : Returns the topic name associated with a participant.
:- mock used in the testing of an existing topic name by simulating the return of a participant's topic when asked
<pre>
<pre>
describe '#topic_name' do
  describe '#topic_name' do
     it 'returns the topic name associated with the participant topic name is nil' do
     it 'returns the participant topic name when nil' do
       expect( participant.topic_name ).to eq( '<center>&#8212;</center>' )
       expect(participant.topic_name).to eq('<center>&#8212;</center>')
     end
     end
     it 'returns the topic name associated with the participant topic name has value' do
     it 'returns the participant topic name when not nil' do
       allow( participant ).to receive( :topic ).and_return( topic )
       allow(participant).to receive(:topic).and_return(topic)
       expect( participant.topic_name ).to eq( "Hello world!" )   
       expect(participant.topic_name).to eq("Hello world!")   
     end
     end
   end
   end
</pre>
</pre>
*able_to_review: check the review rights of the participant  
*''able_to_review'' : Checks and returns the review rights of a participant.
<pre>
<pre>
describe '#able_to_review' do
  describe '#able_to_review' do
     it '#able_to_review when can_review is true' do
     it 'returns true when can_review is true' do
       expect(participant.able_to_review).to eq(true)
       expect(participant.able_to_review).to eq(true)
     end
     end
  end
     it '#returns false when can_review is false' do
 
  describe '#able_to_review' do
     it '#able_to_review when can_review is false' do
       expect(participant3.able_to_review).to eq(false)
       expect(participant3.able_to_review).to eq(false)
     end
     end
   end
   end
</pre>
</pre>
*email: sends an email to participant
*''email'' : Sends an email to a participant, verifying an assignment registration.
<pre>
<pre>
describe '#email' do
  describe '#email' do
     it 'sends an email to the participant' do
     it 'sends an email to the participant' do
       expect { participant.email("Missing 'pw'", "Missing 'home_page'") }.to change {
       expect {participant.email("Missing 'pw'", "Missing 'home_page'")}.to change{
ActionMailer::Base.deliveries.count }.by(1)
ActionMailer::Base.deliveries.count}.by(1)
     end
     end
   end
   end
</pre>
</pre>
*score
*''scores'' : Returns a hash of values that are applicable to a participant's assignment scores (i.e. { participant information, assignment question information, total scores })
<pre>
:- We mock participant.assignment to assign value to list of questionnaire , and for each questionnare we get assessment. For each questionnaire mock compute_score there by getting the score value. Here we have covered the case where assignments having both one and two rounds of rubrics.
<pre>  
  describe '#score' do
    it 'Get participant score within a round' do
      questions = {review: [question2], review1: [question1]}
      test = [questionnaire1, questionnaire2]
 
      allow(participant.assignment).to receive(:questionnaires).and_return(test)
      assessment = double("review")
 
      test.each do |q|
        assignment_questionnaire_map = double("assignment_questionnaire", used_in_round: nil)
        assignment_questionnaire_map = double("assignment_questionnaire", used_in_round: 1) unless q.id != 2
 
        allow(AssignmentQuestionnaire).to receive(:find_by).with(assignment_id: 1, questionnaire_id: q.id).and_return(assignment_questionnaire_map)
        assessment = double("review")
        allow(q).to receive(:get_assessments_for).with(participant).and_return(assessment)
        allow(Answer).to receive(:compute_scores).with(assessment, questions[:review]).and_return(5)
        allow(Answer).to receive(:compute_scores).with(assessment, questions[:review1]).and_return(6)
      end
 
      allow(participant.assignment).to receive(:compute_total_score).with(any_args).and_return(75)
      check = participant.scores(questions)
 
      expect(check).to include(participant: participant)
      expect(check[:review1].to_s).to include({assessments: assessment, scores: 6}.to_s)
      expect(check[:review].to_s).to eq({assessments: assessment, scores: 5}.to_s)
      expect(check).to include(total_score: 75)
    end
  end
</pre>
</pre>
*get_permissions:Returns the permission of various users such as participant , reader, reviewer, submitter
*''get_permissions'' : Returns the permissions association with a participant based on participant type (i.e. participant, reader, reviewer, submitter).
<pre>
<pre>
describe '#get_permissions' do
  describe '#get_permissions' do
     it 'returns the permissions of participant' do
     it 'returns the permissions of participant' do
       expect( Participant.get_permissions( 'participant' ) ).to contain_exactly(
       expect(Participant.get_permissions('participant')).to contain_exactly(
[ :can_submit, true ], [ :can_review, true ], [ :can_take_quiz, true ] )
[:can_submit, true], [:can_review, true], [:can_take_quiz, true])
     end
     end
     it 'returns the permissions of reader' do
     it 'returns the permissions of reader' do
       expect( Participant.get_permissions( 'reader' ) ).to contain_exactly(
       expect(Participant.get_permissions('reader')).to contain_exactly(
[ :can_submit, false ], [ :can_review, true ], [ :can_take_quiz, true ] )
[:can_submit, false], [:can_review, true], [:can_take_quiz, true])
     end
     end
     it 'returns the permissions of reviewer' do
     it 'returns the permissions of reviewer' do
       expect( Participant.get_permissions( 'reviewer' ) ).to contain_exactly(
       expect( Participant.get_permissions('reviewer')).to contain_exactly(
[ :can_submit, false ], [ :can_review, true ], [ :can_take_quiz, false ] )
[:can_submit, false], [:can_review, true], [:can_take_quiz, false])
     end
     end
     it 'returns the permissions of submitter' do
     it 'returns the permissions of submitter' do
       expect(Participant.get_permissions('submitter')).to contain_exactly(
       expect(Participant.get_permissions('submitter')).to contain_exactly(
[:can_submit, true], [:can_review, false], [:can_take_quiz, false] )
[:can_submit, true], [:can_review, false], [:can_take_quiz, false])
     end
     end
   end
   end
</pre>
</pre>
*get_authorization: Returns the authorization role based on the access rights (can_submit,can_review,can_take_quiz).
*''get_authorization'' : Returns the participant's authorization role based on its access rights (can_submit,can_review,can_take_quiz).
<pre>
<pre>
describe '#get_authorization' do
  describe '#get_authorization' do
     it 'returns participant when no arguments are pasted' do
     it 'returns participant when no arguments are passed' do
       expect( Participant.get_authorization( nil, nil, nil ) ).to eq( 'participant' )
       expect(Participant.get_authorization(nil, nil, nil)).to eq('participant')
     end
     end
     it 'returns reader when no arguments are pasted' do
     it 'returns reader when no arguments are passed' do
       expect( Participant.get_authorization( false, true, true ) ).to eq( 'reader' )
       expect(Participant.get_authorization(false, true, true)).to eq('reader')
     end
     end
     it 'returns submitter when no arguments are pasted' do
     it 'returns submitter when no arguments are passed' do
       expect( Participant.get_authorization( true, false, false ) ).to eq( 'submitter' )
       expect(Participant.get_authorization(true, false, false)).to eq('submitter')
     end
     end
     it 'returns reviewer when no arguments are pasted' do
     it 'returns reviewer when no arguments are passed' do
       expect( Participant.get_authorization( false, true, false ) ).to eq( 'reviewer' )
       expect(Participant.get_authorization(false, true, false)).to eq('reviewer')
     end
     end
   end
   end
</pre>
</pre>
*sort_by_name: Returns the sorted participants name.
*''sort_by_name'' : Returns the sorted array of participants by name.
<pre>
<pre>
describe '#sort_by_name' do
  describe '#sort_by_name' do
     it 'returns a sorted list of participants alphabetical by name' do
     it 'returns a sorted list of participants alphabetical by name' do
       unsorted = [ participant3, participant, participant2 ]
       unsorted = [participant3, participant, participant2]
       sorted = [ participant, participant2, participant3 ]
       sorted = [participant, participant2, participant3 ]
       expect( Participant.sort_by_name( unsorted ) ).to eq( sorted )
       expect(Participant.sort_by_name(unsorted)).to eq(sorted)
     end
     end
   end
   end
</pre>
</pre>


== Running the Tests ==
== Running Tests ==


To run the test run below command in terminal
Run all Rspec tests from the terminal :
<pre>
<pre>
  rspec spec/models/participant_spec.rb
  rspec spec/models/participant_spec.rb
</pre>
</pre>


== External links ==
Run a specific Rspec test from the terminal :
<pre>
rspec spec/models/participant_spec.rb:{line number}
</pre>
 
Run Rspec tests from terminal with mutations to test quality of test cases :
<pre>
RAILS_ENV=test bundle exec mutant -r ./config/environment --use rspec Participant
</pre>
:* more information about mutation testing can be found in the link provided in the ''External Links'' section of this wiki.
 
== Testing Outcomes ==
Through the implementation of quality unit tests, 100.00% path coverage was achieved.
 
:*[https://drive.google.com/file/d/1Q2y6P-uSLCtJj1JuvJ37vgk-MJc1WXSN/view?usp=sharing Result of RSpec run]


* https://github.com/carmenbentley/expertiza
:*[https://drive.google.com/file/d/1UQKs_ThAarRU-O0ecnEKPCo2vHCNss0Y/view?usp=sharing Coverage of RSpec run]


== External links ==
== External links ==
References
'''Expertiza Github with implemented tests for Participant model :'''
:* https://github.com/carmenbentley/expertiza
::- ''tests can be found within rspec/models/participant_spec.rb''
'''Mutant gem description and use :'''
:* https://github.com/mbj/mutant
''' Demo video can be found here:'''
:*https://drive.google.com/open?id=1AdqLE_yH5bxX_cynYvfM6lidWSiLu6Hg
::-This demo video involves an in depth walk through of the testing code
:*https://drive.google.com/open?id=1o9Y0Ifsg2GLjaJKvSKBuJKJuycLOkKQZ
::-This demo video involves a short capture of the RSpec test run.

Latest revision as of 03:04, 9 November 2018

This wiki page is for the description of changes made under E1852 OSS assignment for Fall 2018, CSC/ECE 517

Introduction & Background

Expertiza is an open source web-based peer review system developed and maintained by students and faculty members at North Carolina State University. Features of Expertiza enable students to work collaboratively in teams on course projects and assignments.

Project Purpose

The purpose and goal behind this project is to allow students the opportunity to refactor, implement, and test OSS - open source software. Our team chose to work on unit testing existing functionalities within Expertiza, as a way to identify possible pitfalls or possible improvements that could be applied to the existing code.

Unit Testing

Unit Tests are implemented to ensure proper independence and desired functionality of methods in a model. Unit Testing is an essential component for the following strategies:

  • Test Driven Development(TDD), where unit tests are used to drive the development of a product.
  • Behavior Driven Development(BDD), which augments test driven development through the application of principles such as "Five Why's" and "Outside In" to identify and implement behaviors that are directly beneficial to the outcome of the product.

In this project, RSpec testing models were used to satisfy the testing requirements of behavior driven development.

Participant.rb Overview

The Participant model is used to prepare data for participants enrolled in each course or specific assignment. Participants have many relational dependencies such as having associated reviews, responses, and assignment teams. Participants are also assigned to various topics and assignments depending on the involvement of the participant's user. Functionality of the model allows the return of the participant's current team, his or her current responses, identifying attributes such as name and user handle, as well as the participant's role permissions and authorizations. Additional components allow for the generation of notification emails and the calculation of a participant's grade on an assignment.

Problem Statement

There are not enough unit tests for the Participant model of Expertiza. Current path coverage of participant.rb is only 36.08%.

Modified Files

  • spec/models/particpant_spec.rb
  • spec/factories/factories.rb

Work to be Completed

  • Write unit tests using Rspec
  • Achieve a path coverage of more than 90%
  • Achieve a high branch coverage

Team Members

  • Manjunath Gaonkar
  • Zhikai Gao
  • Carmen Bentley

Project Mentor

  • Zhewei Hu

Test Plan

Plan of Work

To achieve a goal of more than 90% test coverage, our team completed the following test plan:

  • Obtain the appropriate testing environment via a provided virtual box image and RSPEC testing framework.
  • Acquire a deeper understanding of the Participant model, its functionalities and dependencies.
  • Create factories and doubles to assist in the testing of model methods.
  • Mock message passing and expected outcomes.
  • Apply generated helper objects and mocks to achieve high test coverage of each method within the Participant model.

Testing Design Choices

Our approach to testing the Participant model involved simplistic use of helper objects and mocked message passing. Because functions proved independent, it was unnecessary to include any common test case setup in a before block. Due to the nature of testing the successful send of a user notification email, it was necessary, however, to include an after block that would reset any system notification. Thus, accurately reflecting the expected outcome when testing the email function. The design approach associated with the scores function required a more involved development of object relations such as assignment questions having answers that could be scored according to the scoring policies associated with the questionnaire type. Further association of scoring rounds was simulated to achieve full coverage.

Created Factories

The Participant model is a super class that encompasses various types of participants. The current factory file contains a default participant build that is associated with a subclass "Assignment Participant". In order to ensure the testing of the super class functions, we created a participant factory that is derived directly from the Participant model.

  factory: participantSuper, class: Participant do
    can_submit true
    can_review true
    assignment { Assignment.first || association(:assignment) }
    association :user, factory: :student
    submitted_at nil
    permission_granted nil
    penalty_accumulated 0
    grade nil
    handle "handle"
    time_stamp nil
    digital_signature nil
    duty nil
    can_take_quiz true
  end

Variables

The following variables were generated using the provided factory class to assist in the testing of the Participant model.

  let(:team) { build(:assignment_team, id: 1, name: 'myTeam') }
  let(:user) { build(:student, id: 4, name: 'no name', fullname: 'no two') }
  let(:team_user) { build(:team_user, id: 1, user: user, team: team) }
  let(:topic) { build(:topic) }
  let(:participant) { build(:participant, user: build(:student, name: "Jane", fullname: "Doe, Jane", id: 1)) }
  let(:participant2) { build(:participant, user: build(:student, name: "John", fullname: "Doe, John", id: 2)) }
  let(:participant3) { build(:participant, can_review: false, user: build(:student, name: "King", fullname: "Titan, King", id: 3)) }
  let(:participant4) { build(:participantSuper, can_review: false, user: user) }
  let(:assignment) { build(:assignment, id: 1, name: 'no assgt') }
  let(:review_response_map) { build(:review_response_map, assignment: assignment, reviewer: participant, reviewee: team) }
  let(:answer) { Answer.new(answer: 1, comments: 'Answer text', question_id: 1) }
  let(:response) { build(:response, id: 1, map_id: 1, response_map: review_response_map, scores: [answer]) }
  let(:question1) { Criterion.new(id: 1, weight: 2, break_before: true) }
  let(:question2) { Criterion.new(id: 2, weight: 2, break_before: true) }
  let(:questionnaire1) { ReviewQuestionnaire.new(id: 1, questions: [question1], max_question_score: 5) }
  let(:questionnaire2) { ReviewQuestionnaire.new(id: 2, questions: [question2], max_question_score: 5) }

  after(:each) do
    ActionMailer::Base.deliveries.clear
  end

Tested Methods

  • team : Returns the team associated with a participant.
- mock simulates the find_by method in TeamsUser object and returns team_user
  describe '#team' do
    it 'returns the team of the participant' do
      allow(TeamsUser).to receive(:find_by).and_return(team_user)
      expect( participant4.team ).to eq(team)
    end
  end
  • response : Returns the response associated with a participant.
- mock used to simulate the return of a response from the participant's response_map
  describe '#response' do
    it 'Returns the participant responses' do
      allow(participant.response_maps).to receive(:map).and_return(response)
      expect(participant.responses).to eq(response)
    end
  end
  • name : Returns the name of a participant.
  describe "#name" do
    it "returns the name of the user" do
      expect(participant.name).to eq("Jane")
    end
  end
  • fullname : Returns the full name of a participant.
  describe "#fullname" do
    it "returns the full name of the user" do
      expect(participant.fullname).to eq("Doe, Jane")
    end
  end
  • handle : Returns the handle of a participant.
  describe '#handle' do
    it 'returns the handle of the participant' do
      expect(participant.handle(nil)).to eq("handle")
    end
  end
  • delete : Deletes a participant according to the participant's current associations and the value passed to the variable 'force'.
- mock to simulate the return of the participant team when asked
- mock used in the testing of a forceful delete of a participant with a team association consisting of a single team user by simulating the return of team length to be one when asked
  describe '#delete' do
    it 'deletes a participant if no associations exist and force is nil' do
      expect(participant.delete(nil)).to eq(participant)
    end
    it 'deletes a participant if no associations exist and force is true' do
      expect(participant.delete(true)).to eq(participant)
    end
    it 'delete a participant with associations and force is true and multiple team_users' do
      allow(participant).to receive(:team).and_return(team)
      expect(participant.delete(true)).to eq(participant)
    end
    it 'delete participant with associations and force is true and single team_user' do
      allow(participant).to receive(:team).and_return(team)
      allow(team).to receive(:teams_users).and_return(length: 1)
      expect(participant.delete(true)).to eq(participant)
    end
    it 'raises error, delete participant with associations and force is nil' do
      allow(participant).to receive(:team).and_return(team)
      expect{participant.delete(nil)}.to raise_error.with_message("Associations exist for this participant.")
    end
  end
  • force_delete : Method called inside #delete to remove the participant and all necessary associations. Testing of this method is covered through the testing of #delete. Therefore, there are not explicit test cases written.
  • topic_name : Returns the topic name associated with a participant.
- mock used in the testing of an existing topic name by simulating the return of a participant's topic when asked
  describe '#topic_name' do
    it 'returns the participant topic name when nil' do
      expect(participant.topic_name).to eq('<center>—</center>')
    end
    it 'returns the participant topic name when not nil' do
      allow(participant).to receive(:topic).and_return(topic)
      expect(participant.topic_name).to eq("Hello world!")   
    end
  end
  • able_to_review : Checks and returns the review rights of a participant.
  describe '#able_to_review' do
    it 'returns true when can_review is true' do
      expect(participant.able_to_review).to eq(true)
    end
    it '#returns false when can_review is false' do
      expect(participant3.able_to_review).to eq(false)
    end
  end
  • email : Sends an email to a participant, verifying an assignment registration.
  describe '#email' do
    it 'sends an email to the participant' do
      expect {participant.email("Missing 'pw'", "Missing 'home_page'")}.to change{
		ActionMailer::Base.deliveries.count}.by(1)
    end
  end
  • scores : Returns a hash of values that are applicable to a participant's assignment scores (i.e. { participant information, assignment question information, total scores })
- We mock participant.assignment to assign value to list of questionnaire , and for each questionnare we get assessment. For each questionnaire mock compute_score there by getting the score value. Here we have covered the case where assignments having both one and two rounds of rubrics.
    
  describe '#score' do
    it 'Get participant score within a round' do
      questions = {review: [question2], review1: [question1]}
      test = [questionnaire1, questionnaire2]

      allow(participant.assignment).to receive(:questionnaires).and_return(test)
      assessment = double("review")

      test.each do |q|
        assignment_questionnaire_map = double("assignment_questionnaire", used_in_round: nil)
        assignment_questionnaire_map = double("assignment_questionnaire", used_in_round: 1) unless q.id != 2

        allow(AssignmentQuestionnaire).to receive(:find_by).with(assignment_id: 1, questionnaire_id: q.id).and_return(assignment_questionnaire_map)
        assessment = double("review")
        allow(q).to receive(:get_assessments_for).with(participant).and_return(assessment)
        allow(Answer).to receive(:compute_scores).with(assessment, questions[:review]).and_return(5)
        allow(Answer).to receive(:compute_scores).with(assessment, questions[:review1]).and_return(6)
      end

      allow(participant.assignment).to receive(:compute_total_score).with(any_args).and_return(75)
      check = participant.scores(questions)

      expect(check).to include(participant: participant)
      expect(check[:review1].to_s).to include({assessments: assessment, scores: 6}.to_s)
      expect(check[:review].to_s).to eq({assessments: assessment, scores: 5}.to_s)
      expect(check).to include(total_score: 75)
    end
  end
  • get_permissions : Returns the permissions association with a participant based on participant type (i.e. participant, reader, reviewer, submitter).
  describe '#get_permissions' do
    it 'returns the permissions of participant' do
      expect(Participant.get_permissions('participant')).to contain_exactly(
		[:can_submit, true], [:can_review, true], [:can_take_quiz, true])
    end
    it 'returns the permissions of reader' do
      expect(Participant.get_permissions('reader')).to contain_exactly(
		[:can_submit, false], [:can_review, true], [:can_take_quiz, true])
    end
    it 'returns the permissions of reviewer' do
      expect( Participant.get_permissions('reviewer')).to contain_exactly(
		[:can_submit, false], [:can_review, true], [:can_take_quiz, false])
    end
    it 'returns the permissions of submitter' do
      expect(Participant.get_permissions('submitter')).to contain_exactly(
		[:can_submit, true], [:can_review, false], [:can_take_quiz, false])
    end
  end
  • get_authorization : Returns the participant's authorization role based on its access rights (can_submit,can_review,can_take_quiz).
  describe '#get_authorization' do
    it 'returns participant when no arguments are passed' do
      expect(Participant.get_authorization(nil, nil, nil)).to eq('participant')
    end
    it 'returns reader when no arguments are passed' do
      expect(Participant.get_authorization(false, true, true)).to eq('reader')
    end
    it 'returns submitter when no arguments are passed' do
      expect(Participant.get_authorization(true, false, false)).to eq('submitter')
    end
    it 'returns reviewer when no arguments are passed' do
      expect(Participant.get_authorization(false, true, false)).to eq('reviewer')
    end
  end
  • sort_by_name : Returns the sorted array of participants by name.
  describe '#sort_by_name' do
    it 'returns a sorted list of participants alphabetical by name' do
      unsorted = [participant3, participant, participant2]
      sorted = [participant, participant2, participant3 ]
      expect(Participant.sort_by_name(unsorted)).to eq(sorted)
    end
  end

Running Tests

Run all Rspec tests from the terminal :

 rspec spec/models/participant_spec.rb

Run a specific Rspec test from the terminal :

 rspec spec/models/participant_spec.rb:{line number}

Run Rspec tests from terminal with mutations to test quality of test cases :

 RAILS_ENV=test bundle exec mutant -r ./config/environment --use rspec Participant
  • more information about mutation testing can be found in the link provided in the External Links section of this wiki.

Testing Outcomes

Through the implementation of quality unit tests, 100.00% path coverage was achieved.

External links

Expertiza Github with implemented tests for Participant model :

- tests can be found within rspec/models/participant_spec.rb

Mutant gem description and use :

Demo video can be found here:

-This demo video involves an in depth walk through of the testing code
-This demo video involves a short capture of the RSpec test run.