Linux中國

用 Jupyter Notebook 教 Python

關於 Ruby 社區的一些事情一直讓我印象深刻,其中兩個例子是對測試的承諾和對易於上手的強調。這兩方面最好的例子是 Ruby Koans,在這裡你可以通過修複測試來學習 Ruby。

要是我們能把這些神奇的工具也用於 Python,我們應該可以做得更好。是的,使用 Jupyter NotebookPyHamcrest,再加上一點類似於膠帶的粘合代碼,我們可以做出一個包括教學、可工作的代碼和需要修復的代碼的教程。

首先,需要一些「膠布」。通常,你會使用一些漂亮的命令行測試器來做測試,比如 pytestvirtue。通常,你甚至不會直接運行它。你使用像 toxnox 這樣的工具來運行它。然而,對於 Jupyter 來說,你需要寫一小段粘合代碼,可以直接在其中運行測試。

幸運的是,這個代碼又短又簡單:

import unittest

def run_test(klass):
    suite = unittest.TestLoader().loadTestsFromTestCase(klass)
    unittest.TextTestRunner(verbosity=2).run(suite)
    return klass

現在,裝備已經就緒,可以進行第一次練習了。

在教學中,從一個簡單的練習開始,建立信心總是一個好主意。

那麼,讓我們來修復一個非常簡單的測試:

@run_test
class TestNumbers(unittest.TestCase):

    def test_equality(self):
        expected_value = 3 # 只改這一行
        self.assertEqual(1+1, expected_value)
    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.002s

    FAILED (failures=1)

「只改這一行」 對學生來說是一個有用的標記。它準確地表明了需要修改的內容。否則,學生可以通過將第一行改為 return 來修複測試。

在這種情況下,修復很容易:

@run_test
class TestNumbers(unittest.TestCase):

    def test_equality(self):
        expected_value = 2 # 修復後的代碼行
        self.assertEqual(1+1, expected_value)
    test_equality (__main__.TestNumbers) ... ok

    -------------------------------------------------------------------    Ran 1 test in 0.002s

    OK

然而,很快,unittest 庫的原生斷言將被證明是不夠的。在 pytest 中,通過重寫 assert 中的位元組碼來解決這個問題,使其具有神奇的屬性和各種啟發式方法。但這在 Jupyter notebook 中就不容易實現了。是時候挖出一個好的斷言庫了:PyHamcrest。

from hamcrest import *
@run_test
class TestList(unittest.TestCase):

    def test_equality(self):
        things = [1,
                  5, # 只改這一行
                  3]
        assert_that(things, has_items(1, 2, 3))
    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.004s

    FAILED (failures=1)

PyHamcrest 不僅擅長靈活的斷言,它還擅長清晰的錯誤信息。正因為如此,問題就顯而易見了。[1, 5, 3] 不包含 2,而且看起來很醜:

@run_test
class TestList(unittest.TestCase):

    def test_equality(self):
        things = [1,
                  2, # 改完的行
                  3]
        assert_that(things, has_items(1, 2, 3))
    test_equality (__main__.TestList) ... ok

    -------------------------------------------------------------------    Ran 1 test in 0.001s

    OK

使用 Jupyter、PyHamcrest 和一點測試的粘合代碼,你可以教授任何適用於單元測試的 Python 主題。

例如,下面可以幫助展示 Python 從字元串中去掉空白的不同方法之間的差異。

source_string = "  hello world  "

@run_test
class TestList(unittest.TestCase):

    # 這是個贈品:它可以工作!
    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 # 只改這一行
        assert_that(result,
                   all_of(starts_with("hello"), ends_with("world  ")))

    def test_end_strip(self):
        result = source_string # 只改這一行
        assert_that(result,
                   all_of(starts_with("  hello"), ends_with("world")))
    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 &apos;  hello&apos; and a string ending with &apos;world&apos;)
         but: a string ending with &apos;world&apos; was &apos;  hello world  &apos;

    ======================================================================
    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 &apos;hello&apos; and a string ending with &apos;world  &apos;)
         but: a string starting with &apos;hello&apos; was &apos;  hello world  &apos;

    -------------------------------------------------------------------    Ran 3 tests in 0.006s

    FAILED (failures=2)

理想情況下,學生們會意識到 .lstrip().rstrip() 這兩個方法可以滿足他們的需要。但如果他們不這樣做,而是試圖到處使用 .strip() 的話:

source_string = "  hello world  "

@run_test
class TestList(unittest.TestCase):

    # 這是個贈品:它可以工作!
    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.strip() # 改完的行
        assert_that(result,
                   all_of(starts_with("hello"), ends_with("world  ")))

    def test_end_strip(self):
        result = source_string.strip() # 改完的行
        assert_that(result,
                   all_of(starts_with("  hello"), ends_with("world")))
    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 &apos;  hello&apos; and a string ending with &apos;world&apos;)
         but: a string starting with &apos;  hello&apos; was &apos;hello world&apos;

    ======================================================================
    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 &apos;hello&apos; and a string ending with &apos;world  &apos;)
         but: a string ending with &apos;world  &apos; was &apos;hello world&apos;

    -------------------------------------------------------------------    Ran 3 tests in 0.007s

    FAILED (failures=2)

他們會得到一個不同的錯誤信息,顯示去除了過多的空白:

source_string = "  hello world  "

@run_test
class TestList(unittest.TestCase):

    # 這是個贈品:它可以工作!
    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")))
    test_complete_strip (__main__.TestList) ... ok
    test_end_strip (__main__.TestList) ... ok
    test_start_strip (__main__.TestList) ... ok

    -------------------------------------------------------------------    Ran 3 tests in 0.005s

    OK

在一個比較真實的教程中,會有更多的例子和更多的解釋。這種使用 Jupyter Notebook 的技巧,有的例子可以用,有的例子需要修正,可以用於實時教學,可以用於視頻課,甚至,可以用更多的其它零散用途,讓學生自己完成一個教程。

現在就去分享你的知識吧!

via: https://opensource.com/article/20/9/teach-python-jupyter

作者:Moshe Zadka 選題:lujun9972 譯者:wxy 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國