The markmyassignment
package is a tool to
easily get automatic feedback on your lab assignment before handing it
in.
The easiest way to install the package in R is as follows:
All documentation of the package and the functionality can be found using:
You can also get troubleshooting or answers to common questions here. Please add your own questions and solutions if you have any!
To use the package markmyassignment, you must first load it into your R session:
Then, use set_assignment()
with the path provided by the
teacher. Below you will find an example assignment that is part of the
markmyassignment
package. The assignment path of this
example assignment depends on the local R installation.
assignment_path <-
file.path(system.file(package = "markmyassignment"), "extdata", "example_assignment01.yml")
set_assignment(assignment_path)
## Assignment set:
## Test assignment 01: An example of an assignment.
## The assignment contain the following (2) tasks:
## - task1
## - task2
Let us look at the tasks included in our example assignment. To check
which task that is included we use the function
show_tasks()
.
## [1] "task1" "task2"
In this example assignment, there are two tasks and also a mandatory requirement.
Mandatory requirement:
Store your name in the variable
my_name
.
task1:
Create a vector containing the values of π and e. The name of the vector
should be task1
.
task2:
Create a function that takes a numeric vector as the argument and
returns the sum of the first and last element. Name the function
task2
.
We start to solve this assignment by solving the first task.
## [1] 3.141593 2.718282
It seems to work as intended.
We now try to correct our lab assignment using
markmyassignment
:
## ✔ | F W S OK | Context
##
## ⠏ | 0 | mandatory-1
## ⠏ | 0 | Mandatory tests
## ⠋ | 1 0 | Mandatory tests
## ✖ | 1 0 | Mandatory tests
## ────────────────────────────────────────────────────────────────────────────────
## Failure ('test-mandatory-1.R:6:3'): Mandatory tests
## exists("my_name") is not TRUE
##
## `actual`: FALSE
## `expected`: TRUE
## Variable my_name is missing
## ────────────────────────────────────────────────────────────────────────────────
##
## ⠏ | 0 | task-1-subtask-1-tests
## ⠏ | 0 | task1
## ✔ | 3 | task1
##
## ⠏ | 0 | task-2-subtask-1-tests
## ⠏ | 0 | task2a
## ✖ | 2 0 | task2a
## ────────────────────────────────────────────────────────────────────────────────
## Failure ('test-task-2-subtask-1-tests.R:6:3'): Marking task2
## exists("task2") is not TRUE
##
## `actual`: FALSE
## `expected`: TRUE
## task2() does not exist.
##
## Error ('test-task-2-subtask-1-tests.R:7:3'): Marking task2
## Error in `eval(code, test_env)`: object 'task2' not found
## Backtrace:
## ▆
## 1. └─testthat::expect_is(task2, "function", info = "task2 is not a function.") at test-task-2-subtask-1-tests.R:7:3
## 2. └─testthat::quasi_label(enquo(object), label, arg = "object")
## 3. └─rlang::eval_bare(expr, quo_get_env(quo))
## ────────────────────────────────────────────────────────────────────────────────
##
## ⠏ | 0 | task-2-subtask-2-tests
## ⠏ | 0 | task2b
## ✖ | 1 0 | task2b
## ────────────────────────────────────────────────────────────────────────────────
## Error ('test-task-2-subtask-2-tests.R:6:3'): Mark even more on task2
## Error in `task2(5:10)`: could not find function "task2"
## Backtrace:
## ▆
## 1. └─testthat::expect_is(task2(5:10), "integer", info = "task2 don't return an integer.") at test-task-2-subtask-2-tests.R:6:3
## 2. └─testthat::quasi_label(enquo(object), label, arg = "object")
## 3. └─rlang::eval_bare(expr, quo_get_env(quo))
## ────────────────────────────────────────────────────────────────────────────────
##
## ══ Results ═════════════════════════════════════════════════════════════════════
## ── Failed tests ────────────────────────────────────────────────────────────────
## Failure ('test-mandatory-1.R:6:3'): Mandatory tests
## exists("my_name") is not TRUE
##
## `actual`: FALSE
## `expected`: TRUE
## Variable my_name is missing
##
## Failure ('test-task-2-subtask-1-tests.R:6:3'): Marking task2
## exists("task2") is not TRUE
##
## `actual`: FALSE
## `expected`: TRUE
## task2() does not exist.
##
## Error ('test-task-2-subtask-1-tests.R:7:3'): Marking task2
## Error in `eval(code, test_env)`: object 'task2' not found
## Backtrace:
## ▆
## 1. └─testthat::expect_is(task2, "function", info = "task2 is not a function.") at test-task-2-subtask-1-tests.R:7:3
## 2. └─testthat::quasi_label(enquo(object), label, arg = "object")
## 3. └─rlang::eval_bare(expr, quo_get_env(quo))
##
## Error ('test-task-2-subtask-2-tests.R:6:3'): Mark even more on task2
## Error in `task2(5:10)`: could not find function "task2"
## Backtrace:
## ▆
## 1. └─testthat::expect_is(task2(5:10), "integer", info = "task2 don't return an integer.") at test-task-2-subtask-2-tests.R:6:3
## 2. └─testthat::quasi_label(enquo(object), label, arg = "object")
## 3. └─rlang::eval_bare(expr, quo_get_env(quo))
##
## [ FAIL 4 | WARN 0 | SKIP 0 | PASS 3 ]
That did not work very well. This is how it looks when something goes
wrong when the markmyassignment
package is used. It looks
very daunting in this vignette, but using the function in R will result
in less output.
The first part of the message contains the names of the tests that are run and the number of OK, F(ailures) and W(arnings). You do not want any Failures and Warnings may be something wrong and should be checked.
The easiest way to go through the error messages is to start in a
chronological order. Start with the first error (error number 1) and
correct this error and then run the mark_my_assignment()
function again. Let’s look at the first error message:
test-2-1.R:6: failure: Marking task2
exists("task2") isn't true.
task2() does not exist.
The problem is that we tried to mark the second task, but we have not
yet tried to solve this part of the assignment. Let us correct only the
first task, using the tasks
argument in
mark_my_assignment()
:
## ✔ | F W S OK | Context
##
## ⠏ | 0 | mandatory-1
## ⠏ | 0 | Mandatory tests
## ✖ | 1 0 | Mandatory tests
## ────────────────────────────────────────────────────────────────────────────────
## Failure ('test-mandatory-1.R:6:3'): Mandatory tests
## exists("my_name") is not TRUE
##
## `actual`: FALSE
## `expected`: TRUE
## Variable my_name is missing
## ────────────────────────────────────────────────────────────────────────────────
##
## ⠏ | 0 | task-1-subtask-1-tests
## ⠏ | 0 | task1
## ✔ | 3 | task1
##
## ══ Results ═════════════════════════════════════════════════════════════════════
## ── Failed tests ────────────────────────────────────────────────────────────────
## Failure ('test-mandatory-1.R:6:3'): Mandatory tests
## exists("my_name") is not TRUE
##
## `actual`: FALSE
## `expected`: TRUE
## Variable my_name is missing
##
## [ FAIL 1 | WARN 0 | SKIP 0 | PASS 3 ]
That worked better! But we still get an error:
test-mandatory-1.R:6: failure: Mandatory tests
exists("my_name") isn't true.
Variable my_name is missing
We forgot to add the mandatory name. Let’s do that now:
Now it worked out! We have solved the first task. Let us try to solve the second task.
## [1] 6
It seems to work well at a first glance. Let us see what markmyassignment says.
## ✔ | F W S OK | Context
##
## ⠏ | 0 | mandatory-1
## ⠏ | 0 | Mandatory tests
## ✔ | 1 | Mandatory tests
##
## ⠏ | 0 | task-2-subtask-1-tests
## ⠏ | 0 | task2a
## ✔ | 3 | task2a
##
## ⠏ | 0 | task-2-subtask-2-tests
## ⠏ | 0 | task2b
## ✖ | 1 3 | task2b
## ────────────────────────────────────────────────────────────────────────────────
## Failure ('test-task-2-subtask-2-tests.R:8:3'): Mark even more on task2
## task2(vector = 5:10) not equal to 15.
## 1/1 mismatches
## [1] 14 - 15 == -1
## task2(vector=5:10) don't return 15
## ────────────────────────────────────────────────────────────────────────────────
##
## ══ Results ═════════════════════════════════════════════════════════════════════
## ── Failed tests ────────────────────────────────────────────────────────────────
## Failure ('test-task-2-subtask-2-tests.R:8:3'): Mark even more on task2
## task2(vector = 5:10) not equal to 15.
## 1/1 mismatches
## [1] 14 - 15 == -1
## task2(vector=5:10) don't return 15
##
## [ FAIL 1 | WARN 0 | SKIP 0 | PASS 7 ]
Oh! There seems to be an error in our function? Ah, the problem seems to be that we assumed a fixed length vector argument. Let us correct that and check our task again.
## ✔ | F W S OK | Context
##
## ⠏ | 0 | mandatory-1
## ⠏ | 0 | Mandatory tests
## ✔ | 1 | Mandatory tests
##
## ⠏ | 0 | task-2-subtask-1-tests
## ⠏ | 0 | task2a
## ✔ | 3 | task2a
##
## ⠏ | 0 | task-2-subtask-2-tests
## ⠏ | 0 | task2b
## ✔ | 4 | task2b
##
## ══ Results ═════════════════════════════════════════════════════════════════════
## [ FAIL 0 | WARN 0 | SKIP 0 | PASS 8 ]
We succeeded this time! Now all tasks are completed and we can now
use mark_my_assignment()
to correct the whole lab
assignment. Note that my_name
, task1
and
task2
need to exist in the global environment. To check
this, we can use ls()
.
## [1] "assignment_path" "my_name" "task1" "task2"
## [5] "x"
## ✔ | F W S OK | Context
##
## ⠏ | 0 | mandatory-1
## ⠏ | 0 | Mandatory tests
## ✔ | 1 | Mandatory tests
##
## ⠏ | 0 | task-1-subtask-1-tests
## ⠏ | 0 | task1
## ✔ | 3 | task1
##
## ⠏ | 0 | task-2-subtask-1-tests
## ⠏ | 0 | task2a
## ✔ | 3 | task2a
##
## ⠏ | 0 | task-2-subtask-2-tests
## ⠏ | 0 | task2b
## ✔ | 4 | task2b
##
## ══ Results ═════════════════════════════════════════════════════════════════════
## [ FAIL 0 | WARN 0 | SKIP 0 | PASS 11 ]
## You're a coding rockstar!
Yay! We have completed the whole lab assignment!
If we save our file now, we could even clean the global environment
and run the tests on the assignment file we will turn in. This is also
what the teacher will do when correcting the labs (but probably with
some extra tests). We need both the file path to our file and the
assignment path that we used to set_assignment()
:
mark_file <- file.path(system.file(package = "markmyassignment"), "extdata", "example_lab_file.R")
assignment_path <-
file.path(system.file(package = "markmyassignment"), "extdata", "example_assignment01.yml")
mark_my_file(mark_file = mark_file, assignment_path = assignment_path)
## ✔ | F W S OK | Context
##
## ⠏ | 0 | mandatory-1
## ⠏ | 0 | Mandatory tests
## ✔ | 1 | Mandatory tests
##
## ⠏ | 0 | task-1-subtask-1-tests
## ⠏ | 0 | task1
## ✔ | 3 | task1
##
## ⠏ | 0 | task-2-subtask-1-tests
## ⠏ | 0 | task2a
## ✔ | 3 | task2a
##
## ⠏ | 0 | task-2-subtask-2-tests
## ⠏ | 0 | task2b
## ✔ | 4 | task2b
##
## ══ Results ═════════════════════════════════════════════════════════════════════
## [ FAIL 0 | WARN 0 | SKIP 0 | PASS 11 ]
## Good work!
It is also possible to just specify the assignment path and then choose the assignment file as follows:
We could also check individual tasks in our file, similar to before:
## ✔ | F W S OK | Context
##
## ⠏ | 0 | mandatory-1
## ⠏ | 0 | Mandatory tests
## ✔ | 1 | Mandatory tests
##
## ⠏ | 0 | task-1-subtask-1-tests
## ⠏ | 0 | task1
## ✔ | 3 | task1
##
## ══ Results ═════════════════════════════════════════════════════════════════════
## [ FAIL 0 | WARN 0 | SKIP 0 | PASS 4 ]
Good luck! If you have any suggestions, comments or ideas feel free to add an issue at the package webpage!