A student’s introduction to markmyassignment

Mans Magnusson and Oscar Pettersson

2024-01-28

The markmyassignment package is a tool to easily get automatic feedback on your lab assignment before handing it in.

Installing the package

The easiest way to install the package in R is as follows:

install.packages("markmyassignment")

How to get help

All documentation of the package and the functionality can be found using:

help(package = "markmyassignment")

You can also get troubleshooting or answers to common questions here. Please add your own questions and solutions if you have any!

Usage

To use the package markmyassignment, you must first load it into your R session:

library(markmyassignment)

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().

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 \(\pi\) 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.

task1 <- c(pi, exp(1))
print(task1)
## [1] 3.141593 2.718282

It seems to work as intended.

We now try to correct our lab assignment using markmyassignment:

mark_my_assignment()
## ✔ | 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():

mark_my_assignment(tasks = "task1")
## ✔ | 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:

my_name <- "SkyNet"

Now it worked out! We have solved the first task. Let us try to solve the second task.

task2 <- function(vector){
  vector[1] + vector[5]
}
task2(1:5)
## [1] 6

It seems to work well at a first glance. Let us see what markmyassignment says.

mark_my_assignment(tasks = "task2")
## ✔ | 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.

task2 <- function(vector){
  vector[1] + vector[length(vector)]
}
mark_my_assignment(tasks = "task2")
## ✔ | 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().

ls()
## [1] "assignment_path" "my_name"         "task1"           "task2"          
## [5] "x"
mark_my_assignment()
## ✔ | 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 ]
## Everything's correct!

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 ]
## You're a coding rockstar!

It is also possible to just specify the assignment path and then choose the assignment file as follows:

mark_my_file(assignment_path = assignment_path)

We could also check individual tasks in our file, similar to before:

mark_my_file(tasks = "task1", 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
## 
## ══ 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!