Gradescope-Googletest Grading Glue (gggg)
This is a lightweight framework for Gradescope autograding of C++ programming assignments.
A gggg assignment involves several software services working together.
- Canvas (or another LMS) announces the assignment to students, and stores canonical grades.
- GitHub Education manages a private repository for each student team. Each repository is initially a copy of a template repository, containing starter code, created by an instructor. Students submit work by pushing to these repositories.
- Gradescope manages grading, including automated grading (autograding) and optional subjective grading. Gradescope pushes scores to Canvas.
- GoogleTest can be used for graded code correctness tests.
- Other rubric items can be defined in Python.
- This gggg project is the glue that connects these components.
- An instructor specifies how a submission is graded by writing a short Python script, conventionally named
grade.py
. Agrade.py
script uses thegggg.py
library, which defines an API for declaring submission and rubric policies. - Sample
Makefile
s have amake grade
target. Students runmake grade
locally to preview their grade. The Gradescope autograde runsmake grade
to produce a machine-readable score. - The
make-autograder
program creates anautograder.zip
needed by Gradescope. Anautograder.zip
contains some very short scripts that tell Gradescope how to prepare a container, runmake grade
, and import the score generated bygrade.py
.
- An instructor specifies how a submission is graded by writing a short Python script, conventionally named
This section explains what happens in an assignment, in chronological order.
Instructor prepares a class (once per term):
- Follow the GitHub Education quickstart instructions to create an organization for the course, and establish or renew an educator discount.
- Create a classroom object in GitHub Education.
- Add the GradeScope App to the class Canvas space.
Students prepare for a class (once per term):
- Create GitHub accounts. It is a best practice to assign this as an out-of-class homework assignment. GitHub has a DDoS mitigation that is triggered by a lab's worth of students all creating accounts at the same moment from similar IP addresses.
Instructor creates an assignment:
- Create a template repository inside the GitHub Organization, to hold starter code distributed to students.
- In the GitHub web view: create a private repository; suggest no to README; MIT license; and C++ .gitignore.
- In Settings: turn on
Template repository
. - Clone the repo to a local machine. Copy files from the
ggg/template-skeleton
directory:Makefile
,README.md
,gggg.py
,grade.py
, and (if relevant)timer.hpp
.
- Create a C++ solution, unit tests, Makefile, and
grade.py
script (see thetemplate-example
directory for a working example). Confirm thatmake grade
works and shows a perfect score. Never commit the solution, because students could view it in the git history. The Gradescope containers run in Ubuntu 18.04, so ensure that your code can compile and run in that environment. - Archive the solution. Suggestion:
zip
the repo on the commandline. - Modify the
.hpp
and.cpp
files to become starter code; add a TODO comment everywhere that students should edit code; confirm thatmake grade
works and shows an imperfect score; and commit+push the starter code. - Archive the starter code. Suggestion: web view > Code > Download ZIP.
- Create an autograder ZIP. In the terminal, move into the template repo and run the gggg
make-autograder
script.make-autograder -h
displays usage help. Pass a-f <filename>
argument for each<filename>
that students may not modify. The autograder will overwrite these files with starter code to prevent an exploit where a student modifies the tests or grading logic. Example:$ make-autograder -f Makefile -f gggg.py -f grade.py -f product_test.cpp -o autograder.zip
- Create a GitHub Education assignment object: classroom.github.com > New Assignment > Create Group Assignment (or individual assignment, as the case may be). Suggested settings:
- Title: "Project 2", "Lab 3", or similar.
- Deadline: blank (Gradescope enforces deadlines)
- Individual or Group: self-explanatory
- (Group assignment) Name your set of teams: "Project 2 Groups" or similar
- (Group assignment) Maximum members per team: 3 (or whatever your class policy is)
- Repository visibility: Private (otherwise plagiarism is extremely easy and tempting)
- Grant students admin access to their repository: no (these privileges allow students to irreparably break their repos)
- Your assignment repository prefix: automatically populates to "project-2" or similar
- Add a template repository to give students starter code: Find and use the template repo you created above.
- Allow students to use an online IDE: no (default)
- (Continue)
- Add autograding tests: no (we use Gradescope instead)
- Enable feedback pull requests: no (default)
- (Create Assignment)
- Copy the assignment link, you will need it below (looks like https://classroom.github.com/g/mwO5m1Za).
- Create a Canvas assignment object. This is a lightweight placeholder that only serves to make the deadline visible in Canvas, and the grades available in the Canvas gradebook. (If you do not use Canvas, instead create an assignment in your chosen LMS.)
- Decide whether your assignment will be graded solely on the basis of automated
grade.py
scores, or will also include manual subjective scores. - Calculate your maximum score = (max
grade.py
points) + (max manual points) - Canvas > Create Assignment
- Points: maximum score calculated above
- Submission type: External Tool > Gradescope. Load This Tool In A New Tab: yes
- Allowed Attempts: Unlimited (unclear if this setting matters when using External Tool)
- Assign to: everyone, with your stated deadline. (This deadline will be communicated to students in their calendar view. Gradescope will enforce the deadline.)
- Decide whether your assignment will be graded solely on the basis of automated
- Create a Gradescope assignment object. This is the more substantial assignment where students make submissions, view feedback, and may request regrades.
- gradescope.com > Course > Assignments > Create New Assignment > Programming Assignment > Next
- Assignment Name: same as the Canvas assignment, e.g. "Project 2"
- Autograder points: (max
grade.py
points) - Enable Manual Grading: yes iff you include manual subjective scores
- Release date: your choice, probably now
- Due date: match deadline in Canvas
- Enable Group Submission: yes (if this is a group project)
- Limit Group Size: match group size in Github Education
- Next
- Configure Autograder: Upload the
autograder.zip
created above. Wait for the container to finish building. - Test Autograder: Upload the solution archive ZIP you created above, and confirm that it is graded properly. Likewise, confirm that the starter code archive ZIP is graded properly.
- Link Canvas: Assignment > Settings > Canvas Assignment: choose your Canvas assignment; click Link
- Create an assignment brief (instructions) and publish it to students. A Canvas Page is best for accessibility and permissions management, but a Google Doc is also acceptable. Include links to:
- The Github Classroom invitation URL (looks like https://classroom.github.com/g/mwO5m1Za)
- Gradescope Student Center: https://help.gradescope.com/category/cyk4ij2dwi-student-workflow
- GitHub guides: https://guides.github.com/
- Pro Git book: https://git-scm.com/book/en/v2
- David McLaren’s github getting started video: https://www.youtube.com/watch?v=1a5L_xsGIm8
- Watch your email for, and approve the third-party application approval request. The first time that a student tries to submit a repo from your organization to Gradescope, github will ask you (instructor) to approve this integration. Until you approve this, when students visit Gradescope and try to submit a Github repo, their repo will not appear in the list of choices.
Students work an assignment:
- Decide on who is in the team. This must be done out-of-band before the next steps. GitHub Education does not support modifying teams, and there is no interface for instructors to modify a team.
- The first team member follows the invitation URL (looks like https://classroom.github.com/g/mwO5m1Za) to create a team and repository.
- Subsequent team members, if any, follow the invitation URL and join the team from the previous step. This is necessary for the team members to access the repo, but does not impact who gets credit for the submission. Technically a team could skip this step and do all edits from a single github account, but this is discouraged.
- Clone the repo to local machines.
- Develop code; write, test, and debug. Run
make test
and respond to unit test feedback. Commit+push regularly. - Preview grade; run
make grade
, respond to feedback, and decide whether to continue working. - Commit+push the final draft to github.
- Submit the repo to Gradescope. Follow the instructions; submitting a GitHub repo directly is bulletproof, but uploading a ZIP also works. At this step, students indicate their team members if any. This choice determines who gets credit, but has no bearing on repo access.
- Confirm that the Gradescope autograder feedback matches the local
make grade
output. Report any discrepancies to the instructor.
Instructors or graders grade the assignment:
- Suggestion: wait until after the deadline and grade all submissions in one pass.
- Spot-check that the autograder results look reasonable (a mix of perfect, low, and in-between scores).
- Use the Code Similarity tool to detect gross plagiarism and respond accordingly.
- Perform manual grading (if applicable).
- Click Publish Grades to push scores to Canvas.
After grading:
- Students may request regrades through Gradescope.
- Suggestion: ask students to request regrades only through Gradescope, not out-of-band. This makes it clear which question the student is inquiring about (which can be difficult to communicate), ensures that regrades do not slip through the cracks, and routes the request properly in the case of multiple graders.
- After regrading in Gradescope, the instructor/grader needs to Publish Grades again to sync to Canvas.
Your assessment logic is defined in a grade.py
script. The make grade
make target compiles the code and then executes grade.py
. Students will run make grade
interactively on local machines, and the Gradescope autograder will run make grade
in a headless container. When invoked by make grade
, grade.py
typically examines the submission code and executables; runs unit tests and examines their output (if using); prints a human-readable summary to standard output, for student consumption; and writes a results.json
, for Gradescope autograder consumption.
There are two kinds of grading results:
- Reject: The submission is illegible or otherwise unacceptable. Examples: no names; identical to starter code; does not compile; unit tests crash. The score is a flat instructor-hardcoded number, often 0% or approximately 50%.
- Accept: The submission is acceptible, and grading proceeds. It is subjected to correctness tests, and each passing tests adds points to the score, counting up from zero.
See gggg.py
for the high-level grading API.
- An
Assignment
object represents assignment policies, notably the maximum score, and score for a rejected submission. - A
State
object represents the current state of the grading process, notably whether the submission has been rejected, and the total earned points.
Generally, a grade.py
script will
import
fromgggg
.- Create an
Assignment
andState
object. - Call
State.reject_if...
functions to detect and reject where appropriate. These functions have no effect if the submission has already been rejected. - Call
State.gtest_run
to execute a unit test program. (Again, no effect if already rejected.) - Call
State...test
functions to evaluate whether to grant points. Each of these function calls corresponds to a rubric row. (Again, no effect if already rejected.) - Call
State.summarize()
to produce output. This step tells the student their score, and whether their submisison was rejected. So it needs to happen even if the submission was rejected.
The template-example/grade.py
example contains:
from gggg import *
a = Assignment(12, 6)
s = State(a)
horizontal_rule()
s.reject_if_missing_contributors()
s.reject_if_starter_contributors()
s.reject_unless_files_exist(['product.hpp',
'product_test.cpp'])
s.reject_if_file_unchanged('product.hpp',
'953ed73434f4cae54b9161b48da2f25a2622522198a655c00de571bb596b16df')
s.reject_if_file_changed('product_test.cpp',
'0139691ee712697636a51e979b866b4a939f7840032fc81208f22c8931f90a5d')
s.reject_unless_command_succeeds(['make', 'clean', 'product_test'])
s.string_removed_test('TODO comments removed', 3, 'TODO', ['product.hpp'])
s.gtest_run('product_test')
s.gtest_suite_test('ProductPositive', 3)
s.gtest_suite_test('ProductZero', 3)
s.gtest_suite_test('ProductNegative', 3)
s.summarize()
Currently the only kind of correctness assessment that gggg
supports is GoogleTest unit tests. This accommodates advanced computer science courses where students implement C++ modules with functions and class members. However, it does not accommodate introductory courses where students write programs that interact with stdin/stdout.
Functionality for running programs with stdin output and commandline arguments, and assessing stdout output and return codes, is planned for Spring 2022. The author will be teaching an introductory course in that time frame. We have legacy prototype code for this kind of assessment, and will refactor it into gggg
.