3

I'm trying to set up a grading script for an intro CS class using unittest. Essentially, students submit a python file student.py, which has some number of functions in it that are generally interdependent (meaning that func3() may use func1() in it's computations).

I'm writing unit tests for each method by comparing the output of student.func1 to the output of correct.func1, a method that's known to be the correct implementation (from a file correct.py).

As an example, say that func2 uses func1 in it's computation. So, either by default or upon student.func1 failing some test, I want to overwrite student.func1 with correct.func1, so student.func2 uses a known correct implementation (and therefore isn't just wrong by default). How could I go about doing that? It seems like the setUp() and tearDown() are similar to what I want, but I don't know how to "unimport" modules in python, and haven't found any resources about it so far.

I'm interested in both the case where student.py contains classes, and func1,func2 are methods of a specific class, and when func1 and func2 are just defined generically within student.py.

Mark
  • 324
  • 3
  • 9
  • I'd argue that replacing wrong functions with correct ones is a bad move in the first place. If `func2` relies on `func1` and `func1` is broken in a way that breaks `func2`, then the students should have noticed that `func2` was broken in testing. Similarly, if they misinterpreted the spec for `func1` and noticed it too late, but all their other functions work with the incorrect `func1` behavior, they shouldn't get points docked on everything because you swapped out their `func1` for something their other functions didn't expect. – user2357112 Mar 07 '17 at 23:47
  • @user2357112 I agree it's not a perfect idea, I just thought of doing it as our current debugging method for finding errors is to replace broken code with known working code until we find out what of theirs didn't work. Additionally, this script isn't for assigning grades at all, only to assist with hand grading (so if they pass all tests, there's a good indication everything is perfect, if only `func7` fails, maybe look there first, etc.) – Mark Mar 07 '17 at 23:51

1 Answers1

3

The easiest way would be to import student into your module, then catch an AssertionError if a test fails, and replace the bad code inside the student module with your own good code:

import student

import unittest

def safe_f1():
    print("Safe f1")
    return 1

class TestSomething(unittest.TestCase):

    def test_f1(self):
        try:
            self.assertEqual(student.func1(), 1)
        except AssertionError:
            student.func1 = safe_f1
            raise


    def test_f2(self):
        self.assertEqual(student.func2(), 2)

Here's a dummy student.py that fails/works:

def func1():
    print("Bad f1")
    return 2

def func2():
    return func1() + 1
    return 2

When I run this, I get:

$ python -m unittest test.py
Bad f1
FSafe f1
.
======================================================================
FAIL: test_f1 (test.TestSomething)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/austin/Code/so/test.py", line 13, in test_f1
    self.assertEqual(student.func1(), 1)
AssertionError: 2 != 1

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
aghast
  • 14,785
  • 3
  • 24
  • 56
  • Doesn't work; the other functions will still use the original `func1`. – user2357112 Mar 07 '17 at 23:48
  • You'd have to `import student` and `student.func1 = correct.func1`. – Aran-Fey Mar 07 '17 at 23:51
  • Yeah, you're right. I glossed over the part where it's the student code calling func1. – aghast Mar 07 '17 at 23:54
  • Okay, better version. – aghast Mar 07 '17 at 23:59
  • Will the `raise`'d exception still be logged by `unittest`? – Mark Mar 08 '17 at 00:03
  • 1
    I don't think unittest actually makes any guarantees about test execution order, which this seems to rely on. – user2357112 Mar 08 '17 at 00:03
  • @user2357112 although it appears I could make them a [TestSuite](https://stackoverflow.com/questions/5387299/python-unittest-testcase-execution-order) potentially. – Mark Mar 08 '17 at 00:06
  • Per [the docs](https://docs.python.org/3/library/unittest.html?highlight=unittest#organizing-test-code), "The order in which the various tests will be run is determined by sorting the test method names with respect to the built-in ordering for strings." – aghast Mar 08 '17 at 00:07
  • @AustinHastings: Huh, so it's actually documented. Weird decision. Notably, it's not declaration order, and `test_f10` comes before `test_f2`. – user2357112 Mar 08 '17 at 00:12
  • There are some order-specifying approaches in [this question](http://stackoverflow.com/questions/5387299/python-unittest-testcase-execution-order?noredirect=1&lq=1) – aghast Mar 08 '17 at 00:19