Commit 25215446 authored by 0815-xyz's avatar 0815-xyz
Browse files

Initial commit

parents
@mod @mod_adaptivequiz
Feature: Adaptive quiz content
In order to take a quiz with the CAT (Computer Adaptive Testing) algorithm
As a student
I need the quiz to adjust the questions sequence with accordance to CAT
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | John | The Teacher | johntheteacher@example.com |
| student1 | Peter | The Student | peterthestudent@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Adaptive Quiz Questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext | answer |
| Adaptive Quiz Questions | truefalse | Q1 | Question 1 (difficulty 1). | True |
| Adaptive Quiz Questions | truefalse | Q2 | Question 2 (difficulty 1). | True |
| Adaptive Quiz Questions | truefalse | Q3 | Question 3 (difficulty 2). | True |
| Adaptive Quiz Questions | truefalse | Q4 | Question 4 (difficulty 2). | True |
| Adaptive Quiz Questions | truefalse | Q5 | Question 5 (difficulty 3). | True |
| Adaptive Quiz Questions | truefalse | Q6 | Question 6 (difficulty 3). | True |
| Adaptive Quiz Questions | truefalse | Q7 | Question 7 (difficulty 4). | True |
| Adaptive Quiz Questions | truefalse | Q8 | Question 8 (difficulty 4). | True |
| Adaptive Quiz Questions | truefalse | Q9 | Question 9 (difficulty 5). | True |
| Adaptive Quiz Questions | truefalse | Q10 | Question 10 (difficulty 5). | True |
And the following "core_question > Tags" exist:
| question | tag |
| Q1 | adpq_1 |
| Q2 | adpq_1 |
| Q3 | adpq_2 |
| Q4 | adpq_2 |
| Q5 | adpq_3 |
| Q6 | adpq_3 |
| Q7 | adpq_4 |
| Q8 | adpq_4 |
| Q9 | adpq_5 |
| Q10 | adpq_5 |
@javascript
Scenario: 20% standard error, user performs 1 level above the starting level
Given the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 2 |
| lowestlevel | 1 |
| highestlevel | 5 |
| minimumquestions | 1 |
| maximumquestions | 10 |
| standarderror | 20 |
| questionpoolnamed | Adaptive Quiz Questions |
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
Then I should see " (difficulty 2)."
And I click on "True" "radio"
And I press "Submit answer"
And I should see " (difficulty 4)."
And I click on "False" "radio"
And I press "Submit answer"
And I should see " (difficulty 3)."
And I click on "True" "radio"
And I press "Submit answer"
And I should see " (difficulty 4)."
And I click on "False" "radio"
And I press "Submit answer"
And I should see " (difficulty 3)."
And I click on "False" "radio"
And I press "Submit answer"
And I should see " (difficulty 2)."
And I click on "True" "radio"
And I press "Submit answer"
And "Continue" "button" should be visible
@javascript
Scenario: 20% standard error, user performs on the lowest level
Given the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 2 |
| lowestlevel | 1 |
| highestlevel | 5 |
| minimumquestions | 1 |
| maximumquestions | 10 |
| standarderror | 20 |
| questionpoolnamed | Adaptive Quiz Questions |
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
Then I should see " (difficulty 2)."
And I click on "False" "radio"
And I press "Submit answer"
And I should see " (difficulty 1)."
And I click on "False" "radio"
And I press "Submit answer"
And I should see " (difficulty 1)."
And I click on "False" "radio"
And I press "Submit answer"
And I should see " (difficulty 2)."
And I click on "False" "radio"
And I press "Submit answer"
And "Continue" "button" should be visible
@javascript
Scenario: 20% standard error, user performs on the highest level
Given the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 2 |
| lowestlevel | 1 |
| highestlevel | 5 |
| minimumquestions | 1 |
| maximumquestions | 10 |
| standarderror | 20 |
| questionpoolnamed | Adaptive Quiz Questions |
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
Then I should see " (difficulty 2)."
And I click on "True" "radio"
And I press "Submit answer"
And I should see " (difficulty 4)."
And I click on "True" "radio"
And I press "Submit answer"
And I should see " (difficulty 5)."
And I click on "True" "radio"
And I press "Submit answer"
And I should see " (difficulty 5)."
And I click on "True" "radio"
And I press "Submit answer"
And I should see " (difficulty 4)."
And I click on "True" "radio"
And I press "Submit answer"
And "Continue" "button" should be visible
@mod @mod_adaptivequiz
Feature: Add an adaptive quiz
In order to evaluate students using an adaptive questions strategy
As a teacher
I need to add an adaptive quiz activity to a course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | John | The Teacher | johntheteacher@example.com |
| student1 | Peter | The Student | peterthestudent@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Adaptive Quiz Questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Adaptive Quiz Questions | truefalse | TF1 | First question. |
| Adaptive Quiz Questions | truefalse | TF2 | Second question. |
| Adaptive Quiz Questions | truefalse | TF3 | Third question. |
| Adaptive Quiz Questions | truefalse | TF4 | Fourth question. |
@javascript
Scenario: Add an adaptive quiz to a course to be visible to a student
When the following "core_question > Tags" exist:
| question | tag |
| TF1 | adpq_1 |
And I log in as "teacher1"
And I add a "adaptivequiz" activity to course "Course 1" section "1" and I fill the form with:
| Name | Adaptive Quiz |
| Description | Adaptive quiz description. |
| Question pool | Adaptive Quiz Questions (4) |
| Starting level of difficulty | 1 |
| Lowest level of difficulty | 1 |
| Highest level of difficulty | 2 |
| Minimum number of questions | 1 |
| Maximum number of questions | 2 |
| Standard Error to stop | 25 |
| ID number | adaptivequiz1 |
And I log out
And I am on the "adaptivequiz1" "Activity" page logged in as "student1"
Then "Start attempt" "link" should exist
@javascript
Scenario: It is impossible to create an adaptive quiz without a properly tagged question for the starting level of difficulty
When the following "core_question > Tags" exist:
| question | tag |
| TF1 | adpq_001 |
| TF2 | adpq_2 |
| TF3 | truefalse_1 |
| TF3 | TF |
And I log in as "teacher1"
And I add a "adaptivequiz" activity to course "Course 1" section "1" and I fill the form with:
| Name | Adaptive Quiz |
| Description | Adaptive quiz description. |
| Question pool | Adaptive Quiz Questions (4) |
| Starting level of difficulty | 1 |
| Lowest level of difficulty | 1 |
| Highest level of difficulty | 2 |
| Minimum number of questions | 1 |
| Maximum number of questions | 2 |
| Standard Error to stop | 25 |
Then I should see "The selected questions categories do not contain questions which are properly tagged to match the selected starting level of difficulty."
@mod @mod_adaptivequiz
Feature: Attempt an adaptive quiz
In order to demonstrate what I know using the adaptive quiz strategy
As a student
I need to be able to attempt an adaptive quiz
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | John | The Teacher | johntheteacher@example.com |
| student1 | Peter | The Student | peterthestudent@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Adaptive Quiz Questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Adaptive Quiz Questions | truefalse | Q1 | First question |
| Adaptive Quiz Questions | truefalse | Q2 | Second question |
And the following "core_question > Tags" exist:
| question | tag |
| Q1 | adpq_1 |
| Q2 | adpq_2 |
And the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 1 |
| lowestlevel | 1 |
| highestlevel | 2 |
| minimumquestions | 1 |
| maximumquestions | 2 |
| standarderror | 25 |
| questionpoolnamed | Adaptive Quiz Questions |
| attempts | 1 |
@javascript
Scenario: Attempt an adaptive quiz
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
Then I should see "First question"
@javascript
Scenario: A student cannot attempt an adaptive quiz if no more attempts are allowed
Given I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
When I am on the "adaptivequiz1" "Activity" page
Then "Start attempt" "link" should not be visible
And I should see "No more attempts allowed at this activity"
@javascript
Scenario: Return to a started attempt
Given the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Adaptive Quiz Questions | truefalse | Q3 | Third question |
| Adaptive Quiz Questions | truefalse | Q4 | Fourth question |
And the following "core_question > Tags" exist:
| question | tag |
| Q3 | adpq_2 |
| Q4 | adpq_3 |
And I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I click on "Settings" "link"
And I set the following fields to these values:
| Highest level of difficulty | 3 |
| Minimum number of questions | 1 |
| Maximum number of questions | 3 |
And I click on "Save and return to course" "button"
And I log out
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio"
And I press "Submit answer"
And I am on the "adaptivequiz1" "Activity" page
And I click on "Start attempt" "link"
And I click on "True" "radio"
And I press "Submit answer"
Then I should see "Fourth question"
@mod @mod_adaptivequiz
Feature: Delete an attempt on adaptive quiz
In order to keep the results of adaptive quiz relevant
As a teacher
I need to be able to delete students' attempts
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | John | The Teacher | johntheteacher@example.com |
| student1 | Peter | The Student | peterthestudent@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Adaptive Quiz Questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext |
| Adaptive Quiz Questions | truefalse | TF1 | First question |
| Adaptive Quiz Questions | truefalse | TF2 | Second question |
And the following "core_question > Tags" exist:
| question | tag |
| TF1 | adpq_2 |
| TF2 | adpq_3 |
And the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 2 |
| lowestlevel | 1 |
| highestlevel | 10 |
| minimumquestions | 2 |
| maximumquestions | 20 |
| standarderror | 5 |
| questionpoolnamed | Adaptive Quiz Questions |
And I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
And I log out
@javascript
Scenario: Delete an individual attempt
When I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I click on "1" "link" in the "Peter The Student" "table_row"
And I click on "Delete attempt" "link" in the "Completed" "table_row"
And I press "Continue"
And I should see "Nothing to display"
@mod @mod_adaptivequiz
Feature: Attempt feedback
In order to get inspired and engaged
As a student
I want to get feedback on my attempts on adaptive quiz and ability estimation if I'm allowed to
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | John | The Teacher | johntheteacher@example.com |
| student1 | Peter | The Student | peterthestudent@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Adaptive Quiz Questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext | answer |
| Adaptive Quiz Questions | truefalse | Q1 | First question | True |
| Adaptive Quiz Questions | truefalse | Q2 | Second question | True |
And the following "core_question > Tags" exist:
| question | tag |
| Q1 | adpq_1 |
| Q2 | adpq_2 |
And the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 1 |
| lowestlevel | 1 |
| highestlevel | 2 |
| minimumquestions | 1 |
| maximumquestions | 2 |
| standarderror | 20 |
| questionpoolnamed | Adaptive Quiz Questions |
| showabilitymeasure | 1 |
@javascript
Scenario: Get default textual feedback after an attempt is finished
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
Then I should see "You've finished the attempt, thank you for taking the quiz!"
@javascript
Scenario: Get customized textual feedback after an attempt is finished
Given I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Attempt feedback | Thank you for taking the test! |
And I click on "Save and return to course" "button"
And I log out
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
Then I should see "Thank you for taking the test!"
@javascript
Scenario: Get estimated ability after an attempt is finished
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
Then I should see "Estimated ability: 1.7 / 1 - 2"
@javascript
Scenario: View attempt summary with estimated ability for the only allowed attempt
Given I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Attempts allowed | 1 |
And I click on "Save and return to course" "button"
And I log out
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
Then I should see "Attempt Summary"
And "attemptsummarytable" "table" should exist
And I should see "Completed" in the "#attemptstatecell" "css_element"
And I should see "1.7 / 1 - 2" in the "#abilitymeasurecell" "css_element"
@javascript
Scenario: View attempts summary with estimated ability for several attempts
Given I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
When I am on the "adaptivequiz1" "Activity" page
Then I should see "Your previous attempts"
And "userattemptstable" "table" should exist
And I should see "Estimated ability / 1 - 2" in the "th.abilitymeasurecol" "css_element"
And I should see "Completed" in the "#userattemptstable_r0 td.statecol" "css_element"
And I should see "Completed" in the "#userattemptstable_r1 td.statecol" "css_element"
And I should see "1.7" in the "#userattemptstable_r0 td.abilitymeasurecol" "css_element"
And I should see "1.7" in the "#userattemptstable_r1 td.abilitymeasurecol" "css_element"
@javascript
Scenario: Estimated ability after an attempt is finished is not visible when set accordingly
Given I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Show ability measure to students | No |
And I click on "Save and return to course" "button"
And I log out
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
Then I should not see "Estimated ability: 1.7 / 1 - 2"
@javascript
Scenario: Estimated ability for the only allowed attempt is not visible for a student when set accordingly
Given I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Attempts allowed | 1 |
| Show ability measure to students | No |
And I click on "Save and return to course" "button"
And I log out
And I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
When I am on the "adaptivequiz1" "Activity" page
Then "#abilitymeasurecell" "css_element" should not exist
@javascript
Scenario: Estimated ability is not visible for a student in attempts summary when set accordingly
Given I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I navigate to "Settings" in current page administration
And I set the following fields to these values:
| Show ability measure to students | No |
And I click on "Save and return to course" "button"
And I log out
And I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
When I am on the "adaptivequiz1" "Activity" page
Then ".abilitymeasurecol" "css_element" should not exist
@mod @mod_adaptivequiz
Feature: Set activity as completed when at least one attempt is completed
In order to control whether the activity has been complete by students
As a teacher
I need the activity to be marked as completed for a student when they have made at least one attempt on the adaptive quiz
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | John | The Teacher | johntheteacher@example.com |
| student1 | Peter | The Student | peterthestudent@example.com |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Adaptive Quiz Questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext | answer |
| Adaptive Quiz Questions | truefalse | TF1 | First question | True |
| Adaptive Quiz Questions | truefalse | TF2 | Second question | True |
And the following "core_question > Tags" exist:
| question | tag |
| TF1 | adpq_2 |
| TF2 | adpq_3 |
And the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 2 |
| lowestlevel | 1 |
| highestlevel | 10 |
| minimumquestions | 2 |
| maximumquestions | 20 |
| standarderror | 5 |
| questionpoolnamed | Adaptive Quiz Questions |
@javascript
Scenario: Teacher sets the completion rule and student completes an attempt in Moodle version 4.2 or lower
Given the site is running Moodle version 4.2.7 or lower
And I am on the "Adaptive Quiz" "adaptivequiz activity editing" page logged in as teacher1
And I expand all fieldsets
And I set the following fields to these values:
| Completion tracking | Show activity as complete when conditions are met |
And I click on "completionattemptcompleted" "checkbox"
And I click on "Save and return to course" "button"
And I log out
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
And I log out
And I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
Then "Adaptive Quiz" should have the "Complete an attempt" completion condition
And I am on "Course 1" course homepage
And I navigate to "Reports" in current page administration
And I click on "Activity completion" "link"
And "Completed" "icon" should exist in the "Peter The Student" "table_row"
@javascript
Scenario: Teacher sets the completion rule and student completes an attempt in Moodle version 4.3 or higher
Given the site is running Moodle version 4.3 or higher
And I am on the "Adaptive Quiz" "adaptivequiz activity editing" page logged in as teacher1
And I expand all fieldsets
And I set the following fields to these values:
| Add requirements | 1 |
| completionattemptcompleted | 1 |
And I click on "Save and return to course" "button"
And I log out
When I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
And I log out
And I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
Then "Adaptive Quiz" should have the "Complete an attempt" completion condition
And I am on "Course 1" course homepage
And I navigate to "Reports" in current page administration
And I click on "Activity completion" "link"
And "Completed" "icon" should exist in the "Peter The Student" "table_row"
@mod @mod_adaptivequiz
Feature: View analysis of questions usage in adaptive quizzes
In order to assess how items from the question bank are administered and answered in adaptive quizzes
As a teacher
I need a report with analysis of questions usage
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | John | The Teacher | johntheteacher@example.com |
| student1 | Peter | The Student | peterthestudent@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Adaptive Quiz Questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext | answer |
| Adaptive Quiz Questions | truefalse | TF1 | First question | True |
| Adaptive Quiz Questions | truefalse | TF2 | Second question | True |
And the following "core_question > Tags" exist:
| question | tag |
| TF1 | adpq_2 |
| TF2 | adpq_3 |
And the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 2 |
| lowestlevel | 1 |
| highestlevel | 10 |
| minimumquestions | 2 |
| maximumquestions | 20 |
| standarderror | 5 |
| questionpoolnamed | Adaptive Quiz Questions |
And I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
And I log out
@javascript
Scenario: Navigate to reviewing of an attempt listed in single question analysis
When I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I click on "Question Analysis" "link"
And I click on "TF1" "link"
And I click on "Review attempt" "link" in the "Peter The Student" "table_row"
Then I should see "Adaptive Quiz - reviewing attempt by Peter The Student"
@mod @mod_adaptivequiz
Feature: View students results in adaptive quiz
In order to control what results students have on attempting adaptive quizzes
As a teacher
I need an access to attempts reporting
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | John | The Teacher | johntheteacher@example.com |
| student1 | Peter | The Student | peterthestudent@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Adaptive Quiz Questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext | answer |
| Adaptive Quiz Questions | truefalse | TF1 | First question | True |
| Adaptive Quiz Questions | truefalse | TF2 | Second question | True |
And the following "core_question > Tags" exist:
| question | tag |
| TF1 | adpq_2 |
| TF2 | adpq_3 |
And the following "activity" exists:
| activity | adaptivequiz |
| idnumber | adaptivequiz1 |
| course | C1 |
| name | Adaptive Quiz |
| startinglevel | 2 |
| lowestlevel | 1 |
| highestlevel | 10 |
| minimumquestions | 2 |
| maximumquestions | 20 |
| standarderror | 5 |
| questionpoolnamed | Adaptive Quiz Questions |
And I am on the "adaptivequiz1" "Activity" page logged in as "student1"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
And I log out
@javascript
Scenario: Attempts report
When I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
Then I should see "Attempts report"
And "Peter The Student" "table_row" should exist
And "Peter The Student" row "Number of attempts" column of "usersattemptstable" table should contain "1"
@javascript
Scenario: Deleted user should not appear in the attempts report
Given the following "user" exists:
| username | student2 |
| firstname | Henry |
| lastname | The Student |
| email | henrythestudent@example.com |
And the following "course enrolment" exists:
| user | student2 |
| course | C1 |
| role | student |
And I am on the "adaptivequiz1" "Activity" page logged in as "student2"
And I click on "Start attempt" "link"
And I click on "True" "radio" in the "First question" "question"
And I press "Submit answer"
And I click on "True" "radio" in the "Second question" "question"
And I press "Submit answer"
And I press "Continue"
And I log out
And I log in as "admin"
And I navigate to "Users > Accounts > Bulk user actions" in site administration
And I set the field "Available" to "Henry The Student"
And I press "Add to selection"
And I set the field "id_action" to "Delete"
And I press "Go"
And I press "Yes"
And I log out
When I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
Then "Henry The Student" "table_row" should not exist
@javascript
Scenario: Individual user attempts report
When I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I click on "1" "link" in the "Peter The Student" "table_row"
Then I should see "Adaptive Quiz - individual user attempts report for Peter The Student"
And "Completed" "table_row" should exist
And "Completed" row "Reason for stopping attempt" column of "individualuserattemptstable" table should contain "Unable to fetch a question for level 5"
And "Completed" row "Sum of questions attempted" column of "individualuserattemptstable" table should contain "2"
@javascript
Scenario: View attempt summary
When I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I click on "1" "link" in the "Peter The Student" "table_row"
And I click on "Review attempt" "link" in the "Completed" "table_row"
Then I should see "Peter The Student (peterthestudent@example.com)" in the "User" "table_row"
@javascript
Scenario: View attempt questions details
When I am on the "adaptivequiz1" "Activity" page logged in as "teacher1"
And I click on "1" "link" in the "Peter The Student" "table_row"
And I click on "Review attempt" "link" in the "Completed" "table_row"
And I click on "Questions Details" "link"
# Info on the first question
Then I should see "Correct" in the "[id^=question-][id$=-1] .info .state" "css_element"
# Info on the second question
And I should see "Correct" in the "[id^=question-][id$=-2] .info .state" "css_element"
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\completion;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/adaptivequiz/locallib.php');
use advanced_testcase;
use cm_info;
use mod_adaptivequiz\completion\custom_completion;
/**
* @covers \mod_adaptivequiz\completion\custom_completion
*/
class custom_completion_test extends advanced_testcase {
public function test_it_defines_completion_state_based_on_attempt_completion():void {
global $DB;
$this->resetAfterTest();
$this->setup_test_data_xml();
$attemptuniqueid = 330;
$adaptivequizid = 330;
$cmid = 5;
$userid = 2;
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $adaptivequizid]);
$context = \context_module::instance($cmid);
$cm = get_coursemodule_from_id('adaptivequiz', $cmid);
$cminfo = cm_info::create($cm);
$cminfo->override_customdata('customcompletionrules',
['completionattemptcompleted' => $adaptivequiz->completionattemptcompleted]);
$completion = new custom_completion($cminfo, $userid);
$this->assertEquals(COMPLETION_INCOMPLETE, $completion->get_state('completionattemptcompleted'));
adaptivequiz_complete_attempt($attemptuniqueid, $adaptivequiz, $context, $userid, '1', 'php unit test');
$this->assertEquals(COMPLETION_COMPLETE, $completion->get_state('completionattemptcompleted'));
}
private function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/../fixtures/mod_adaptivequiz_adaptiveattempt.xml']
)->to_database();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!-- Tests no logs - Only query 3 should be processed -->
<dataset>
<table name="adaptivequiz_question">
<column>id</column>
<column>instance</column>
<column>questioncategory</column>
<row>
<value>1</value>
<value>12</value>
<value>2</value>
</row>
<row>
<value>2</value>
<value>12</value>
<value>4</value>
</row>
<row>
<value>3</value>
<value>12</value>
<value>6</value>
</row>
<row>
<value>4</value>
<value>12</value>
<value>8</value>
</row>
<row>
<value>5</value>
<value>12</value>
<value>10</value>
</row>
<row>
<value>6</value>
<value>12</value>
<value>12</value>
</row>
<row>
<value>7</value>
<value>13</value>
<value>1</value>
</row>
</table>
<table name="modules">
<column>id</column>
<column>name</column>
<row>
<value>50</value>
<value>adaptivequiz</value>
</row>
</table>
<table name="course">
<column>id</column>
<column>category</column>
<column>fullname</column>
<column>shortname</column>
<row>
<value>2</value>
<value>1</value>
<value>101</value>
<value>101</value>
</row>
</table>
<table name="course_sections">
<column>id</column>
<column>course</column>
<column>section</column>
<column>sequence</column>
<row>
<value>10</value>
<value>2</value>
<value>1</value>
<value>5,6</value>
</row>
</table>
<table name="course_modules">
<column>id</column>
<column>course</column>
<column>module</column>
<column>instance</column>
<column>section</column>
<row>
<value>5</value>
<value>2</value>
<value>50</value>
<value>13</value>
<value>10</value>
</row>
<row>
<value>6</value>
<value>2</value>
<value>50</value>
<value>1</value>
<value>10</value>
</row>
</table>
<table name="adaptivequiz">
<column>id</column>
<column>course</column>
<column>name</column>
<column>intro</column>
<column>attemptfeedback</column>
<column>highestlevel</column>
<column>lowestlevel</column>
<column>minimumquestions</column>
<column>maximumquestions</column>
<column>standarderror</column>
<column>startinglevel</column>
<row>
<value>1</value>
<value>2</value>
<value>Test re-using quba</value>
<value>intro asdf</value>
<value>attempt feedback</value>
<value>100</value>
<value>1</value>
<value>15</value>
<value>20</value>
<value>1.0</value>
<value>50</value>
</row>
<row>
<value>13</value>
<value>2</value>
<value>Test min_attempts_reached</value>
<value>intro asdf</value>
<value>attempt feedback</value>
<value>100</value>
<value>1</value>
<value>15</value>
<value>20</value>
<value>1.0</value>
<value>50</value>
</row>
<row>
<value>330</value>
<value>2</value>
<value>Test re-using quba</value>
<value>intro asdf</value>
<value>attempt feedback</value>
<value>100</value>
<value>1</value>
<value>15</value>
<value>20</value>
<value>1.0</value>
<value>50</value>
</row>
</table>
<table name="adaptivequiz_attempt">
<column>id</column>
<column>instance</column>
<column>userid</column>
<column>uniqueid</column>
<column>attemptstate</column>
<column>questionsattempted</column>
<column>standarderror</column>
<column>difficultysum</column>
<row>
<value>1</value>
<value>1</value>
<value>2</value>
<value>3</value>
<value>inprogress</value>
<value>0</value>
<value>1</value>
<value>1</value>
</row>
<row>
<value>2</value>
<value>13</value>
<value>3</value>
<value>3</value>
<value>complete</value>
<value>0</value>
<value>1</value>
<value>1</value>
</row>
<row>
<value>3</value>
<value>13</value>
<value>4</value>
<value>4</value>
<value>complete</value>
<value>16</value>
<value>1</value>
<value>1</value>
</row>
<row>
<value>5</value>
<value>330</value>
<value>2</value>
<value>330</value>
<value>inprogress</value>
<value>0</value>
<value>1</value>
<value>1</value>
</row>
</table>
<table name="question_usages">
<column>id</column>
<column>contextid</column>
<column>component</column>
<column>preferredbehaviour</column>
<row>
<value>330</value>
<value>330</value>
<value>mod_adaptivequiz</value>
<value>immediatefeedback</value>
</row>
</table>
<table name="context">
<column>id</column>
<column>contextlevel</column>
<column>instanceid</column>
<column>path</column>
<column>depth</column>
<row>
<value>16</value>
<value>50</value>
<value>2</value>
<value>/1/3/16</value>
<value>3</value>
</row>
<row>
<value>330</value>
<value>70</value>
<value>5</value>
<value>/1/3/16/330</value>
<value>4</value>
</row>
</table>
<table name="question_attempts">
<column>id</column>
<column>questionusageid</column>
<column>slot</column>
<column>questionid</column>
<column>maxmark</column>
<column>minfraction</column>
<column>rightanswer</column>
<column>timemodified</column>
<row>
<value>1</value>
<value>330</value>
<value>1</value>
<value>1</value>
<value>1.0</value>
<value>0.0</value>
<value>true</value>
<value>0</value>
</row>
<row>
<value>2</value>
<value>330</value>
<value>2</value>
<value>2</value>
<value>1.0</value>
<value>0.0</value>
<value>answer 1 (correct)</value>
<value>0</value>
</row>
</table>
<table name="question_attempt_steps">
<column>id</column>
<column>questionattemptid</column>
<column>sequencenumber</column>
<column>state</column>
<column>timecreated</column>
<column>userid</column>
<row>
<value>1</value>
<value>1</value>
<value>0</value>
<value>todo</value>
<value>0</value>
<value>2</value>
</row>
<row>
<value>2</value>
<value>1</value>
<value>1</value>
<value>gradedwrong</value>
<value>1</value>
<value>2</value>
</row>
<row>
<value>3</value>
<value>2</value>
<value>0</value>
<value>complete</value>
<value>1</value>
<value>2</value>
</row>
</table>
</dataset>
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<table name="context">
<column>id</column>
<column>contextlevel</column>
<column>instanceid</column>
<column>path</column>
<column>depth</column>
<row>
<value>16</value>
<value>50</value>
<value>200</value>
<value>/1/3/16</value>
<value>3</value>
</row>
<row>
<value>330</value>
<value>70</value>
<value>5</value>
<value>/1/3/16/330</value>
<value>4</value>
</row>
</table>
<table name="modules">
<column>id</column>
<column>name</column>
<row>
<value>50</value>
<value>adaptivequiz</value>
</row>
</table>
<table name="course">
<column>id</column>
<column>category</column>
<column>fullname</column>
<column>shortname</column>
<row>
<value>200</value>
<value>1</value>
<value>101</value>
<value>101</value>
</row>
</table>
<table name="course_sections">
<column>id</column>
<column>course</column>
<column>section</column>
<column>sequence</column>
<row>
<value>10</value>
<value>200</value>
<value>1</value>
<value>5</value>
</row>
</table>
<table name="course_modules">
<column>id</column>
<column>course</column>
<column>module</column>
<column>instance</column>
<column>section</column>
<column>completion</column>
<row>
<value>5</value>
<value>200</value>
<value>50</value>
<value>330</value>
<value>10</value>
<value>2</value>
</row>
</table>
<table name="question_categories">
<column>id</column>
<column>name</column>
<column>contextid</column>
<column>info</column>
<row>
<value>1</value>
<value>Default for 101</value>
<value>16</value>
<value>default question category</value>
</row>
<row>
<value>2</value>
<value>Default for Miscellaneous</value>
<value>3</value>
<value>default question category</value>
</row>
<row>
<value>3</value>
<value>Default for System</value>
<value>1</value>
<value>default question category</value>
</row>
</table>
<table name="question">
<column>id</column>
<column>category</column>
<column>parent</column>
<column>name</column>
<column>questiontext</column>
<column>generalfeedback</column>
<column>defaultmark</column>
<column>qtype</column>
<row>
<value>1</value>
<value>1</value>
<value>0</value>
<value>true or false 1</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>2</value>
<value>1</value>
<value>0</value>
<value>multiple choice 1</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>multichoice</value>
</row>
</table>
<table name="qtype_multichoice_options">
<column>id</column>
<column>questionid</column>
<column>layout</column>
<column>single</column>
<column>shuffleanswers</column>
<column>correctfeedback</column>
<column>correctfeedbackformat</column>
<column>partiallycorrectfeedback</column>
<column>partiallycorrectfeedbackformat</column>
<column>incorrectfeedback</column>
<column>incorrectfeedbackformat</column>
<column>answernumbering</column>
<column>shownumcorrect</column>
<column>showstandardinstruction</column>
<row>
<value>1</value>
<value>2</value>
<value>0</value>
<value>1</value>
<value>1</value>
<value>Your answer is correct.</value>
<value>1</value>
<value>Your answer is partially correct.</value>
<value>1</value>
<value>Your answer is incorrect.</value>
<value>1</value>
<value>abc</value>
<value>1</value>
<value>0</value>
</row>
</table>>
<table name="question_answers">
<column>id</column>
<column>question</column>
<column>answer</column>
<column>fraction</column>
<column>feedback</column>
<row>
<value>1</value>
<value>1</value>
<value>true</value>
<value>1.0</value>
<value>feedback for true</value>
</row>
<row>
<value>2</value>
<value>1</value>
<value>false</value>
<value>0.0</value>
<value>feedback for false</value>
</row>
<row>
<value>3</value>
<value>2</value>
<value>answer 1 (correct)</value>
<value>1.0</value>
<value>feedback for answer 1</value>
</row>
<row>
<value>4</value>
<value>2</value>
<value>answer 2 (incorrect)</value>
<value>0.0</value>
<value>feedback for answer 2</value>
</row>
<row>
<value>5</value>
<value>2</value>
<value>answer 3 (incorrect)</value>
<value>0.0</value>
<value>feedback for answer 3</value>
</row>
<row>
<value>6</value>
<value>2</value>
<value>answer 4 (incorrect)</value>
<value>0.0</value>
<value>feedback for answer 4</value>
</row>
<row>
<value>7</value>
<value>2</value>
<value>answer 5 (incorrect)</value>
<value>0.0</value>
<value>feedback for answer %</value>
</row>
</table>
<table name="question_attempts">
<column>id</column>
<column>questionusageid</column>
<column>slot</column>
<column>questionid</column>
<column>maxmark</column>
<column>minfraction</column>
<column>rightanswer</column>
<column>timemodified</column>
<row>
<value>1</value>
<value>330</value>
<value>1</value>
<value>1</value>
<value>1.0</value>
<value>0.0</value>
<value>true</value>
<value>0</value>
</row>
<row>
<value>2</value>
<value>330</value>
<value>2</value>
<value>2</value>
<value>1.0</value>
<value>0.0</value>
<value>answer 1 (correct)</value>
<value>0</value>
</row>
</table>
<table name="question_attempt_steps">
<column>id</column>
<column>questionattemptid</column>
<column>sequencenumber</column>
<column>state</column>
<column>timecreated</column>
<column>userid</column>
<row>
<value>1</value>
<value>1</value>
<value>0</value>
<value>todo</value>
<value>0</value>
<value>2</value>
</row>
<row>
<value>2</value>
<value>1</value>
<value>1</value>
<value>gradedwrong</value>
<value>1</value>
<value>2</value>
</row>
<row>
<value>3</value>
<value>2</value>
<value>0</value>
<value>complete</value>
<value>1</value>
<value>2</value>
</row>
</table>
<table name="question_truefalse">
<column>id</column>
<column>question</column>
<column>trueanswer</column>
<column>falseanswer</column>
<row>
<value>1</value>
<value>1</value>
<value>1</value>
<value>2</value>
</row>
</table>
<table name="tag">
<column>id</column>
<column>userid</column>
<column>tagcollid</column>
<column>name</column>
<column>rawname</column>
<column>tagtype</column>
<column>flag</column>
<row>
<value>1</value>
<value>2</value>
<value>1</value>
<value>adpq_5</value>
<value>adpq_5</value>
<value>default</value>
<value>0</value>
</row>
<row>
<value>2</value>
<value>2</value>
<value>1</value>
<value>phpunittag_5</value>
<value>phpunittag_5</value>
<value>default</value>
<value>0</value>
</row>
<row>
<value>3</value>
<value>2</value>
<value>1</value>
<value>phpunittag_888</value>
<value>phpunittag_888</value>
<value>default</value>
<value>0</value>
</row>
</table>
<table name="tag_instance">
<column>id</column>
<column>tagid</column>
<column>itemtype</column>
<column>itemid</column>
<column>tiuserid</column>
<row>
<value>1</value>
<value>1</value>
<value>question</value>
<value>1</value>
<value>0</value>
</row>
</table>
<table name="adaptivequiz">
<column>id</column>
<column>course</column>
<column>name</column>
<column>intro</column>
<column>attemptfeedback</column>
<column>highestlevel</column>
<column>lowestlevel</column>
<column>minimumquestions</column>
<column>maximumquestions</column>
<column>standarderror</column>
<column>startinglevel</column>
<column>completionattemptcompleted</column>
<row>
<value>330</value>
<value>200</value>
<value>Test re-using quba</value>
<value>intro asdf</value>
<value>attempt feedback</value>
<value>100</value>
<value>1</value>
<value>15</value>
<value>20</value>
<value>1.0</value>
<value>50</value>
<value>1</value>
</row>
</table>
<table name="adaptivequiz_attempt">
<column>id</column>
<column>instance</column>
<column>userid</column>
<column>uniqueid</column>
<column>attemptstate</column>
<column>questionsattempted</column>
<column>standarderror</column>
<row>
<value>1</value>
<value>220</value>
<value>2</value>
<value>1110</value>
<value>inprogress</value>
<value>0</value>
<value>1</value>
</row>
<row>
<value>2</value>
<value>221</value>
<value>2</value>
<value>1111</value>
<value>inprogress</value>
<value>10</value>
<value>1</value>
</row>
<row>
<value>3</value>
<value>222</value>
<value>2</value>
<value>1112</value>
<value>inprogress</value>
<value>20</value>
<value>1</value>
</row>
<row>
<value>4</value>
<value>223</value>
<value>2</value>
<value>1113</value>
<value>inprogress</value>
<value>40</value>
<value>1</value>
</row>
<row>
<value>5</value>
<value>330</value>
<value>2</value>
<value>330</value>
<value>inprogress</value>
<value>0</value>
<value>1</value>
</row>
</table>
<table name="question_usages">
<column>id</column>
<column>contextid</column>
<column>component</column>
<column>preferredbehaviour</column>
<row>
<value>330</value>
<value>330</value>
<value>mod_adaptivequiz</value>
<value>immediatefeedback</value>
</row>
</table>
</dataset>
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<table name="adaptivequiz_attempt">
<column>id</column>
<column>instance</column>
<column>userid</column>
<column>uniqueid</column>
<column>attemptstate</column>
<column>questionsattempted</column>
<column>standarderror</column>
<column>difficultysum</column>
<column>measure</column>
<row>
<value>1</value>
<value>220</value>
<value>2</value>
<value>1110</value>
<value>inprogress</value>
<value>0</value>
<value>1.2</value>
<value>99</value>
<value>2.222</value>
</row>
</table>
<table name="adaptivequiz">
<column>id</column>
<column>course</column>
<column>name</column>
<column>intro</column>
<column>attemptfeedback</column>
<column>highestlevel</column>
<column>lowestlevel</column>
<column>minimumquestions</column>
<column>maximumquestions</column>
<column>standarderror</column>
<column>startinglevel</column>
<row>
<value>220</value>
<value>1</value>
<value>Test for retrieve_standard_error()</value>
<value>intro asdf</value>
<value>attempt feedback</value>
<value>100</value>
<value>1</value>
<value>15</value>
<value>20</value>
<value>9.9</value>
<value>50</value>
</row>
</table>
</dataset>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<dataset>
<table name="context">
<column>id</column>
<column>contextlevel</column>
<column>instanceid</column>
<column>path</column>
<column>depth</column>
<row>
<value>16</value>
<value>50</value>
<value>2</value>
<value>/1/3/16</value>
<value>3</value>
</row>
</table>
<table name="course">
<column>id</column>
<column>category</column>
<column>fullname</column>
<column>shortname</column>
<row>
<value>2</value>
<value>1</value>
<value>101</value>
<value>101</value>
</row>
</table>
<table name="question_categories">
<column>id</column>
<column>name</column>
<column>contextid</column>
<column>info</column>
<column>stamp</column>
<row>
<value>1</value>
<value>Default for 101</value>
<value>16</value>
<value>default question category</value>
<value>example.loc+161208110835+ZNzubk</value>
</row>
<row>
<value>2</value>
<value>Default for Miscellaneous</value>
<value>3</value>
<value>default question category</value>
<value>example.loc+161208112421+aQXIHP</value>
</row>
<row>
<value>3</value>
<value>Default for System</value>
<value>1</value>
<value>default question category</value>
<value>example.loc+161208112421+HX4iPR</value>
</row>
<row>
<value>4</value>
<value>Test reattempt</value>
<value>16</value>
<value>Data test for fetch_question</value>
<value>example.loc+161208112853+RAFrV1</value>
</row>
<row>
<value>5</value>
<value>retrieve_tags_with_question_count</value>
<value>16</value>
<value>test_retrieve_tags_with_question_count</value>
<value>example.loc+161208112930+1oAcF9</value>
</row>
<row>
<value>6</value>
<value>find_questions_with_tags_with_multiple_quest_in_quest_category 1</value>
<value>16</value>
<value>find_questions_with_tags_with_multiple_quest_in_quest_category</value>
<value>example.loc+161208120113+5PvXXH</value>
</row>
<row>
<value>7</value>
<value>find_questions_with_tags_with_multiple_quest_in_quest_category 2</value>
<value>16</value>
<value>find_questions_with_tags_with_multiple_quest_in_quest_category</value>
<value>example.loc+170110181010+Ig7DE6</value>
</row>
<row>
<value>8</value>
<value>find_questions_with_tags_with_multiple_quest_in_quest_category 3</value>
<value>16</value>
<value>find_questions_with_tags_with_multiple_quest_in_quest_category</value>
<value>example.loc+170110181307+5LFZK2</value>
</row>
</table>
<table name="question">
<column>id</column>
<column>category</column>
<column>parent</column>
<column>name</column>
<column>questiontext</column>
<column>generalfeedback</column>
<column>defaultmark</column>
<column>qtype</column>
<row>
<value>1</value>
<value>1</value>
<value>0</value>
<value>true or false 1</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>2</value>
<value>4</value>
<value>0</value>
<value>true or false 2</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>3</value>
<value>4</value>
<value>0</value>
<value>true or false 3</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>4</value>
<value>4</value>
<value>0</value>
<value>true or false 4</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>5</value>
<value>4</value>
<value>0</value>
<value>true or false 5</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>6</value>
<value>4</value>
<value>0</value>
<value>true or false 6</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>7</value>
<value>5</value>
<value>0</value>
<value>TF test 1</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>8</value>
<value>5</value>
<value>0</value>
<value>TF test 2</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>9</value>
<value>5</value>
<value>0</value>
<value>TF test 3</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>10</value>
<value>5</value>
<value>0</value>
<value>TF test 4</value>
<value>question text</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>11</value>
<value>6</value>
<value>0</value>
<value>multiple_quest_in_quest_category 1</value>
<value>multiple_quest_in_quest_category 1</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>12</value>
<value>7</value>
<value>0</value>
<value>multiple_quest_in_quest_category 2</value>
<value>multiple_quest_in_quest_category 2</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
<row>
<value>13</value>
<value>8</value>
<value>0</value>
<value>multiple_quest_in_quest_category 3</value>
<value>multiple_quest_in_quest_category 3</value>
<value>general feedback</value>
<value>1.0</value>
<value>truefalse</value>
</row>
</table>
<table name="question_answers">
<column>id</column>
<column>question</column>
<column>answer</column>
<column>fraction</column>
<column>feedback</column>
<row>
<value>1</value>
<value>1</value>
<value>true</value>
<value>1.0</value>
<value>feedback for true</value>
</row>
<row>
<value>2</value>
<value>1</value>
<value>false</value>
<value>0.0</value>
<value>feedback for false</value>
</row>
</table>
<table name="question_truefalse">
<column>id</column>
<column>question</column>
<column>trueanswer</column>
<column>falseanswer</column>
<row>
<value>1</value>
<value>1</value>
<value>1</value>
<value>2</value>
</row>
</table>
<table name="course_modules">
<column>id</column>
<column>course</column>
<column>module</column>
<column>falseanswer</column>
</table>
<table name="tag">
<column>id</column>
<column>userid</column>
<column>tagcollid</column>
<column>name</column>
<column>rawname</column>
<column>flag</column>
<row>
<value>1</value>
<value>2</value>
<value>1</value>
<value>adpq_5</value>
<value>adpq_5</value>
<value>0</value>
</row>
<row>
<value>2</value>
<value>2</value>
<value>1</value>
<value>phpunittag_5</value>
<value>phpunittag_5</value>
<value>0</value>
</row>
<row>
<value>3</value>
<value>2</value>
<value>1</value>
<value>phpunittag_888</value>
<value>phpunittag_888</value>
<value>0</value>
</row>
<row>
<value>4</value>
<value>2</value>
<value>1</value>
<value>adpq_6</value>
<value>adpq_6</value>
<value>0</value>
</row>
<row>
<value>5</value>
<value>2</value>
<value>1</value>
<value>adpq_7</value>
<value>adpq_7</value>
<value>0</value>
</row>
<row>
<value>6</value>
<value>2</value>
<value>1</value>
<value>adpq_8</value>
<value>adpq_8</value>
<value>0</value>
</row>
<row>
<value>7</value>
<value>2</value>
<value>1</value>
<value>adpq_9</value>
<value>adpq_9</value>
<value>0</value>
</row>
<row>
<value>8</value>
<value>2</value>
<value>1</value>
<value>adpq_10</value>
<value>adpq_10</value>
<value>0</value>
</row>
<row>
<value>22</value>
<value>2</value>
<value>1</value>
<value>test1_22</value>
<value>test1_22</value>
<value>0</value>
</row>
<row>
<value>23</value>
<value>2</value>
<value>1</value>
<value>test1_23</value>
<value>test1_23</value>
<value>0</value>
</row>
<row>
<value>24</value>
<value>2</value>
<value>1</value>
<value>test1_24</value>
<value>test1_24</value>
<value>0</value>
</row>
<row>
<value>25</value>
<value>2</value>
<value>2</value>
<value>adpq_5</value>
<value>adpq_5</value>
<value>0</value>
</row>
</table>
<table name="tag_instance">
<column>id</column>
<column>tagid</column>
<column>component</column>
<column>itemtype</column>
<column>itemid</column>
<column>tiuserid</column>
<row>
<value>1</value>
<value>1</value>
<value>core_question</value>
<value>question</value>
<value>1</value>
<value>0</value>
</row>
<row>
<value>2</value>
<value>4</value>
<value>core_question</value>
<value>question</value>
<value>2</value>
<value>0</value>
</row>
<row>
<value>3</value>
<value>5</value>
<value>core_question</value>
<value>question</value>
<value>3</value>
<value>0</value>
</row>
<row>
<value>4</value>
<value>6</value>
<value>core_question</value>
<value>question</value>
<value>4</value>
<value>0</value>
</row>
<row>
<value>5</value>
<value>7</value>
<value>core_question</value>
<value>question</value>
<value>5</value>
<value>0</value>
</row>
<row>
<value>6</value>
<value>8</value>
<value>core_question</value>
<value>question</value>
<value>6</value>
<value>0</value>
</row>
<row>
<value>7</value>
<value>1</value>
<value>core_question</value>
<value>question</value>
<value>8</value>
<value>0</value>
</row>
<row>
<value>8</value>
<value>8</value>
<value>core_question</value>
<value>question</value>
<value>9</value>
<value>0</value>
</row>
<row>
<value>9</value>
<value>8</value>
<value>core_question</value>
<value>question</value>
<value>10</value>
<value>0</value>
</row>
<row>
<value>22</value>
<value>22</value>
<value>core_question</value>
<value>question</value>
<value>11</value>
<value>0</value>
</row>
<row>
<value>23</value>
<value>23</value>
<value>core_question</value>
<value>question</value>
<value>12</value>
<value>0</value>
</row>
<row>
<value>24</value>
<value>24</value>
<value>core_question</value>
<value>question</value>
<value>13</value>
<value>0</value>
</row>
<row>
<value>25</value>
<value>25</value>
<value>core</value>
<value>course</value>
<value>2</value>
<value>0</value>
</row>
<row>
<value>26</value>
<value>2</value>
<value>core_question</value>
<value>question</value>
<value>5</value>
<value>0</value>
</row>
</table>
<table name="adaptivequiz_question">
<column>id</column>
<column>instance</column>
<column>questioncategory</column>
<row>
<value>1</value>
<value>1</value>
<value>11</value>
</row>
<row>
<value>2</value>
<value>1</value>
<value>22</value>
</row>
</table>
</dataset>
\ No newline at end of file
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Generator for the module.
*
* @package mod_adaptivequiz
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2024 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_adaptivequiz_generator extends testing_module_generator {
/**
* Creates new module instance.
*
* For params description see the parent's method.
*
* @param array|stdClass|null $record
* @param array|null $options
* @return stdClass
* @throws coding_exception
*/
public function create_instance($record = null, array $options = null) {
global $CFG;
require_once($CFG->dirroot .'/mod/adaptivequiz/locallib.php');
$record = (object)(array)$record;
if (!isset($record->questionpool) && !isset($record->questionpoolnamed)) {
throw new coding_exception('either \'questionpool\' or \'questionpoolnamed\' property must be specified when '.
'generating an adaptive quiz instance');
}
// Named question pool takes precedence over the 'questionpool' setting.
if (isset($record->questionpoolnamed)) {
if (is_string($record->questionpoolnamed)) {
$record->questionpoolnamed = [$record->questionpoolnamed];
}
$record->questionpool = $this->get_question_category_id_list_by_names($record->questionpoolnamed);
unset($record->questionpoolnamed);
}
$defaultsettings = [
'introformat' => FORMAT_MOODLE,
'attempts' => 0,
'grademethod' => ADAPTIVEQUIZ_GRADEHIGHEST,
'password' => '',
'attemptfeedback' => '',
'attemptfeedbackformat' => FORMAT_MOODLE,
'attemptonlast' => 0,
'highestlevel' => 111,
'lowestlevel' => 1,
'minimumquestions' => 1,
'maximumquestions' => 111,
'standarderror' => 1.1,
'startinglevel' => 11,
'timecreated' => time(),
'timemodified' => time(),
];
foreach ($defaultsettings as $name => $value) {
if (!isset($record->{$name})) {
$record->{$name} = $value;
}
}
return parent::create_instance($record, $options);
}
/**
* Fetches a list of id for the given names of question categories.
*
* @param string[] $names
* @return int[]
*/
private function get_question_category_id_list_by_names(array $names): array {
global $DB;
[$namesql, $nameparams] = $DB->get_in_or_equal($names);
return $DB->get_fieldset_select('question_categories', 'id', "name $namesql", $nameparams);
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_adaptivequiz;
use advanced_testcase;
use context_course;
use context_module;
/**
* Adaptive PHPUnit data generator testcase.
*
* @package mod_adaptivequiz
* @copyright 2013 onwards Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @covers \mod_adaptivequiz_generator
*/
class generator_test extends advanced_testcase {
public function test_generator() {
global $DB, $SITE;
$this->resetAfterTest();
$this->assertEquals(0, $DB->count_records('adaptivequiz'));
$generator = $this->getDataGenerator()->get_plugin_generator('mod_adaptivequiz');
$this->assertInstanceOf('mod_adaptivequiz_generator', $generator);
$this->assertEquals('adaptivequiz', $generator->get_modulename());
$questioncategory = $this->getDataGenerator()
->get_plugin_generator('core_question')
->create_question_category(['name' => 'My category']);
$generator->create_instance([
'course' => $SITE->id,
'questionpool' => [$questioncategory->id],
]);
$generator->create_instance([
'course' => $SITE->id,
'questionpool' => [$questioncategory->id],
]);
$adaptivequiz = $generator->create_instance([
'course' => $SITE->id,
'questionpool' => [$questioncategory->id],
]);
$this->assertEquals(3, $DB->count_records('adaptivequiz'));
$cm = get_coursemodule_from_instance('adaptivequiz', $adaptivequiz->id);
$this->assertEquals($adaptivequiz->id, $cm->instance);
$this->assertEquals('adaptivequiz', $cm->modname);
$this->assertEquals($SITE->id, $cm->course);
$context = context_module::instance($cm->id);
$this->assertEquals($adaptivequiz->cmid, $context->instanceid);
}
public function test_it_handles_question_category_names_when_creating_an_instance(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
$questioncategoryname1 = 'My category 1';
$questioncategoryname2 = 'My category 2';
$questioncategory1 = $this->getDataGenerator()
->get_plugin_generator('core_question')
->create_question_category([
'contextid' => $coursecontext->id,
'name' => $questioncategoryname1,
]);
$questioncategory2 = $this->getDataGenerator()
->get_plugin_generator('core_question')
->create_question_category([
'contextid' => $coursecontext->id,
'name' => $questioncategoryname2,
]);
$generator = $this->getDataGenerator()->get_plugin_generator('mod_adaptivequiz');
// Pool as a single string.
$adaptivequiz = $generator->create_instance([
'course' => $course->id,
'questionpoolnamed' => $questioncategoryname1,
]);
self::assertEquals(1, $DB->count_records('adaptivequiz_question', [
'instance' => $adaptivequiz->id,
'questioncategory' => $questioncategory1->id,
]));
// Pool as an array of strings.
$adaptivequiz = $generator->create_instance([
'course' => $course->id,
'questionpoolnamed' => [$questioncategoryname1, $questioncategoryname2],
]);
self::assertEquals(1, $DB->count_records('adaptivequiz_question', [
'instance' => $adaptivequiz->id,
'questioncategory' => $questioncategory1->id,
]));
self::assertEquals(1, $DB->count_records('adaptivequiz_question', [
'instance' => $adaptivequiz->id,
'questioncategory' => $questioncategory2->id,
]));
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Adaptive lib.php PHPUnit tests
*
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/lib.php');
use advanced_testcase;
use mod_adaptivequiz\local\attempt\attempt_state;
use stdClass;
/**
* @group mod_adaptivequiz
*/
class lib_test extends advanced_testcase {
/**
* This functions loads data via the tests/fixtures/mod_adaptivequiz.xml file
* @return void
*/
protected function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/fixtures/mod_adaptivequiz.xml']
)->to_database();
}
/**
* Provide input data to the parameters of the test_questioncat_association_insert() method.
*/
public function questioncat_association_records() {
$data = array();
$adaptivequiz = new stdClass();
$adaptivequiz->questionpool = array(1, 2, 3, 4);
$data[] = array(1, $adaptivequiz);
$adaptivequiz = new stdClass();
$adaptivequiz->questionpool = array(1, 2);
$data[] = array(2, $adaptivequiz);
$adaptivequiz = new stdClass();
$adaptivequiz->questionpool = array(1, 2, 4);
$data[] = array(3, $adaptivequiz);
return $data;
}
/**
* Test insertion of question category association records.
*
* @dataProvider questioncat_association_records
* @param int $instance: activity instance id
* @param object $adaptivequiz: An object from the form in mod_form.php
* @group adaptivequiz_lib_test
* @covers ::adaptivequiz_add_questcat_association
*/
public function test_questioncat_association_insert($instance, stdClass $adaptivequiz) {
global $DB;
$this->resetAfterTest(true);
adaptivequiz_add_questcat_association($instance, $adaptivequiz);
if (1 == $instance) {
$this->assertEquals(4, $DB->count_records('adaptivequiz_question', array('instance' => $instance)));
}
if (2 == $instance) {
$this->assertEquals(2, $DB->count_records('adaptivequiz_question', array('instance' => $instance)));
}
if (3 == $instance) {
$this->assertEquals(3, $DB->count_records('adaptivequiz_question', array('instance' => $instance)));
}
}
/**
* Test update of question category associations records.
*
* @dataProvider questioncat_association_records
* @param int $instance: activity instance id
* @param object $adaptivequiz: An object from the form in mod_form.php
* @group adaptivequiz_lib_test
* @covers ::adaptivequiz_update_questcat_association
*/
public function test_questioncat_association_update($instance, stdClass $adaptivequiz) {
global $DB;
$this->resetAfterTest(true);
adaptivequiz_add_questcat_association($instance, $adaptivequiz);
if (1 == $instance) {
$adaptivequizupdate = new stdClass();
$adaptivequizupdate->questionpool = array(111, 222, 333, 444, 555, 122, 133, 144, 155, 166);
adaptivequiz_update_questcat_association($instance, $adaptivequizupdate);
$this->assertEquals(10, $DB->count_records('adaptivequiz_question', array('instance' => $instance)));
}
if (2 == $instance) {
$adaptivequizupdate = new stdClass();
$adaptivequizupdate->questionpool = array(4);
adaptivequiz_update_questcat_association($instance, $adaptivequizupdate);
$this->assertEquals(1, $DB->count_records('adaptivequiz_question', array('instance' => $instance)));
}
if (3 == $instance) {
$adaptivequizupdate = new stdClass();
$adaptivequizupdate->questionpool = array(4, 10, 20, 30, 40, 100, 333);
adaptivequiz_update_questcat_association($instance, $adaptivequizupdate);
$this->assertEquals(7, $DB->count_records('adaptivequiz_question', array('instance' => $instance)));
}
}
/**
* This function tests the removal of an activity instance and all related data.
*
* @covers ::adaptivequiz_delete_instance
*/
public function test_adaptivequiz_delete_instance() {
global $DB;
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$instance = 330;
adaptivequiz_delete_instance($instance);
$this->assertEquals(0, $DB->count_records('adaptivequiz', array('id' => $instance)));
$this->assertEquals(0, $DB->count_records('adaptivequiz_question', array('instance' => $instance)));
$this->assertEquals(0, $DB->count_records('adaptivequiz_attempt', array('instance' => $instance)));
$this->assertEquals(0, $DB->count_records('question_usages', array('id' => $instance)));
}
/**
* This function tests the output from adaptivequiz_print_recent_mod_activity().
*
* @covers ::adaptivequiz_print_recent_mod_activity
*/
public function test_adaptivequiz_print_recent_mod_activity_details_true() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->user = new stdClass();
$dummy->user->id = 2;
$dummy->user->fullname = 'user-phpunit';
$dummy->user->alternatename = 'user-phpunit';
$dummy->user->picture = '';
$dummy->user->firstname = 'user';
$dummy->user->middlename = '-';
$dummy->user->lastname = 'phpunit';
$dummy->user->imagealt = '';
$dummy->user->email = 'a@a.com';
$dummy->user->firstnamephonetic = 'user';
$dummy->user->lastnamephonetic = 'phpunit';
$dummy->content = new stdClass();
$dummy->content->attemptstate = attempt_state::IN_PROGRESS;
$dummy->content->questionsattempted = '12';
$dummy->timestamp = 1234;
$dummy->type = 'mod_adaptivequiz';
$dummy->name = 'my-phpunit-test';
$dummy->cmid = 99;
$output = adaptivequiz_print_recent_mod_activity($dummy, 1, true, array('mod_adaptivequiz' => 'adaptivequiz'), true, true);
$this->assertStringContainsString('<table', $output);
$this->assertStringContainsString('<tr>', $output);
$this->assertStringContainsString('<td', $output);
$this->assertStringContainsString('mod/adaptivequiz/view.php?id=99', $output);
$this->assertStringContainsString('/user/view.php?id=2', $output);
$this->assertStringContainsString('user phpunit', $output);
$this->assertStringContainsString('my-phpunit-test', $output);
}
/**
* This function tests the output from adaptivequiz_print_recent_mod_activity().
*
* @covers ::adaptivequiz_print_recent_mod_activity
*/
public function test_adaptivequiz_print_recent_mod_activity_details_false() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->user = new stdClass();
$dummy->user->id = 2;
$dummy->user->fullname = 'user-phpunit';
$dummy->user->alternatename = 'user-phpunit';
$dummy->user->picture = '';
$dummy->user->firstname = 'user';
$dummy->user->middlename = '-';
$dummy->user->lastname = 'phpunit';
$dummy->user->imagealt = '';
$dummy->user->email = 'a@a.com';
$dummy->user->firstnamephonetic = 'user';
$dummy->user->lastnamephonetic = 'phpunit';
$dummy->content = new stdClass();
$dummy->content->attemptstate = attempt_state::IN_PROGRESS;
$dummy->content->questionsattempted = '12';
$dummy->timestamp = 1234;
$dummy->type = 'mod_adaptivequiz';
$dummy->name = 'my-phpunit-test';
$dummy->cmid = 99;
$output = adaptivequiz_print_recent_mod_activity($dummy, 1, false, array('mod_adaptivequiz' => 'adaptivequiz'), true, true);
$this->assertStringContainsString('<table', $output);
$this->assertStringContainsString('<tr>', $output);
$this->assertStringContainsString('<td', $output);
$this->assertStringContainsString('/user/view.php?id=2', $output);
$this->assertStringContainsString('user phpunit', $output);
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_adaptivequiz\local;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
use advanced_testcase;
use context_module;
use mod_adaptivequiz\local\attempt\attempt_state;
use stdClass;
/**
* Tests for the attempt class.
*
* @package mod_adaptivequiz
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @covers \mod_adaptivequiz\local\attempt
*/
class attempt_test extends advanced_testcase {
/** @var stdClass $activityinstance adaptivequiz activity instance object */
protected $activityinstance = null;
/** @var stdClass $cm a partially completed course module object */
protected $cm = null;
/** @var stdClass $user a user object */
protected $user = null;
/**
* This function loads data into the PHPUnit tables for testing
*/
protected function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/../fixtures/mod_adaptivequiz_adaptiveattempt.xml']
)->to_database();
}
/**
* This function creates a default user and activity instance using generator classes
* The activity parameters created are are follows:
* lowest difficulty level: 1
* highest difficulty level: 10
* minimum question attempts: 2
* maximum question attempts: 10
* standard error: 1.1
* starting level: 5
* question category ids: 1
* course id: 2
* @return void
*/
protected function setup_generator_data() {
// Create test user.
$this->user = $this->getDataGenerator()->create_user();
$this->setUser($this->user);
$this->setAdminUser();
// Create activity.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_adaptivequiz');
$options = array(
'highestlevel' => 10,
'lowestlevel' => 1,
'minimumquestions' => 2,
'maximumquestions' => 10,
'standarderror' => 1.1,
'startinglevel' => 5,
'questionpool' => array(1),
'course' => 1
);
$this->activityinstance = $generator->create_instance($options);
$this->cm = new stdClass();
$this->cm->id = $this->activityinstance->cmid;
}
/**
* This function tests retrieving an adaptivequiz attempt record
*/
public function test_get_attempt() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$dummy = new stdClass();
$dummy->id = 220;
$userid = 2;
$attempt = new attempt($dummy, $userid);
$data = $attempt->get_attempt();
$expected = new stdClass();
$expected->id = '1';
$expected->instance = '220';
$expected->userid = '2';
$expected->uniqueid = '1110';
$expected->attemptstate = attempt_state::IN_PROGRESS;
$expected->questionsattempted = '0';
$expected->attemptstopcriteria = '';
$expected->standarderror = '1.00000';
$expected->difficultysum = '0.0000000';
$expected->measure = '0.00000';
$expected->timemodified = '0';
$expected->timecreated = '0';
$this->assertEquals($expected, $data);
}
/*
* @test
*/
public function it_fails_to_set_quba_for_an_attempt_with_an_invalid_argument(): void {
$this->resetAfterTest(true);
$this->setup_generator_data();
$this->activityinstance->context = context_module::instance($this->cm->id);
$adaptiveattempt = new attempt($this->activityinstance, $this->user->id);
$this->expectException('coding_exception');
$adaptiveattempt->set_quba(new stdClass());
}
/**
* This function tests the accessor methods for question_usage_by_activity ($quba) property
*/
public function test_set_get_quba() {
$this->resetAfterTest(true);
$this->setup_generator_data();
$this->activityinstance->context = context_module::instance($this->cm->id);
$adaptiveattempt = new attempt($this->activityinstance, $this->user->id);
// Test a non initialized quba.
$this->assertNull($adaptiveattempt->get_quba());
// Test a property initializes quba.
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$adaptiveattempt->set_quba($mockquba);
$this->assertInstanceOf('question_usage_by_activity', $adaptiveattempt->get_quba());
}
/**
* This function tests whether the user's attempt has exceeded the maximum number of questions attempted
*/
public function test_max_questions_answered() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$dummy = new stdClass();
$dummy->id = 220;
$dummy->maximumquestions = 20;
$userid = 2;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->max_questions_answered());
$dummy->id = 221;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->max_questions_answered());
$dummy->id = 222;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(true, $attempt->max_questions_answered());
$dummy->id = 223;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(true, $attempt->max_questions_answered());
}
/**
* This function tests whether the user's attempt has met the minimum number of questions attempted
*/
public function test_min_questions_answered() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$dummy = new stdClass();
$dummy->id = 220;
$dummy->minimumquestions = 21;
$userid = 2;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->min_questions_answered());
$dummy->id = 221;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->min_questions_answered());
$dummy->id = 222;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(false, $attempt->min_questions_answered());
$dummy->id = 223;
$attempt = new attempt($dummy, $userid);
$attempt->get_attempt();
$this->assertEquals(true, $attempt->min_questions_answered());
}
/**
* This function test that one value is returned or an empty array is returned
*/
public function test_return_random_question() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->id = 220;
$dummy->minimumquestions = 21;
$userid = 2;
$adaptiveattempt = new attempt($dummy, $userid);
$result = $adaptiveattempt->return_random_question(array());
$this->assertEquals(0, $result);
$result = (string) $adaptiveattempt->return_random_question(
[1 => 'quest 1', 2 => 'quest 2', 3 => 'quest 3', 4 => 'quest 4']
);
$this->assertMatchesRegularExpression('/[1-4]/', $result);
}
/**
* This function tests the creation of a question_usage_by_activity object for an attempt
*/
public function test_initialize_quba() {
$this->resetAfterTest(true);
$this->setup_generator_data();
$this->activityinstance->context = context_module::instance($this->cm->id);
$adaptiveattempt = new attempt($this->activityinstance, $this->user->id);
$adaptiveattempt->get_attempt();
$quba = $adaptiveattempt->initialize_quba();
$this->assertInstanceOf('question_usage_by_activity', $quba);
}
/**
* This function tests the re-using of the question_usage_by_activity object, due to an incomplete attempt
*/
public function test_initialize_quba_with_existing_attempt_uniqueid() {
global $DB;
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$param = array('id' => 330);
$activityinstance = $DB->get_record('adaptivequiz', $param);
$adaptiveattempt = new attempt($activityinstance, 2);
$adaptiveattempt->get_attempt();
$quba = $adaptiveattempt->initialize_quba();
$this->assertInstanceOf('question_usage_by_activity', $quba);
// Check if the uniqueid of the adaptive attempt equals the id of the object loaded by quba class.
$this->assertEquals(330, $quba->get_id());
}
/**
* This function tests retrieving the last question viewed by the student for a given attempt
*/
public function test_find_last_quest_used_by_attempt() {
global $DB;
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$param = array('id' => 330);
$activityinstance = $DB->get_record('adaptivequiz', $param);
$adaptiveattempt = new attempt($activityinstance, 2);
$adaptiveattempt->get_attempt();
$quba = $adaptiveattempt->initialize_quba();
$result = $adaptiveattempt->find_last_quest_used_by_attempt($quba);
$this->assertEquals(2, $result);
}
/**
* @test
*/
public function it_fails_to_find_the_last_question_used_by_attempt_with_an_invalid_argument() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 2);
$this->expectException('coding_exception');
$adaptiveattempt->find_last_quest_used_by_attempt($dummy);
}
/**
* This function tests retrieving the last question viewed by the student for a given attempt, but using failing data
*/
public function test_find_last_umarked_question_using_bad_data() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = $this->getMockBuilder(attempt::class)
->disableOriginalConstructor()
->getMock();
// Test passing an invalid object instance.
$result = $adaptiveattempt->find_last_quest_used_by_attempt($dummy);
$this->assertEquals(0, $result);
// Test getting an empty slot value.
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$result = $adaptiveattempt->find_last_quest_used_by_attempt($mockquba);
$this->assertEquals(0, $result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_right() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_gradedright', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->never())
->method('get_id');
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertTrue($result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_wrong() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_gradedwrong', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->never())
->method('get_id');
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertTrue($result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_partial() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_gradedpartial', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->never())
->method('get_id');
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertTrue($result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_gaveup() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_gaveup', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->never())
->method('get_id');
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertTrue($result);
}
/**
* This function tests whether the user submitted an answer to the question
*/
public function test_was_answer_submitted_to_question_with_graded_todo() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$adaptiveattempt = new attempt($dummy, 1);
$mockquba = $this->getMockBuilder('question_usage_by_activity')
->disableOriginalConstructor()
->getMock();
$mockstate = $this->createMock('question_state_todo', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_state')
->with(1)
->will($this->returnValue($mockstate));
$mockquba->expects($this->once())
->method('get_id')
->will($this->returnValue(1));
$result = $adaptiveattempt->was_answer_submitted_to_question($mockquba, 1);
$this->assertFalse($result);
}
/**
* This function tests the accessor method for $slot
*/
public function test_set_get_question_slot_number() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$adaptiveattempt->set_question_slot_number(2);
$result = $adaptiveattempt->get_question_slot_number();
$this->assertEquals(2, $result);
}
/**
* This function tests the accessor method for $slot
*/
public function test_set_get_question_level() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$adaptiveattempt->set_level(2);
$result = $adaptiveattempt->get_level();
$this->assertEquals(2, $result);
}
/**
* This function tests results returned from get_question_mark()
*/
public function test_get_question_mark_with_quba_return_float() {
$this->resetAfterTest(true);
// Test quba returning a mark of 1.0.
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$mockquba->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(1.0));
$dummy = new stdClass();
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$result = $adaptiveattempt->get_question_mark($mockquba, 1);
$this->assertEquals(1.0, $result);
}
/**
* This function tests results returned from get_question_mark()
*/
public function test_get_question_mark_with_quba_return_non_float() {
$this->resetAfterTest(true);
// Test quba returning a non float value.
$mockqubatwo = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$mockqubatwo->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(0));
$dummy = new stdClass();
$userid = 1;
$adaptiveattempttwo = new attempt($dummy, $userid);
$result = $adaptiveattempttwo->get_question_mark($mockqubatwo, 1);
$this->assertEquals(0, $result);
}
/**
* This function tests results returned from get_question_mark()
*/
public function test_get_question_mark_with_quba_return_non_null() {
$this->resetAfterTest(true);
// Test quba returning null.
$mockqubathree = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$mockqubathree->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(null));
$dummy = new stdClass();
$userid = 1;
$adaptiveattemptthree = new attempt($dummy, $userid);
$result = $adaptiveattemptthree->get_question_mark($mockqubathree, 1);
$this->assertEquals(0, $result);
}
/**
* This function tests what happens when the maximum number of questions have been answered
*/
public function test_start_attempt_max_num_of_quest_answered() {
$this->resetAfterTest(true);
$attempt = $this->createPartialMock(attempt::class,
['get_attempt', 'level_in_bounds', 'max_questions_answered']);
$attempt->method('get_attempt')->willReturn(new stdClass());
$attempt->method('level_in_bounds')->willReturn(true);
$attempt->method('max_questions_answered')->willReturn(true);
$this->assertFalse($attempt->start_attempt());
}
/**
* This function tests what happens when a question slot number is not found, but the number of submitted answers is greater
* than zero.
*/
public function test_start_attempt_quest_slot_empty_quest_submit_greater_than_one() {
$dummyadaptivequiz = new stdClass();
$dummyadaptivequiz->lowestlevel = 1;
$dummyadaptivequiz->highestlevel = 100;
$mockattemptthree = $this
->getMockBuilder(attempt::class)
->onlyMethods(
['get_attempt', 'max_questions_answered', 'initialize_quba', 'find_last_quest_used_by_attempt',
'level_in_bounds']
)
->setConstructorArgs(
[$dummyadaptivequiz, 1]
)
->getMock();
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 1;
$mockattemptthree->expects($this->once())
->method('get_attempt')
->will($this->returnValue($dummyattempt));
$mockattemptthree->expects($this->once())
->method('level_in_bounds')
->will($this->returnValue(true));
$mockattemptthree->expects($this->once())
->method('max_questions_answered')
->will($this->returnValue(false));
$mockattemptthree->expects($this->once())
->method('initialize_quba');
$mockattemptthree->expects($this->once())
->method('find_last_quest_used_by_attempt')
->will($this->returnValue(0));
$this->assertFalse($mockattemptthree->start_attempt());
}
/**
* This function tests the return values for level_in_bounds()
*/
public function test_level_in_bounds() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 5;
$dummy->highestlevel = 10;
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$adaptivequiz = $adaptiveattempt->get_adaptivequiz();
$result = $adaptiveattempt->level_in_bounds(6, $adaptivequiz);
$this->assertTrue($result);
}
/**
* This function tests retrieving of question ids
*/
public function test_get_all_questions_in_attempt() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$dummy = new stdClass();
$userid = 1;
$adaptiveattempt = new attempt($dummy, $userid);
$questids = $adaptiveattempt->get_all_questions_in_attempt(330);
$this->assertEquals(2, count($questids));
$this->assertEquals(array('1' => 1, '2' => 2), $questids);
}
/**
* @test
*/
public function it_can_check_if_a_user_has_a_completed_attempt_on_a_quiz(): void {
global $DB;
$this->resetAfterTest();
$this->setup_test_data_xml();
$uniqueid = 330;
$adaptivequizid = 330;
$cmid = 5;
$userid = 2;
$adaptivequiz = $DB->get_record('adaptivequiz', ['id' => $adaptivequizid]);
$context = context_module::instance($cmid);
adaptivequiz_complete_attempt($uniqueid, $adaptivequiz, $context, $userid, '', '');
$this->assertTrue(attempt::user_has_completed_on_quiz($adaptivequizid, $userid));
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* PHPUnit tests for catalgo class.
*
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
use advanced_testcase;
use coding_exception;
use question_usage_by_activity;
use stdClass;
/**
* @group mod_adaptivequiz
* @covers \mod_adaptivequiz\local\catalgo
*/
class catalgo_test extends advanced_testcase {
/**
* This function loads data into the PHPUnit tables for testing
*
* @throws coding_exception
*/
protected function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/../fixtures/mod_adaptivequiz_catalgo.xml']
)->to_database();
}
/**
* This function tests instantiating the catalgo class without an instance of question_usage_by_activity.
*/
public function test_init_catalgo_no_quba_object_instance() {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
$algo = new catalgo(new stdClass(), 1, true, 1);
}
/**
* This function tests instantiating the catalgo class with a non-positive integer and throwing an exception.
*/
public function test_init_catalgo_negative_int_throw_except() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$this->expectException('coding_exception');
$algo = new catalgo($mockquba, -1);
}
/**
* This function tests instantiating the catalgo class without setting the level argument.
*/
public function test_init_catalgo_no_level_throw_except() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$this->expectException('coding_exception');
$algo = new catalgo($mockquba, 1, true);
}
/**
* This fuction tests the retrieval of an attempt record
*/
public function test_retrieve_attempt_record() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$algo = new catalgo($mockquba, 1, true, 1);
$result = $algo->retrieve_attempt_record(1);
$expected = new stdClass();
$expected->id = 1;
$expected->questionsattempted = 0;
$expected->difficultysum = 99;
$expected->standarderror = 1.2;
$expected->lowestlevel = 1;
$expected->highestlevel = 100;
$expected->measure = 2.222;
$this->assertEquals($expected, $result);
}
/**
* This function tests the retrieval of using illegible attempt id.
*/
public function test_retrieve_illegit_attempt_record_throw_except() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
$algo = new catalgo($mockquba, 1, true, 1);
$this->expectException('dml_missing_record_exception');
$result = $algo->retrieve_attempt_record(511);
}
/**
* This function tests was_answer_submitted_to_question() returning a false instead of a true
*/
public function test_quest_was_marked_correct_no_submit_prev_quest_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'was_answer_submitted_to_question', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(99);
$mockcatalgo->expects($this->once())
->method('was_answer_submitted_to_question')
->with(99)
->willReturn(false);
$mockcatalgo->expects($this->never())
->method('get_question_mark');
$result = $mockcatalgo->question_was_marked_correct();
$this->assertFalse($result);
}
/**
* This function tests was_answer_submitted_to_question() returning a 0 instead of a slot number
*/
public function test_quest_was_marked_correct_zero_slot_number_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(0);
$mockcatalgo->expects($this->never())
->method('get_question_mark');
$result = $mockcatalgo->question_was_marked_correct();
$this->assertNull($result);
}
/**
* This function tests get_question_mark() returning null
*/
public function test_quest_was_marked_correct_mark_is_null_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'was_answer_submitted_to_question', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(99);
$mockcatalgo->expects($this->once())
->method('was_answer_submitted_to_question')
->with(99)
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('get_question_mark')
->withAnyParameters()
->willReturn(null);
$result = $mockcatalgo->question_was_marked_correct();
$this->assertNull($result);
}
/**
* This function tests get_question_mark() returning a mark of zero
*/
public function test_quest_was_marked_correct_mark_zero() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'was_answer_submitted_to_question', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(99);
$mockcatalgo->expects($this->once())
->method('was_answer_submitted_to_question')
->with(99)
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('get_question_mark')
->withAnyParameters()
->willReturn(0.00);
$result = $mockcatalgo->question_was_marked_correct();
$this->assertFalse($result);
}
/**
* This function tests get_question_mark() returning a mark of greater than zero
*/
public function test_quest_was_marked_correct_mark_non_zero() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class,
['find_last_quest_used_by_attempt', 'was_answer_submitted_to_question', 'get_question_mark']);
$mockcatalgo->expects($this->once())
->method('find_last_quest_used_by_attempt')
->willReturn(99);
$mockcatalgo->expects($this->once())
->method('was_answer_submitted_to_question')
->with(99)
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('get_question_mark')
->withAnyParameters()
->willReturn(0.10);
$result = $mockcatalgo->question_was_marked_correct();
$this->assertTrue($result);
}
/**
* This function tests retrieve_standard_error(), retrieving the standard error value set for the activity.
*/
public function test_retrieve_standard_error() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$mockquba = $this->createMock(question_usage_by_activity::class);
$algo = new catalgo($mockquba, 1, true, 1);
$result = $algo->retrieve_standard_error(1);
$this->assertEquals(9.9, $result);
}
/**
* This function tests retrieve_standard_error() with illegible attempt id.
*/
public function test_retrieve_standard_error_throw_excep() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$mockquba = $this->createMock('question_usage_by_activity');
$algo = new catalgo($mockquba, 1, true, 1);
$this->expectException('dml_missing_record_exception');
$result = $algo->retrieve_standard_error(511);
}
/**
* This function tests compute_next_difficulty()
* Setting 0 as the lowest level and 100 as the highest level
*/
public function test_compute_next_difficulty_zero_min_one_hundred_max() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
$catalgo = new catalgo($mockquba, 1, true, 1);
$dummy = new stdClass();
$dummy->lowestlevel = 0;
$dummy->highestlevel = 100;
// Test the next difficulty level shown to the student if the student got a level 30 question wrong,
// having attempted 1 question.
$result = $catalgo->compute_next_difficulty(30, 1, false, $dummy);
$this->assertEquals(5, $result);
// Test the next difficulty level shown to the student if the student got a level 30 question right,
// having attempted 1 question.
$result = $catalgo->compute_next_difficulty(30, 1, true, $dummy);
$this->assertEquals(76, $result);
// Test the next difficulty level shown to the student if the student got a level 80 question wrong,
// having attempted 2 questions.
$result = $catalgo->compute_next_difficulty(80, 2, false, $dummy);
$this->assertEquals(60, $result);
// Test the next difficulty level shown to the student if the student got a level 80 question right,
// having attempted 2 question.
$result = $catalgo->compute_next_difficulty(80, 2, true, $dummy);
$this->assertEquals(92, $result);
}
/**
* This function tests compute_next_difficulty()
* Setting 1 as the lowest level and 10 as the highest level
*/
public function test_compute_next_difficulty_one_min_ten_max_compute_infinity() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
$catalgo = new catalgo($mockquba, 1, true, 1);
$dummy = new stdClass();
$dummy->lowestlevel = 1;
$dummy->highestlevel = 10;
$result = $catalgo->compute_next_difficulty(1, 2, false, $dummy);
$this->assertEquals(1, $result);
$result = $catalgo->compute_next_difficulty(10, 2, true, $dummy);
$this->assertEquals(10, $result);
}
/**
* This function tests results returned from get_question_mark()
*/
public function test_get_question_mark() {
$this->resetAfterTest(true);
// Test quba returning a mark of 1.0.
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockquba->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(1.0));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->get_question_mark($mockquba, 1);
$this->assertEquals(1.0, $result);
// Test quba returning a non float value.
$mockqubatwo = $this->createMock('question_usage_by_activity');
$mockqubatwo->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(1));
$catalgo = new catalgo($mockqubatwo, 1, true, 1);
$result = $catalgo->get_question_mark($mockqubatwo, 1);
$this->assertNull($result);
}
/**
* This function tests the return data from compute_right_answers()
*/
public function test_compute_right_answers() {
$this->resetAfterTest(true);
// Test use case where user got all 5 question correct.
$mockquba = $this->createPartialMock('question_usage_by_activity', ['get_slots', 'get_question_mark']);
$mockquba->expects($this->exactly(5))
->method('get_question_mark')
->willReturn(1.0);
$mockquba->expects($this->once())
->method('get_slots')
->willReturn([1, 2, 3, 4, 5]);
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_right_answers($mockquba);
$this->assertEquals(5, $result);
}
/**
* This function tests the return data from compute_right_answers()
*/
public function test_compute_right_answers_none_correct() {
$this->resetAfterTest(true);
// Test use case where user got all 5 question incorrect.
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->exactly(5))
->method('get_question_mark')
->will($this->returnValue(0));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1, 2, 3, 4, 5)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_right_answers($mockquba);
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from compute_right_answers() when get_question_mark() returns a null
*/
public function test_compute_right_answers_null_return_from_get_question_mark() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(null));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_right_answers($mockquba);
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from compute_right_answers()
*/
public function test_compute_wrong_answers() {
$this->resetAfterTest(true);
// Test use case where user got all 5 question incorrect.
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->exactly(5))
->method('get_question_mark')
->will($this->returnValue(0));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1, 2, 3, 4, 5)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_wrong_answers($mockquba);
$this->assertEquals(5, $result);
}
/**
* This function tests the return data from compute_right_answers()
*/
public function test_compute_wrong_answers_all_correct() {
$this->resetAfterTest(true);
// Test use case where user got all 5 question correct.
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->exactly(5))
->method('get_question_mark')
->will($this->returnValue(1.0));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1, 2, 3, 4, 5)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_wrong_answers($mockquba);
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from compute_wrong_answers() when get_question_mark() returns a null
*/
public function test_compute_wrong_answers_null_return_from_get_question_mark() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity');
$mockquba->expects($this->once())
->method('get_question_mark')
->will($this->returnValue(null));
$mockquba->expects($this->once())
->method('get_slots')
->will($this->returnValue(array(1)));
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->compute_wrong_answers($mockquba);
$this->assertEquals(1, $result);
}
/**
* This function tests the return data from estimate_measure()
*/
public function test_estimate_measure() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
// Test an attempt with the following details:
// sum of difficulty - 20, number of questions attempted - 10, number of correct answers - 7,
// number of incorrect answers - 3.
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->estimate_measure(20, 10, 7, 3);
$this->assertEquals(2.8473, $result);
}
/**
* This function tests the return data from estimate_standard_error()
*/
public function test_estimate_standard_error() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity', array(), array(), '', false);
// Test an attempt with the following details;
// sum of questions attempted - 10, number of correct answers - 7, number of incorrect answers - 3.
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->estimate_standard_error(10, 7, 3);
$this->assertEquals(0.69007, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where question_was_marked_correct() returns null
*/
public function test_perform_calc_steps_marked_correct_return_null_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record',
'question_was_marked_correct', 'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers',
'estimate_measure', 'estimate_standard_error', 'retrieve_standard_error',
'standard_error_within_parameters']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 10;
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(null); // Questions marked correctly returns null.
$mockcatalgo->expects($this->never())
->method('compute_next_difficulty');
$mockcatalgo->expects($this->never())
->method('compute_right_answers');
$mockcatalgo->expects($this->never())
->method('compute_wrong_answers');
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where compute_right_answers() returns 0,
* but the next difficulty number is returned.
*/
public function test_perform_calc_steps_right_ans_ret_zero_but_return_non_zero() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record',
'question_was_marked_correct', 'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers',
'retrieve_standard_error']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 10;
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(30);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->willReturn(0); // Right answers is set to zero.
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->willReturn(10); // Wrong answers is set to 10.
$mockcatalgo->expects($this->once())
->method('retrieve_standard_error')
->willReturn(1.45095);
$result = $mockcatalgo->perform_calculation_steps();
$this->assertNotEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where compute_wrong_answers() returns 0,
* but the next difficulty number is returned.
*/
public function test_perform_calc_steps_wrong_ans_return_zero_but_return_non_zero() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record', 'question_was_marked_correct',
'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers', 'retrieve_standard_error']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 10;
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->any())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(30);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->willReturn(10); // Right answers is set to 10.
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->willReturn(0); // Wrong answers is set to zero.
$mockcatalgo->expects($this->once())
->method('retrieve_standard_error')
->willReturn(1.45095);
$result = $mockcatalgo->perform_calculation_steps();
$this->assertNotEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where questions attempted is set to 0
*/
public function test_perform_calc_steps_quest_attempted_return_zero_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record',
'question_was_marked_correct', 'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers',
'estimate_measure', 'estimate_standard_error', 'retrieve_standard_error',
'standard_error_within_parameters']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 0; // Questions attempted is set to zero.
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(30);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->willReturn(1);
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->willReturn(1);
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where the sum of correct and incorrect answers
* does not equal the sum of questions attempted.
*/
public function test_perform_calc_steps_sum_corr_and_incorr_not_equl_sum_quest_attempt_fail() {
$this->resetAfterTest(true);
$mockcatalgo = $this->createPartialMock(catalgo::class, ['retrieve_attempt_record', 'question_was_marked_correct',
'compute_next_difficulty', 'compute_right_answers', 'compute_wrong_answers', 'estimate_measure',
'estimate_standard_error', 'retrieve_standard_error', 'standard_error_within_parameters']);
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 3; // Sum of question attempted.
$dummyattempt->difficultysum = 20;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(30);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->willReturn(1); // Sum of right answers.
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->willReturn(1); // Sum of wrong answers.
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(0, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where the user answered
* the last question correctly and the attempt has not met the minimum stopping criteria.
*/
public function test_perform_calculation_steps_nostop_correct_answer() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
// Minimum stopping criteria has not been met.
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['retrieve_attempt_record', 'question_was_marked_correct', 'compute_next_difficulty',
'compute_right_answers', 'compute_wrong_answers', 'estimate_measure', 'estimate_standard_error',
'retrieve_standard_error', 'standard_error_within_parameters']
)
->setConstructorArgs(
[$mockquba, 1, false, 50]
)
->getMock();
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 1;
$dummyattempt->difficultysum = 50;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true); // Last attempted question marked correctly.
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(52);
$mockcatalgo->expects($this->never())
->method('compute_right_answers');
$mockcatalgo->expects($this->never())
->method('compute_wrong_answers');
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(52, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where the user answered
* the last question incorrectly and the attempt has not met the minimum stopping criteria.
*/
public function test_perform_calculation_steps_nostop_incorrect_answer() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
// Minimum stopping criteria has not been met.
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['retrieve_attempt_record', 'question_was_marked_correct', 'compute_next_difficulty',
'compute_right_answers', 'compute_wrong_answers', 'estimate_measure', 'estimate_standard_error',
'retrieve_standard_error', 'standard_error_within_parameters']
)
->setConstructorArgs(
[$mockquba, 1, false, 50]
)
->getMock();
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 1;
$dummyattempt->difficultysum = 50;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(false); // Last attempted question marked incorrectly.
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->willReturn(48);
$mockcatalgo->expects($this->never())
->method('compute_right_answers');
$mockcatalgo->expects($this->never())
->method('compute_wrong_answers');
$mockcatalgo->expects($this->never())
->method('estimate_measure');
$mockcatalgo->expects($this->never())
->method('estimate_standard_error');
$mockcatalgo->expects($this->never())
->method('retrieve_standard_error');
$mockcatalgo->expects($this->never())
->method('standard_error_within_parameters');
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(48, $result);
}
/**
* This function tests the return data from perform_calculation_steps(), where the attempt has met
* all the criteria to determine the standard error and the function runs from beginning to end.
*/
public function test_perform_calculation_steps_stop_no_fail() {
$this->resetAfterTest(true);
$mockquba = $this->createMock(question_usage_by_activity::class);
// Minimum stopping criteria has been met.
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['retrieve_attempt_record', 'question_was_marked_correct', 'compute_next_difficulty', 'compute_right_answers',
'compute_wrong_answers', 'retrieve_standard_error', 'standard_error_within_parameters']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$dummyattempt = new stdClass();
$dummyattempt->questionsattempted = 2;
$dummyattempt->difficultysum = 50;
$mockcatalgo->expects($this->once())
->method('retrieve_attempt_record')
->with(1)
->willReturn($dummyattempt);
$mockcatalgo->expects($this->once())
->method('question_was_marked_correct')
->willReturn(true);
$mockcatalgo->expects($this->once())
->method('compute_next_difficulty')
->with(50, 2, true, $dummyattempt)
->willReturn(48);
$mockcatalgo->expects($this->once())
->method('compute_right_answers')
->withAnyParameters()
->willReturn(1);
$mockcatalgo->expects($this->once())
->method('compute_wrong_answers')
->withAnyParameters()
->willReturn(1);
$mockcatalgo->expects($this->once())
->method('retrieve_standard_error')
->with(1)
->willReturn(5);
$mockcatalgo->expects($this->once())
->method('standard_error_within_parameters')
->withAnyParameters() // Second parameter (is not rounded) and has decimal with many decimal places.
->willReturn(1.0);
$result = $mockcatalgo->perform_calculation_steps();
$this->assertEquals(48, $result);
}
/**
* This function tests the return value from standard_error_within_parameters().
*/
public function test_standard_error_within_parameters_return_true_then_false() {
$this->resetAfterTest(true);
$mockquba = $this->createMock('question_usage_by_activity');
$catalgo = new catalgo($mockquba, 1, true, 1);
$result = $catalgo->standard_error_within_parameters(0.02, 0.1);
$this->assertTrue($result);
$result = $catalgo->standard_error_within_parameters(0.01, 0.002);
$this->assertFalse($result);
}
/**
* This function tests the return value from get_current_diff_level().
*/
public function test_get_current_diff_level_using_level_zero() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 1;
$dummy->highestlevel = 20;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($mockquba, 0, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level_using_no_quba() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 1;
$dummy->highestlevel = 20;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($dummy, 1, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level_using_no_attempt_obj() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($mockquba, 1, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level_using_attempt_obj_missing_lowestlevel() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->highestlevel = 20;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($mockquba, 1, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level_using_attempt_obj_missing_highestlevel() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 20;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->never())
->method('return_current_diff_level');
$this->expectException('coding_exception');
$mockcatalgo->get_current_diff_level($mockquba, 1, $dummy);
}
/**
* This function tests the return value from get_current_diff_level()
*/
public function test_get_current_diff_level() {
$this->resetAfterTest(true);
$dummy = new stdClass();
$dummy->lowestlevel = 20;
$dummy->highestlevel = 21;
$mockquba = $this->createMock(question_usage_by_activity::class);
$mockcatalgo = $this
->getMockBuilder(catalgo::class)
->onlyMethods(
['return_current_diff_level']
)
->setConstructorArgs(
[$mockquba, 1, true, 50]
)
->getMock();
$mockcatalgo->expects($this->once())
->method('return_current_diff_level')
->willReturn(3);
$result = $mockcatalgo->get_current_diff_level($mockquba, 1, $dummy);
$this->assertEquals(3, $result);
}
/**
* This function tests the output from convert_percent_to_logit()
*/
public function test_convert_percent_to_logit_using_param_less_than_zero() {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
$result = catalgo::convert_percent_to_logit(-1);
}
/**
* This function tests the output from convert_percent_to_logit()
*/
public function test_convert_percent_to_logit_using_param_greater_than_decimal_five() {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
catalgo::convert_percent_to_logit(0.51);
}
/**
* This function tests the output from convert_percent_to_logit()
*/
public function test_convert_percent_to_logit() {
$this->resetAfterTest(true);
$result = catalgo::convert_percent_to_logit(0.05);
$result = round($result, 1);
$this->assertEquals(0.2, $result);
}
/**
* This function tests the output from convert_logit_to_percent()
*/
public function test_convert_logit_to_percent_using_param_less_than_zero() {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
catalgo::convert_logit_to_percent(-1);
}
/**
* This function tests the output from convert_logit_to_percent()
*/
public function test_convert_logit_to_percent() {
$this->resetAfterTest(true);
$result = catalgo::convert_logit_to_percent(0.2);
$result = round($result, 2);
$this->assertEquals(0.05, $result);
}
/**
* This function tests the output from map_logit_to_scale()
*/
public function test_map_logit_to_scale() {
$this->resetAfterTest(true);
$result = catalgo::map_logit_to_scale(-0.6, 16, 1);
$result = round($result, 1);
$this->assertEquals(6.3, $result);
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* fetch question PHPUnit tests
*
* @copyright 2013 Remote-Learner {@link http://www.remote-learner.ca/}
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/adaptivequiz/locallib.php');
use advanced_testcase;
use coding_exception;
use mod_adaptivequiz\local\repository\questions_number_per_difficulty;
use stdClass;
/**
* @group mod_adaptivequiz
* @covers \mod_adaptivequiz\local\fetchquestion
*/
class fetchquestion_test extends advanced_testcase {
/** @var stdClass $activityinstance adaptivequiz activity instance object */
protected $activityinstance = null;
/** @var stdClass $cm a partially completed course module object */
protected $cm = null;
/** @var stdClass $user a user object */
protected $user = null;
/**
* This function loads data into the PHPUnit tables for testing
*
* @return void
* @throws coding_exception
*/
protected function setup_test_data_xml() {
$this->dataset_from_files(
[__DIR__.'/../fixtures/mod_adaptivequiz_findquestion.xml']
)->to_database();
}
/**
* This function creates a default user and activity instance using generator classes
* The activity parameters created are are follows:
* lowest difficulty level: 1
* highest difficulty level: 10
* minimum question attempts: 2
* maximum question attempts: 10
* standard error: 1.1
* starting level: 5
* question category ids: 1
* course id: 2
* @return void
*/
protected function setup_generator_data() {
// Create test user.
$this->user = $this->getDataGenerator()->create_user();
$this->setUser($this->user);
$this->setAdminUser();
// Create activity.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_adaptivequiz');
$options = array(
'highestlevel' => 10,
'lowestlevel' => 1,
'minimumquestions' => 2,
'maximumquestions' => 10,
'standarderror' => 1.1,
'startinglevel' => 5,
'questionpool' => array(1),
'course' => 2
);
$this->activityinstance = $generator->create_instance($options);
$this->cm = new stdClass();
$this->cm->id = $this->activityinstance->cmid;
}
/**
* This function creates a default user and activity instance using generator classes (using a different question category)
* The activity parameters created are are follows:
* lowest difficulty level: 1
* highest difficulty level: 10
* minimum question attempts: 2
* maximum question attempts: 10
* standard error: 1.1
* starting level: 5
* question category ids: 1
* course id: 2
* @return void
*/
protected function setup_generator_data_two() {
// Create test user.
$this->user = $this->getDataGenerator()->create_user();
$this->setUser($this->user);
$this->setAdminUser();
// Create activity.
$generator = $this->getDataGenerator()->get_plugin_generator('mod_adaptivequiz');
$options = array(
'highestlevel' => 10,
'lowestlevel' => 1,
'minimumquestions' => 2,
'maximumquestions' => 10,
'standarderror' => 1.1,
'startinglevel' => 5,
'questionpool' => array(4),
'course' => 2
);
$this->activityinstance = $generator->create_instance($options);
$this->cm = new stdClass();
$this->cm->id = $this->activityinstance->cmid;
}
/**
* This function tests the retrieval of using illegible tag ids.
* @see setup_generator_data() for detail of activity instance.
*/
public function test_find_questions_fail_tag_ids() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$this->setup_generator_data();
$attempt = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories']
)
->setConstructorArgs(
[$this->activityinstance, 1, 1, 100]
)
->getMock();
$attempt->expects($this->exactly(2))
->method('retrieve_question_categories')
->willReturn(
[1 => 1, 2 => 2, 3 => 3]
);
$data = $attempt->find_questions_with_tags(
[99]
);
$this->assertEquals(0, count($data));
$data = $attempt->find_questions_with_tags([]);
$this->assertEquals(0, count($data));
}
/**
* This function tests the retrieval of questions using an empty set of question categories.
* @see setup_generator_data() for detail of activity instance.
*/
public function test_find_questions_fail_question_cat() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$this->setup_generator_data();
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories']
)
->setConstructorArgs(
[$this->activityinstance, 1, 1, 100]
)
->getMock();
$mockclass->expects($this->exactly(2))
->method('retrieve_question_categories')
->willReturn([]);
// Call class method with illegible tag id.
$data = $mockclass->find_questions_with_tags(
[99]
);
$this->assertEquals(0, count($data));
// Call class method with legit tag id.
$data = $mockclass->find_questions_with_tags(
[1]
);
$this->assertEquals(0, count($data));
}
/**
* This function tests the retrieval of questions using the exclude parameter
* @see setup_generator_data() for detail of activity instance
* @return void
*/
public function test_find_questions_exclude() {
$this->resetAfterTest(true);
$this->setup_test_data_xml();
$this->setup_generator_data();
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories']
)
->setConstructorArgs(
[$this->activityinstance, 1, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('retrieve_question_categories')
->willReturn(
[1 => 1, 2 => 2, 3 => 3]
);
$data = $mockclass->find_questions_with_tags([1], [1]);
$this->assertEquals(0, count($data));
}
/**
* This functions tests the accessor methods for the $level class variable.
*/
public function test_get_set_level() {
$this->resetAfterTest(true);
$dummyclass = new stdClass();
$fetchquestion = new fetchquestion($dummyclass, 99, 1, 100);
$this->assertEquals(99, $fetchquestion->get_level());
$fetchquestion->set_level(22);
$this->assertEquals(22, $fetchquestion->get_level());
$this->expectException('coding_exception');
$fetchquestion->set_level(-22);
}
/**
* @test
*/
public function it_fails_when_instantiated_with_a_zero_difficulty_level(): void {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
(new fetchquestion(new stdClass(), 0, 1, 100, ['phpunittag_']));
}
/**
* @test
*/
public function it_fails_when_instantiated_with_a_negative_difficulty_level(): void {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
(new fetchquestion(new stdClass(), -11, 1, 100, ['phpunittag_']));
}
/**
* @test
*/
public function it_fails_when_instantiated_with_a_difficulty_level_as_a_string(): void {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
(new fetchquestion(new stdClass(), 'asdf', 1, 100, ['phpunittag_']));
}
/**
* This functions tests the retrevial of tag ids with an associated difficulty level
* but using legit data.
*/
public function test_retrieve_tag() {
$this->resetAfterTest();
$this->setup_test_data_xml();
$dummyclass = new stdClass();
$fetchquestion = new fetchquestion($dummyclass, 5, 1, 100, ['phpunittag_']);
$data = $fetchquestion->retrieve_tag(5);
$this->assertEquals(2, count($data));
$this->assertEquals([0 => 1, 1 => 2], $data);
$fetchquestion2 = new fetchquestion($dummyclass, 888, 1, 100, ['phpunittag_']);
$data = $fetchquestion2->retrieve_tag(888);
$this->assertEquals(0, count($data));
}
/**
* This function test output from fetch_question() where initalize_tags_with_quest_count() returns an empty array
*/
public function test_fetch_question_initalize_tags_with_quest_count_return_empty_array() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->willReturn([]);
$mockclass->expects($this->never())
->method('retrieve_tag');
$mockclass->expects($this->never())
->method('find_questions_with_tags');
$result = $mockclass->fetch_questions();
$this->assertEquals([], $result);
}
/**
* This function test output from fetch_question() where the initial requested level has available questions
*/
public function test_fetch_question_requested_level_has_questions() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], '1', '100')
->willReturn(
[5 => 2]
);
$mockclass->expects($this->once())
->method('retrieve_tag')
->with(5)
->willReturn(
[11]
);
$mockclass->expects($this->once())
->method('find_questions_with_tags')
->with(array(11), array())
->willReturn(
[22]
);
$result = $mockclass->fetch_questions();
$this->assertEquals([22], $result);
}
/**
* This function test output from fetch_question() where one level higher than requested level has available
* questions.
*/
public function test_fetch_question_one_level_higher_has_questions() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], '1', '100')
->willReturn(
[5 => 0, 6 => 1]
);
$mockclass->expects($this->once())
->method('retrieve_tag')
->with(6)
->willReturn(
[11]
);
$mockclass->expects($this->once())
->method('find_questions_with_tags')
->with([11], [])
->willReturn(
[22]
);
$result = $mockclass->fetch_questions();
$this->assertEquals([22], $result);
}
/**
* This function test output from fetch_question() where five levels higher than requested level has available
* questions.
*/
public function test_fetch_question_five_levels_higher_has_questions() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], '1', '100')
->willReturn(
[1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0, 9 => 0, 10 => 2]
);
$mockclass->expects($this->once())
->method('retrieve_tag')
->with(10)
->willReturn(
[11]
);
$mockclass->expects($this->once())
->method('find_questions_with_tags')
->with([11], [])
->willReturn(
[22]
);
$result = $mockclass->fetch_questions();
$this->assertEquals([22], $result);
}
/**
* This function test output from fetch_question() where four levels lower than requested level has available
* questions.
*/
public function test_fetch_question_four_levels_lower_has_questions() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 5, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], '1', '100')
->willReturn(
[1 => 1, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0, 7 => 0, 8 => 0, 9 => 0, 10 => 0]
);
$mockclass->expects($this->once())
->method('retrieve_tag')
->with(1)
->willReturn(
[11]
);
$mockclass->expects($this->once())
->method('find_questions_with_tags')
->with([11], [])
->willReturn([22]);
$result = $mockclass->fetch_questions();
$this->assertEquals([22], $result);
}
/**
* This function test output from fetch_question() where searching for a question goes outside
* the min and max boundaries and stops the searching.
*/
public function test_fetch_question_search_outside_min_max_bounds() {
$this->resetAfterTest(true);
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['initalize_tags_with_quest_count', 'retrieve_tag', 'find_questions_with_tags']
)
->setConstructorArgs(
[new stdClass(), 50, 49, 51]
)
->getMock();
$mockclass->expects($this->once())
->method('initalize_tags_with_quest_count')
->with([], ['adpq_'], 49, 51)
->willReturn(
[48 => 1, 52 => 1]
);
$mockclass->expects($this->never())
->method('retrieve_tag');
$mockclass->expects($this->never())
->method('find_questions_with_tags');
$result = $mockclass->fetch_questions();
$this->assertEquals([], $result);
}
/**
* @test
*/
public function it_retrieves_all_tag_ids(): void {
$this->resetAfterTest();
$this->setup_test_data_xml();
$fetchquestion = new fetchquestion(new stdClass(), 5, 1, 100);
$result = $fetchquestion->retrieve_all_tag_ids(1, 100, ADAPTIVEQUIZ_QUESTION_TAG);
$this->assertEquals(
[5 => '1', 6 => '4', 7 => '5', 8 => '6', 9 => '7', 10 => '8'],
$result
);
}
/**
* @test
*/
public function it_throws_an_exception_when_retrieves_all_tag_ids_for_an_empty_tag_prefix(): void {
$fetchquestion = new fetchquestion(new stdClass(), 5, 1, 100);
$this->expectException('invalid_parameter_exception');
$fetchquestion->retrieve_all_tag_ids(1, 5, '');
}
/**
* This is a data provider for
* @return $data - an array with arrays of data
*/
public function constructor_throw_coding_exception_provider() {
$data = array(
array(0, 1, 100),
array(1, 100, 100),
array(1, 100, 99)
);
return $data;
}
/**
* This function tests throwing an exception by passing incorrect parameters
*
* @param int $level the difficulty level
* @param int $min the minimum level of the attempt
* @param int $max the maximum level of the attempt
* @dataProvider constructor_throw_coding_exception_provider
*/
public function test_constructor_throw_coding_exception($level, $min, $max) {
$this->resetAfterTest(true);
$this->expectException('coding_exception');
(new fetchquestion(new stdClass(), $level, $min, $max));
}
/**
* This function tests the output from initalize_tags_with_quest_count()
*/
public function test_initalize_tags_with_quest_count() {
$this->resetAfterTest();
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories', 'retrieve_all_tag_ids', 'retrieve_tags_with_question_count']
)
->setConstructorArgs(
[new stdClass(), 1, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('retrieve_question_categories')
->willReturn(
[1 => 1, 2 => 2, 3 => 3]
);
$mockclass->expects($this->exactly(2))
->method('retrieve_all_tag_ids')
->withAnyParameters()
->willReturn(
[4 => 4, 5 => 5, 6 => 6]
);
$mockclass->expects($this->exactly(2))
->method('retrieve_tags_with_question_count')
->withAnyParameters()
->willReturn(
[
new questions_number_per_difficulty(1, 8),
new questions_number_per_difficulty(2, 3),
new questions_number_per_difficulty(5, 10),
]
);
$result = $mockclass->initalize_tags_with_quest_count([], ['test1_', 'test2_'], 1, 100);
$this->assertEquals([1 => 16, 2 => 6, 5 => 20], $result);
}
/**
* This function tests the output from initalize_tags_with_quest_count(), passing an already built difficulty question
* sum structure, forcing a rebuild.
*/
public function test_initalize_tags_with_quest_count_pre_built_quest_sum_struct_rebuild_true() {
$this->resetAfterTest();
$mockclass = $this
->getMockBuilder(fetchquestion::class)
->onlyMethods(
['retrieve_question_categories', 'retrieve_all_tag_ids', 'retrieve_tags_with_question_count']
)
->setConstructorArgs(
[new stdClass(), 1, 1, 100]
)
->getMock();
$mockclass->expects($this->once())
->method('retrieve_question_categories')
->willReturn(
[1 => 1, 2 => 2, 3 => 3]
);
$mockclass->expects($this->exactly(2))
->method('retrieve_all_tag_ids')
->withAnyParameters()
->willReturn(
[4 => 4, 5 => 5, 6 => 6]
);
$mockclass->expects($this->exactly(2))
->method('retrieve_tags_with_question_count')
->withAnyParameters()
->willReturn(
[
new questions_number_per_difficulty(1, 8),
new questions_number_per_difficulty(2, 3),
new questions_number_per_difficulty(5, 10),
]
);
$result = $mockclass->initalize_tags_with_quest_count([1, 2, 3, 4], ['test1_', 'test2_'], 1, 100, true);
$this->assertEquals([1 => 16, 2 => 6, 5 => 20], $result);
}
/**
* This function tests the output from decrement_question_sum_from_difficulty().
*/
public function test_decrement_question_sum_from_difficulty() {
$this->resetAfterTest(true);
$dummyclass = new stdClass();
$result = array(1 => 12);
$expected = array(1 => 11);
$fetchquestion = new fetchquestion($dummyclass, 1, 1, 2);
$result = $fetchquestion->decrement_question_sum_from_difficulty($result, 1);
$this->assertEquals($expected, $result);
}
/**
* This function tests the output from decrement_question_sum_from_difficulty(), using a key that doesn't exist.
*/
public function test_decrement_question_sum_from_difficulty_user_missing_key() {
$this->resetAfterTest(true);
$dummyclass = new stdClass();
$result = array(1 => 12);
$expected = array(1 => 12);
$fetchquestion = new fetchquestion($dummyclass, 1, 1, 2);
$result = $fetchquestion->decrement_question_sum_from_difficulty($result, 2);
$this->assertEquals($expected, $result);
}
}
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* @copyright 2022 onwards Vitaly Potenko <potenkov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_adaptivequiz\local\report;
use basic_testcase;
use stdClass;
/**
* @covers \mod_adaptivequiz\local\report\questions_difficulty_range
*/
class questions_difficulty_range_test extends basic_testcase {
public function test_it_can_be_created_from_activity_record(): void {
$record = new stdClass();
$record->lowestlevel = 5;
$record->highestlevel = 25;
$range = questions_difficulty_range::from_activity_instance($record);
$this->assertEquals(5, $range->lowest_level());
$this->assertEquals(25, $range->highest_level());
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment