With Jupyter, PyHamcrest, and a little bit of testing code strung together, you can teach any Python content suitable for unit testing.
There are a few things that have always struck me about the Ruby community, two examples of which are the commitment to testing and the emphasis on ease of use. The best example of both is Ruby Koans, where you can learn Ruby by fixing tests.
We could do better if we could apply these amazing tools to Python as well. Yes, using Jupyter Notebook, PyHamcrest, and a bit of sticky code like tape, we can make a tutorial that includes teaching, code that works, and code that needs fixing.
First, you need some tape. Often, you use some nifty command-line tester to do your tests, such as PyTest or Virtue. Often, you won’t even run it directly. You use tools like TOX or nox to run it. For Jupyter, however, you need to write a small piece of glue code that you can run tests in directly.
Fortunately, the code is short and simple:
import unittest
def run_test(klass):
suite = unittest.TestLoader().loadTestsFromTestCase(klass)
unittest.TextTestRunner(verbosity=2).run(suite)
return klass
Copy the code
Now, the gear is ready for the first practice.
In teaching, it’s always a good idea to start with a simple exercise to build confidence.
So, let’s fix a very simple test:
@run_test class TestNumbers(unittest.TestCase): def test_equality(self): AssertEqual (1+1, expected_value) self.assertEqual(1+1, expected_value)Copy the code
test_equality (__main__.TestNumbers) ... FAIL ====================================================================== FAIL: test_equality (__main__.TestNumbers) ---------------------------------------------------------------------- Traceback (most recent call last): File "<ipython-input-7-5ebe25bc00f3>", line 6, in test_equality self.assertEqual(1+1, expected_value) AssertionError: 2! = 3 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Ran 1 test in 0.002 s FAILED (failures = 1)Copy the code
“Change only one line” is a useful marker for students. It shows exactly what needs to be changed. Otherwise, students can fix the test by changing the first line to RETURN.
In this case, the fix is easy:
@run_test class TestNumbers(unittest.TestCase): def test_equality(self): AssertEqual (1+1, EXPECted_value)Copy the code
test_equality (__main__.TestNumbers) ... Ok -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Ran 1 test in 0.002 s okCopy the code
Soon, however, the unitTest library’s native assertions will prove inadequate. In PyTest, this problem was solved by overwriting the bytecodes in Assert with magical properties and various heuristics. That’s not so easy with Jupyter Notebook. It’s time to dig up a good assertion library: PyHamcrest.
from hamcrest import * @run_test class TestList(unittest.TestCase): def test_equality(self): Assert_that (things, has_items(1, 2, 3))Copy the code
test_equality (__main__.TestList) ... FAIL ====================================================================== FAIL: test_equality (__main__.TestList) ---------------------------------------------------------------------- Traceback (most recent call last): File "<ipython-input-11-96c91225ee7d>", line 8, in test_equality assert_that(things, has_items(1, 2, 3)) AssertionError: Expected: (a sequence containing <1> and a sequence containing <2> and a sequence containing <3>) but: a sequence containing <2> was <[1, 5, 3] > -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Ran 1 test in 0.004 s FAILED (failures = 1)Copy the code
PyHamcrest is not only good at flexible assertions, it is also good at clear error messages. Because of this, the problem is obvious. [1, 5, 3] contains no 2 and looks ugly:
@run_test class TestList(unittest.TestCase): def test_equality(self): Assert_that (things, has_items(1, 2, 3))Copy the code
test_equality (__main__.TestList) ... Ok -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Ran 1 test in 0.001 s okCopy the code
Using Jupyter, PyHamcrest, and the glue code for a bit of testing, you can teach any Python topic suitable for unit testing.
For example, the following can help show the differences between Python’s different methods of removing whitespace from strings.
Source_string = "hello world" @run_test class TestList(unittest.testCase): # This is a freebie: it works! def test_complete_strip(self): result = source_string.strip() assert_that(result, all_of(starts_with("hello"), ends_with("world"))) def test_start_strip(self): Assert_that (result, all_of(starts_with("hello")), ends_with("world "))) def test_end_strip(self): Assert_that (result, all_of(starts_with(" hello"), ends_with("world")))Copy the code
test_complete_strip (__main__.TestList) ... ok test_end_strip (__main__.TestList) ... FAIL test_start_strip (__main__.TestList) ... FAIL ====================================================================== FAIL: test_end_strip (__main__.TestList) ---------------------------------------------------------------------- Traceback (most recent call last): File "<ipython-input-16-3db7465bd5bf>", line 19, in test_end_strip assert_that(result, AssertionError: Expected: (a string starting with ' hello' and a string ending with 'world') but: a string ending with 'world' was ' hello world ' ====================================================================== FAIL: test_start_strip (__main__.TestList) ---------------------------------------------------------------------- Traceback (most recent call last): File "<ipython-input-16-3db7465bd5bf>", line 14, in test_start_strip assert_that(result, AssertionError: Expected: (a string starting with 'hello' and a string ending with 'world ') but: a string starting with 'hello' was ' hello world ' -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Ran three tests in 0.006 s FAILED (failures = 2)Copy the code
Ideally, students will realize that the.lstrip() and.rstrip() methods will meet their needs. But if they don’t do that and try to use.strip() everywhere:
Source_string = "hello world" @run_test class TestList(unittest.testCase): # This is a freebie: it works! def test_complete_strip(self): result = source_string.strip() assert_that(result, all_of(starts_with("hello"), ends_with("world"))) def test_start_strip(self): Assert_that (result, all_of(starts_with("hello")), ends_with("world "))) def test_end_strip(self): Assert_that (result, all_of(starts_with(" hello"), ends_with("world")))Copy the code
test_complete_strip (__main__.TestList) ... ok test_end_strip (__main__.TestList) ... FAIL test_start_strip (__main__.TestList) ... FAIL ====================================================================== FAIL: test_end_strip (__main__.TestList) ---------------------------------------------------------------------- Traceback (most recent call last): File "<ipython-input-17-6f9cfa1a997f>", line 19, in test_end_strip assert_that(result, AssertionError: Expected: (a string starting with ' hello' and a string ending with 'world') but: a string starting with ' hello' was 'hello world' ====================================================================== FAIL: test_start_strip (__main__.TestList) ---------------------------------------------------------------------- Traceback (most recent call last): File "<ipython-input-17-6f9cfa1a997f>", line 14, in test_start_strip assert_that(result, AssertionError: Expected: (a string starting with 'hello' and a string ending with 'world ') but: a string ending with 'world ' was 'hello world' ---------------------------------------------------------------------- Ran 3 tests in 0.007s FAILED (Failures =2)Copy the code
They get a different error message that says too much white space has been removed:
Source_string = "hello world" @run_test class TestList(unittest.testCase): # This is a freebie: it works! def test_complete_strip(self): result = source_string.strip() assert_that(result, all_of(starts_with("hello"), ends_with("world"))) def test_start_strip(self): result = source_string.lstrip() # Fixed this line assert_that(result, all_of(starts_with("hello"), ends_with("world "))) def test_end_strip(self): result = source_string.rstrip() # Fixed this line assert_that(result, all_of(starts_with(" hello"), ends_with("world")))Copy the code
test_complete_strip (__main__.TestList) ... ok test_end_strip (__main__.TestList) ... ok test_start_strip (__main__.TestList) ... Ok -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Ran three tests in 0.005 s okCopy the code
In a more realistic tutorial, there are more examples and more explanations. The technique of using Jupyter Notebook, which can be used in some examples or others that need fixing, can be used for real-time teaching, video lessons, or even, for many other odd uses, for students to complete a tutorial themselves.
Go share your knowledge now!
Via: opensource.com/article/20/…
By Moshe Zadka, Author: Lujun9972
This article is originally compiled by LCTT and released in Linux China