test_object_after_expression

test_object_after_expression(name, extra_env=None, context_vals=None, undefined_msg=None, incorrect_msg=None, eq_condition='equal', expr_code=None, pre_code=None, keep_objs_in_env=None, state=None)

Test object after expression.

The code of the student is ran in the active state and the the value of the given object is compared with the value of that object in the solution. This can be used in nested pythonwhat calls like test_for_loop. In these kind of calls, the code of the active state is set to the code in a part of the sub statement (e.g. the body of a for loop). It has various parameters to control the execution of the (sub)expression. This test function is ideal to check if a value is updated correctly in the body of a for loop.

Parameters:
  • name (str) – the name of the object which value has to be checked after evaluation of the expression.
  • extra_env (dict) – set variables to the extra environment. They will update the student and solution environment in the active state before the student/solution code in the active state is ran. This argument should contain a dictionary with the keys the names of the variables you want to set, and the values are the values of these variables.
  • context_vals (list) – set variables which are bound in a for loop to certain values. This argument is only useful if you use the function in a test_for_loop or test_function_definition. It contains a list with the values of the bound variables.
  • incorrect_msg (str) – feedback message if the value of the object in the solution environment doesn’t match the one in the student environment. This feedback message will be expanded if it is used in the context of another test function, like test_for_loop.
  • eq_condition (str) – how objects are compared. Currently, only “equal” is supported, meaning that the resulting objects in student and solution process should have exactly the same value.
  • expr_code (str) – if this variable is not None, the expression in the studeont/solution code will not be ran. Instead, the given piece of code will be ran in the student as well as the solution environment and the result will be compared.
  • pre_code (str) – the code in string form that should be executed before the expression is executed. This is the ideal place to set a random seed, for example.
  • keep_obj_in_env (list()) – a list of variable names that should be hold in the copied environment where the expression is evaluated. All primitive types are copied automatically, other objects have to be passed explicitely.
Example:

Student code:

count = 1
for i in range(100):
    count = count + i

Solution code:

count = 15
for n in range(30):
    count = count + n

SCT:

test_for_loop(1,
    body = test_object_after_expression("count",
            extra_env = { 'count': 20 },
            contex_vals = [ 10 ])

This SCT will pass as the value of count is updated identically in the body of the for loop in the student code and solution code.

test_object_after_expression(name,
                             extra_env=None,
                             context_vals=None,
                             undefined_msg=None,
                             incorrect_msg=None,
                             eq_condition="equal",
                             pre_code=None,
                             keep_objs_in_env=None)

test_object_after_expression() is a function that is primarily used to check the correctness of the body of control statements. Through extra_env and context_vals you can adapt the student/solution environment with manual elements. Next, the ‘currently active expression tree’, such as the body of a for loop, is executed, and the resulting environment is inspected. This is done for both the student and the solution code, and afterwards the value of the object that you specify in name is checked for equality. With pre_code, you can prepend the execution of the default expression tree with some extra code, for example to set some variables.

Example 1: Function defintion

Suppose you want to student to code up a function shout(), that adds three exclamation marks to every word you pass it:

*** =solution
```{python}
def shout(word):
    shout_word = word + '!!!'
    return shout_word
```

To test whether the student did this appropriately, you want to first test whether shout is a user-defined function, and then whether inside the function, a new variable shout_word is created. Finally, you also want to check whether the result of calling shout('hello') is correct. The following SCT will do that for us:

*** =sct
```{python}
test_function_definition('shout',
                         body = test_object_after_expression('shout_word', context_vals = ['anything']),
                         results = [('hello')])
```

Let’s focus on the body argument of test_function_definition() here, that uses test_object_after_expression(). For the other elements, refer to the test_function_definition() article.

The first argument of test_object_after_expression() tells the system to check the value of shout_word after executing the body of the function definition. Which part of the code to execute, the ‘expression’, is implicitly specified by pythonwhat. However, to run correctly, this expression has to know what word is. You can specify a value of word through the context_vals argument. It’s a simple list: the first element of the list will be the value for the first argument of the function definition, the second element of the list will be the value for the second argument of the list, and so on. Here, there’s only one argument, so a list with a single element, a string (that will be the value of the word variable), suffises.

test_object_after_expression() will execute the expression, and run it on the solution side and the student side. On the solution side, the value of shout_word after the execution will be 'anything!!!'. If the value on the student code is the same, we can rest assured that shout_word has been appropriately defined by the student and the test passes.

Example 2: for loop

Suppose you want the student to build up a dictionary of word counts based on a list, as follows:

*** =solution
```{python}
words = ['it', 'is', 'a', 'the', 'is', 'the', 'a', 'the', 'it']
counts = {}
for word in words:
    if word in counts:
        counts[word] += 1
    else:
        counts[word] = 1
```

To check whether the counts list was correctly built, you can simply use test_object(), but you can also go deeper if it goes wrong. This calls for a test_correct() in combination with test_object() and test_for(), that in its turn uses test_object_after_expression():

``` =solution
def check_test():
    test_object('counts')

def diagnose_test():
    body_test():
        test_object_after_expression('counts',
                                     extra_env = {'counts': {'it': 1}},
                                     context_vals = ['it'])
        test_object_after_expression('counts',
                                     extra_env = {'counts': {'it': 1}},
                                     context_vals = ['is'])
    test_for_loop(index = 1,
                  test = test_expression_result(), # Check if correct iterable used
                  body = body_test)

test_correct(check_test, diagnose_test)
```

Let’s focus on the body_test for the for loop. Here, we’re using test_object_after_expression() twice.

In the first function call, we override the environment so that counts is a dictionary with a single key and value. Also, the context value, word in this case (the iterator of the for loop), is set to it. In this case, the body of the for loop - making abstraction of the if-else test - should increment the value of the value, without adding a new key.

In the second function call, we override the environment so that counts is again a dictionary with a single key and value. This time, the context value is set to is, so a value that is not yet in the counts dictionary, so this should lead to a counts dictionary with two elements.

As in the first example, test_object_after_expression() sets the environment variables and context values, runs the expression (in this case the entire body of the for loop), and then inspects the value of counts after this expression. The combination of the two test_object_after_expression() calls here, will indirectly check whether both the if and else part of the body has been correctly implemented.