CSC/ECE 517 Fall 2019 - E1949. Write Unit Tests for Importing assignment participants and import glitches

From Expertiza_Wiki
Jump to navigation Jump to search

E1949. Write Unit Tests for Importing assignment participants and import glitches

This page is a description of Expertiza OSS project E.1949 Write Unit Tests for Importing assignment participants and import glitches.

Expertiza Introduction

Expertiza is a web application where students can submit and peer-review learning objects (articles, code, web sites, etc). It is used in select courses at NC State and by professors at several other colleges and universities.

Problem Background

The import feature is the most helpful feature for instructors to set up assignments. The instructors usually have a list of students, teams, etc from their learning management system. Being able to import these into expertiza saves a lot of time when setting up an assignment.

Problem Statement

  • Importing participants redirects to a confirmation screen listing the users that are going to be imported but the users do not seem to be imported.
  • This is an intermittent issue that can be addressed through a test case.

Test Plan

Task 1. Write a test case to import participants.

We looked into the files responsible for importing assignment participants. The imported file goes through the following process.

  • Add one line to expect that #create_new_user method in import_file_helper is NOT called in each test case that the user HAS an account, and do the opposite in case the user DOES NOT HAVE an account.
#spec/models/assignment_particpant_spec.rb

describe ".import" do
    context 'when record is empty' do
      it 'raises an ArgumentError' do
        expect { AssignmentParticipant.import({}, nil, nil, nil) }.to raise_error(ArgumentError, 'No user id has been specified.')
      end
    end

        context 'when the record has less than 4 items' do
        it 'raises an ArgumentError' do
          row = {name: 'no one', fullname: 'no one', email: 'no_one@email.com'}
          expect(ImportFileHelper).not_to receive(:create_new_user)
          expect { AssignmentParticipant.import(row, nil, nil, nil) }.to raise_error('The record containing no one does not have enough items.')
        end
      end

        context 'when certain assignment cannot be found' do
          it 'creates a new user based on import information and raises an ImportError' do
            allow(Assignment).to receive(:find).with(1).and_return(nil)
            expect(ImportFileHelper).to receive(:create_new_user)
            expect { AssignmentParticipant.import(row, nil, {}, 1) }.to raise_error('The assignment with id "1" was not found.')
          end
        end

            allow(AssignmentParticipant).to receive(:exists?).with(user_id: 1, parent_id: 1).and_return(false)
            allow(AssignmentParticipant).to receive(:create).with(user_id: 1, parent_id: 1).and_return(participant)
            allow(participant).to receive(:set_handle).and_return('handle')
            expect(ImportFileHelper).to receive(:create_new_user)
            expect(AssignmentParticipant.import(row, nil, {}, 1)).to be_truthy
          end
        end
      end

Task 2. Write a test case to import topics with special cases.

  • Thoroughly test the #import method in the sign_up_topic model. This includes the following test cases:
  1. The record is empty
  2. The record is not empty and the topic is not existing, including special cases
  3. The record is not empty and the topic is existing, including special cases
#spec/models/sign_up_topic_spec.rb

describe SignUpTopic do
  let(:topic) { build(:topic) }
  describe '.import' do
    context 'when record is empty' do
      it 'raises an ArgumentError' do
        expect { SignUpTopic.import({}, nil, nil) }.to raise_error(ArgumentError, 'The CSV File expects the format: Topic identifier, Topic name, Max choosers, Topic Category (optional), Topic Description (Optional), Topic Link (optional).')
      end
    end

context 'when record is not empty' do
      let(:row) do
        {topic_identifier: 'identifier', topic_name: 'name', max_choosers: 'chooser', category: 'category', description: 'description', link: 'link'}
      end
      let(:session) do
        {assignment_id: 1}
      end
      let(:attributes) do
        {topic_identifier: 'identifier', topic_name: 'name', max_choosers: 'chooser', category: 'category', description: 'description', link: 'link'}
      end

context 'when the topic is not found' do
        it 'creates a new sign up topic' do
          allow(SignUpTopic).to receive_message_chain(:where, :first).with(topic_name: row[:topic_name], assignment_id: session[:assignment_id]).with(no_args).and_return(nil)
          allow(ImportTopicsHelper).to receive(:define_attributes).with(row).and_return(attributes)
          allow(ImportTopicsHelper).to receive(:create_new_sign_up_topic).with(attributes, session).and_return(true)
          expect(ImportTopicsHelper).to receive(:create_new_sign_up_topic).with(attributes, session)
          SignUpTopic.import(row, session, nil)
        end
      end

 context 'when the topic is found' do
        it 'changes the max_chooser and topic_identifier of the existing topic' do
          allow(SignUpTopic).to receive_message_chain(:where, :first).with(topic_name: row[:topic_name], assignment_id: session[:assignment_id]).with(no_args).and_return(topic)
          allow(topic).to receive(:save).with(no_args).and_return(true)
          expect(topic).to receive(:save).with(no_args)
          SignUpTopic.import(row, session, nil)
        end
      end
    end
  end

Task 3. Write a test case to handle import conflicts. ( Issue #329 and Issue #328)

  • Thoroughly test the #import method in the team model. This includes the following test cases:
  1. Duplicates exist. Ignore the new team.
  2. Duplicates exist. Replace the existing team with the new team.
  3. Duplicates exist. Insert any new members to the existing team.
  4. Duplicates exist. Rename the new team and import.
  5. Duplicates exist. Rename the existing team and import.

describe '.import' do
      context 'when row is empty and has_column_names option is not true' do
        it 'raises an ArgumentError' do
          expect { Team.import({}, 1, {has_column_names: 'false'}, AssignmentTeam.new) }
              .to raise_error(ArgumentError, 'Not enough fields on this line.')
        end
      end

    context 'when there are duplicates in new teams with existing teams' do
      let(:row) do
        {teammembers: %w(member1 member2), teamname: 'name'}
      end
      let(:options) do
        {has_teamname: 'true_first'}
      end
      let(:id) {1}
      before(:each) do
        allow(Team).to receive_message_chain(:where, :first).with(["name =? && parent_id =?", row[:teamname], id]).with(no_args).and_return(team)
        allow(AssignmentTeam).to receive(:create_team_and_node).with(id).and_return(team)
        allow(team).to receive(:save).and_return(true)
        allow(team).to receive(:import_team_members)
      end
      context 'when choosing to ignore the new team' do
        it 'handles duplicates with "ignore" argument' do
          options[:handle_dups] = "ignore"
          allow(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "ignore", AssignmentTeam).and_return(nil)
          expect(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "ignore", AssignmentTeam).and_return(nil)
          Team.import(row, id, options, AssignmentTeam)
        end
      end

      context 'when choosing to replace the existing team with the new team' do
        it 'handles duplicates with "replace" argument' do
          options[:handle_dups] = "replace"
          allow(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "replace", AssignmentTeam).and_return(row[:teamname])
          expect(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "replace", AssignmentTeam).and_return(row[:teamname])
          Team.import(row, id, options, AssignmentTeam)
        end
      end

      context 'when choosing to insert any new members to existing team' do
        it 'handles duplicates with "insert" argument' do
          options[:handle_dups] = "insert"
          allow(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "insert", AssignmentTeam).and_return(nil)
          expect(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "insert", AssignmentTeam).and_return(nil)
          Team.import(row, id, options, AssignmentTeam)
        end
      end

      context 'when choosing to rename the new team and import' do
        it 'handles duplicates with "rename" argument' do
          options[:handle_dups] = "rename"
          allow(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "rename", AssignmentTeam).and_return(row[:teamname])
          expect(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "rename", AssignmentTeam).and_return(row[:teamname])
          Team.import(row, id, options, AssignmentTeam)
        end
      end

      context 'when choosing to rename the existing team and import' do
        it 'handles duplicates with "rename_existing" argument' do
          options[:handle_dups] = "rename_existing"
          allow(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "rename_existing", AssignmentTeam).and_return(row[:teamname])
          expect(Team).to receive(:handle_duplicate).with(team, row[:teamname], id, "rename_existing", AssignmentTeam).and_return(row[:teamname])
          Team.import(row, id, options, AssignmentTeam)
        end
      end
    end
describe '.handle_duplicate' do
    context 'when parameterized team is nil' do
      it 'returns team name' do
        expect(Team.handle_duplicate(nil, 'no name', 1, 'replace', CourseTeam.new)).to eq('no name')
      end
    end

    context 'when parameterized team is not nil' do
      context 'when handle_dups option is ignore' do
        it 'does not create the new team and returns nil' do
          expect(Team.handle_duplicate(team, 'no name', 1, 'ignore', CourseTeam.new)).to be nil
        end
      end

      context 'when handle_dups option is rename' do
        it 'returns new team name' do
          allow(Course).to receive(:find).with(1).and_return(double('Course', name: 'no course'))
          allow(Assignment).to receive(:find).with(1).and_return(double('Assignment', name: 'no assignment'))
          allow(Team).to receive(:generate_team_name).with('no course').and_return('new team name')
          allow(Team).to receive(:generate_team_name).with('no assignment').and_return('new team name')
          expect(Team.handle_duplicate(team, 'no name', 1, 'rename', CourseTeam.new)).to eq('new team name')
          expect(Team.handle_duplicate(team, 'no name', 1, 'rename', AssignmentTeam.new)).to eq('new team name')
        end
      end

      context 'when handle_dups option is replace' do
        it 'deletes the old team' do
          allow(team).to receive(:delete)
          expect(Team.handle_duplicate(team, 'no name', 1, 'replace', CourseTeam.new)).to eq('no name')
        end
      end

      context 'when handle_dups option is insert' do
        it 'does nothing and returns nil' do
          expect(Team.handle_duplicate(team, 'no name', 1, 'insert', CourseTeam.new)).to be nil
        end
      end

      # By the time this test is added (by E1949), the renaming existing team function does not exist yet,
      # so it should fail unless the function is implemented and the existing team is renamed and saved.
      context 'when handle_dups option is rename_existing' do
        it 'renames the existing team and returns nil' do
          allow(Course).to receive(:find).with(1).and_return(double('Course', name: 'no course'))
          allow(Assignment).to receive(:find).with(1).and_return(double('Assignment', name: 'no assignment'))
          allow(Team).to receive(:generate_team_name).with('no course').and_return('new team name')
          allow(Team).to receive(:generate_team_name).with('no assignment').and_return('new team name')
          allow(team).to receive(:name=).with('new team name')
          allow(team).to receive(:save)
          expect(team).to receive(:name=).with('new team name').exactly(2).times
          expect(team).to receive(:save).exactly(2).times
          expect(Team.handle_duplicate(team, 'no name', 1, 'replace_existing', CourseTeam.new)).to be nil
          expect(Team.handle_duplicate(team, 'no name', 1, 'replace_existing', AssignmentTeam.new)).to be nil
        end
      end
    end
  end

Testing Video

  • To view the testing vedio, please click the link upon.
  • This video shows the complete test process of the CSC/ECE 517-E1949 task 1, 2 and 3 using RubyMine, all tasks have been tested successfully. The modification of testing code and testing result of each task can also be found in the Test Plan section and Result section above respectively.
  • All tests passed successfully except for the part 2 of task 3. The fail of part 2 is as expected and the reason part 2 of task 3 failed is explained in Result part above. More details please see Result in Part 2: Reason for building failing in the Result Section.

Result

Task 1 Result

  • All test passed.

Task 2 Result

  • All test passed.

Task 3 Result

  • Part1: All test passed.

  • Part2: Reason for build failing:

One of our tasks is to add a test for a function that does not exist yet, namely to rename the existing team when there is import conflict. Therefore, as per test-driven development practice, the test will always fail until the function is correctly implemented.

References

  1. Expertiza homapage
  2. Expertiza on Github
  3. GitHub Project Repository Fork
  4. Testing Video
  5. Rspec