Skip to main content

Chapter 7 Writing your own Exercises

Section 7.1 Adding an Exercise to the Exercise Bank

Subsection 7.1.1 Introduction

Although all of the Runestone books have a pretty good selection of exercises, you can never have too many exercises. We hope to crowd source an enormous collection of questions and problems in our Exercise database. You can help by adding custom exercises for your own course to the database! Once your exercise is in the database it is available for others to find using the search feature on the create assignment page.

Since the most common exercises are programming exercises let's look at the activecode directive in detail. Here is the full directive with every possible option. This is what you would see if you add an activecode exercise from the web interface. Let's not get bogged down in the details here, let's look at a much simpler example.

.. activecode:: uniqueid 'nocanvas': directives.flag,
:nopre: do not create an output component
:above: put the canvas above the code
:autorun: run this activecode as soon as the page is loaded
:autograde: typically unittest
:caption: caption under the active code
:include: invisibly include code from another activecode
:hidecode: Don:t show the editor initially
:language: python, html, javascript, java, python2, python3
:tour_1: audio tour track
:tour_2: audio tour track
:tour_3: audio tour track
:tour_4: audio tour track
:tour_5: audio tour track
:nocodelens: Do not show the codelens button
:coach: Show the codecoach button
:timelimit: set the time limit for this program
:stdin: : A file to simulate stdin (java, python2, python3)
:datafile: : A datafile for the program to read (java, python2, python3)
:sourcefile: : source files (java, python2, python3)
:available_files: : other additional files (java, python2, python3)

If this is a homework problem instead of an example in the text
then the assignment text should go here. The assignment text ends with
the line containing four tilde ~
~~~~
print("hello world")
====
print("Hidden code, such as unit tests come after the four = signs")

Let's make an exercise to have the student compute the sum of the first N numbers.

Checkpoint 7.1.1.

Write a Python function to sum the first N numbers starting with 0. So if N is 4 then your function should add 0 + 1 + 2 + 3

The source code for the above looks like this:

.. activecode:: sigcse_ex1
:language: python

Write a Python function to sum the first N numbers starting with 0. So if N is 4 then your function should add 0 + 1 + 2 + 3
~~~~
def sum_first_n(N):
# your code here

Indentation and whitespace is important. In the previous example, you will see that the optional directive :language: is indented by four spaces. from the previous line. It must be indented at least three spaces to line up with the β€œa” in activecode, but using 4 keeps it consistent with my own personal python indentation style. Everything else in the body of the directive must also be indented to match the indentation of the optional parameters.

You can enter this into a textbox by clicking on the β€œWrite” button under the Problems area.

After you enter the text you need to compile and render the text. You can get a preview of your question on the right of the page.

The first part of the directive body are the instructions for the student. You can use any valid restructured text (rst) in this part of the directive including embedded images. You separate the instructions from any code you want to give the student with ~~~~ thats four tildes. The next part of the body is any code you want to provide. Just put in your Python or other language as you would normally write it.

To keep an exercise page looking a bit neater you can substitute the .. actex:: directive for the .. activecode:: directive. Making that substitution gives us the following:

Checkpoint 7.1.2.

Write a Python function to sum the first N numbers starting with 0. So if N is 4 then your function should add 0 + 1 + 2 + 3

Subsection 7.1.2 Unit Testing exercises

Of course, as you have learned in the previous section, the real killer feature of these exercises is to be able to create your own unit tests, and have the grader autograde the assignment.

Let's expand our example to include some simple unittests. We can do this by adding a hidden block of code to our previous example that uses the standard Python unittest framework.

from unittest.gui import TestCaseGui

class myTests(TestCaseGui):

def testOne(self):
self.assertEqual(add(2,2),4,"A feedback string when the test fails")
self.assertAlmostEqual(add(2.0,3.0), 5.0, 1, "Try adding your parmeters")

myTests().main()

If you are not familiar with Python unittests they are pretty easy to write. You create your own class that is a subclass of TestCase, or in our work TestCaseGui so we get some graphical output. Your tests are all methods of the class and must start with the word β€œtest”. There are a host of assertXXXX functions that you can use. Check out the unittest documentation here 1 

The second important addition in this example is the :autograde: option. This will allow the grade to make use of your unittests and assign a grade automatically for this particular problem.

Checkpoint 7.1.3.

Write a Python function to sum the first N numbers starting with 0. So if N is 4 then your function should add 0 + 1 + 2 + 3

Let's try to add another test to the example above. This time we'll show the unittests in the active code window to make it easy for us to test. I strongly recommend you do this in a scratch activecode window when you are writing a question. It will save lots of frustrating wait time as you work out the details of your testing.

Let's try to add another test to the example above. This time we'll show the unittests in the active code window to make it easy for us to test. I strongly recommend you do this in a scratch activecode window when you are writing a question. It will save lots of frustrating wait time as you work out the details of your testing.

Checkpoint 7.1.4.

Write a Python function to sum the first N numbers starting with 0. So if N is 4 then your function should add 0 + 1 + 2 + 3

Subsection 7.1.3 Advanced activecode Options

  • include

    – This option lets you include other activecodes in the current

  • timelimit

    seconds – What to do when students create an infinite loop and lock up their browser? Just wait a bit, every run of Python has a built in time limit of 30 seconds. Some things might take longer than this, so if you know an example or assignment is going to take longer, then you can set a higher time limit with this option.

  • nocodelens

    – codelens is an awesome addition, but it does not work with very many libraries. This is part of the sandboxed security of the codelens server. The most common one to be aware of is the turtle module. If you are doing a turtle example or assigning a turtle problem then you should set this flag so the β€œShow Codelens” button is hidden.

Subsection 7.1.4 For languages outside the browser

  • language

    – As you saw earlier, Runestone supports Java, python2, and python3 in a sandboxed server environment environment.

If you choose any of the above, the code from the window is packaged up and set to a separate server for compilation and execution. There are a few options to activecode that only apply to these languages.

  • datafile

    You can provide an id to a datafile that will be sent along with your Java code

  • sourcefile

    You can provide additional source files that should be compiled along with the java file you upload.

  • available_files

    You can provide additional binary files to link into the final executable.

You have attempted of activities on this page.

Section 7.2 Multiple Choice Questions

Another common question type is the multiple choice question.

.. mchoice:: uniqueid
    :multiple_answers: boolean  [optional]
    :random: boolean [optional]
    :answer_a: possible answer  -- what follows _ is label
    :answer_b: possible answer
    :answer_c: possible answer
    :answer_d: possible answer
    :answer_e: possible answer
    :correct: letter of correct answer or list of correct answer letters (in case of multiple answe
    rs)
    :feedback_a: displayed if a is picked
    :feedback_b: displayed if b is picked
    :feedback_c: displayed if c is picked
    :feedback_d: displayed if d is picked
    :feedback_e: displayed if e is picked

    Question text   ...

Here is an example from a Data Structures class:

Checkpoint 7.2.1.

    3-1-1: Given the following sequence of stack operations, what is the top item on the stack when the sequence is complete?

    m = Stack()
    m.push('x')
    m.push('y')
    m.push('z')
    while not m.isEmpty():
       m.pop()
       m.pop()
    
  • 'x'

  • You may want to check out the docs for isEmpty

  • the stack is empty

  • There is an odd number of things on the stack but each time through the loop 2 things are popped.

  • an error will occur

  • Good Job.

  • 'z'

  • You may want to check out the docs for isEmpty

The source code is here:

.. mchoice:: stack_2
    :answer_a: 'x'
    :answer_b: the stack is empty
    :answer_c: an error will occur
    :answer_d: 'z'
    :correct: c
    :feedback_a: You may want to check out the docs for isEmpty
    :feedback_b: There is an odd number of things on the stack but each time through the loop 2 things are popped.
    :feedback_c: Good Job.
    :feedback_d: You may want to check out the docs for isEmpty

    Given the following sequence of stack operations, what is the top item on the stack when the sequence is complete?

    .. code-block:: python

        m = Stack()
        m.push('x')
        m.push('y')
        m.push('z')
        while not m.isEmpty():
            m.pop()
            m.pop()
You have attempted of activities on this page.

Section 7.3 Mixed-Up Code (Parsons)

Mixed-up code or Parsons problems provide the correct code to solve a problem, but the code is broken into blocks and mixed up.

   .. parsonsprob:: unqiue_problem_id_here
      :maxdist:
      :order:
      :language:
      :noindent:
      :adaptive:
      :numbered: left

      Instructions for the user. These can include a textual description of how to solve the problem. You must leave a blank line before this.
      -----
      def findmax(alist):
      =====
      if len(alist) == 0:
      return None
      =====
      curmax = alist[0]
      for item in alist:
      =====
      if item > curmax:
      =====
      curmax = item
      =====
      return curmax

Create a working program and then paste the code for it into the editor. Indent the code so that the left edge lines up with the options. Indent each line with 4 spaces beyond the previous line. Separate the blocks with β€œ=====” which must line up under the options. Put the instructions before the code after a blank line and then followed by β€œ----”.

Subsection 7.3.1 Parsons Options

Options are indented under the Parsons directive (under the p).

Table 7.3.1.
Option Description
maxdist The maximum number of distractors to use. They will be picked at random
order The order for the lines, they are displayed in a random order normally
language Specify the language: java, python
noindent Provide the indentation for the user by adding spaces to the left of the code
adaptive Provide help is the user is struggling and modify the difficulty of the problem based on the user's performance on the previous problem
numbered Show numbered labels to the left of the code if you add left or to the right when you add right

Subsection 7.3.2 Parsons Distractor Types

You can include distractor blocks in the problem. A distractor is code that isn't needed in a correct solution, such as code with a syntax error. Add a distractor block after the correct code block. Distractors can either be paired or unpaired. For paired distractors use #paired at end of the first line of code in the distractor block. For unpaired distractors add #distractor.

Subsection 7.3.3 Example with Paired Distractors

Here is an example with paired distractors from a data oriented intermediate programming course in Python.

Checkpoint 7.3.2.

Complete the function greater_dictionary. Given a dictionary d and an integer cutoff, return a dictionary that contains only the key-value pairs where they key is greater than or equal to cutoff.

The source code for this problem is shown here:

.. parsonsprob:: mt1dict1ex
:numbered: left

Complete the function greater_dictionary. Given a dictionary d and an integer cutoff, return a dictionary that contains only the key-value pairs where they key is greater than or equal to cutoff.
-----
def greater_dictionary(d, cutoff):
=====
def greater_dictionary(self, d, cutoff): #paired
=====
result = {}
=====
for key in d.keys():
=====
for key in range(d): #paired
=====
if key >= cutoff:
=====
if key > cutoff: #paired
=====
result[key] = d[key]
=====
d[key] = result[key] #paired
=====
return result

Subsection 7.3.4 Example with Unpaired Distractor

Here is an example with an unpaired distractor.

Checkpoint 7.3.3.

The following has the correct code to β€˜swap' the values in x and y (so that x ends up with y's initial value and y ends up with x's initial value), but the code is mixed up and contains <b>one extra block</b> which is not needed in a correct solution. Drag the needed blocks from the left into the correct order on the right. Check your solution by clicking on the <i>Check Me</i> button. You will be told if any of the blocks are in the wrong order or if you need to remove one or more blocks.

The source code for this problem is shown here.

.. parsonsprob:: 2_swapex
:noindent:

The following has the correct code to 'swap' the values in x and y (so that x ends up with y's initial value and y ends up with x's initial value), but the code is mixed up and contains <b>one extra block</b> which is not needed in a correct solution. Drag the needed blocks from the left into the correct order on the right. Check your solution by clicking on the <i>Check Me</i> button. You will be told if any of the blocks are in the wrong order or if you need to remove one or more blocks.
-----
int x = 3;
int y = 5;
int temp = 0;
=====
temp = x;
=====
x = y;
=====
y = temp;
=====
y = x; #distractor
You have attempted of activities on this page.

Section 7.4 Pro Tip – Test Your Exercises Locally

This is definitey an advanced topic If you are not comfortable with the command line you can definitely skip this section. But if you are at least somewhat comfortable with the command line and an editor then this can save you a bunch of time! The workflow is simple, edit your questions locally, then paste them into the interface once you have them working and debugged.

Here is a short video that demonstrates how to install the runestone command in under two minutes.

Here are the steps I go through in the video. This assumes you already have python installed on your system.

  1. virtualenv runestone - Creates a python virtual environment so you don't need administrator privileges to install the rest of what you need. If virtualenv is not installed you should do pip install virtualenv (Python 2.7.x) or python -m venv runestone (Python > 3.5). If the pip command is not found then you should upgrade your version of Python to something newer. All modern version of Python come with pip pre-installed.

  2. . runestone/bin/activate – this activates the virtual environment you created in the previous step.

  3. mkdir assignments – Create an assignment to do your runestone work in.

  4. cd assignments – Make assignments your working directory.

  5. runestone init – Create a new project with some template directives

  6. runestone build – Convert the restructuredText to html

  7. runestone serve – start a simple web server so you can access your new project in your browser at http://localhost:8000/index.html

Once the works you can simply edit the index.rst file created by runestone init, and run runestone build again to check your work.

You have attempted of activities on this page.

Section 7.5 Add your own Question

In the final exercise for the night, add your own question to the exercise bank using the web interface.

You can make it any kind of question you would like, but it would be great if it was a real exercise that you like to use in one of your own courses.

To keep the build system from bogging down, you can let me know once you have saved an exercise and I'll pull it into the sigcse2019 book and build it for you.

Warning 7.5.1.

Make sure you assign at least 0 points to your question before you press done. If everything works, you will get a popup telling you that the question has been added successfully.

You have attempted of activities on this page.

Exercises 7.6 Exercises

this page left intentionally blank

You have attempted of activities on this page.
https://docs.python.org/2/library/unittest.html#assert-methods