Author: Mans Magnusson and Oscar Pettersson
Date: 2025-11-22

# A student’s introduction to markmyassignment

#### Mans Magnusson and Oscar Pettersson

#### 2025-11-22

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
    ## ────────────────────────────────────────────────────────────────────────────────
    ## Failure ('test-mandatory-1.R:6:3'): Mandatory tests
    ## Expected `exists("my_name")` to be TRUE.
    ## Differences:
    ## `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
    ## Expected `exists("task2")` to be TRUE.
    ## Differences:
    ## `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)
    ##  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)
    ##  3.     └─rlang::eval_bare(expr, quo_get_env(quo))
    ## ────────────────────────────────────────────────────────────────────────────────
    ##
    ## ══ Results ═════════════════════════════════════════════════════════════════════
    ## ── Failed tests ────────────────────────────────────────────────────────────────
    ## Failure ('test-mandatory-1.R:6:3'): Mandatory tests
    ## Expected `exists("my_name")` to be TRUE.
    ## Differences:
    ## `actual`:   FALSE
    ## `expected`: TRUE
    ##
    ## Variable my_name is missing
    ##
    ## Failure ('test-task-2-subtask-1-tests.R:6:3'): Marking task2
    ## Expected `exists("task2")` to be TRUE.
    ## Differences:
    ## `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)
    ##  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)
    ##  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
    ## Expected `exists("my_name")` to be TRUE.
    ## Differences:
    ## `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
    ## Expected `exists("my_name")` to be TRUE.
    ## Differences:
    ## `actual`:   FALSE
    ## `expected`: TRUE
    ##
    ## Variable my_name is missing
    ##
    ## [ FAIL 1 | WARN 0 | SKIP 0 | PASS 3 ]
    ##
    ## Don't worry, you'll get it.

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
    ## Expected `task2(vector = 5:10)` to equal 15.
    ## Differences:
    ## 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
    ## Expected `task2(vector = 5:10)` to equal 15.
    ## Differences:
    ## 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 ]
    ##
    ## Way to go!

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 ]
    ## Yay! All done!

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:

    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!