数弱の文系大学生によるブログです。

Python、Webデザイン関連の記事を投稿していけたらなと思っています。

備忘録的なブログなので、読みにくい文章だと思います。ご了承ください。

Python3の復習(コードのテスト)

こんにちは。

かにたまごです。一週間ぶりくらいかな。

唐突ですが、Python3の復習は今回の記事で一旦終了したいと思います。
参考にしている本を大体復習し終わったので、数学や統計、アプリの開発等に力を入れます('ω')ノ


さて、今回は

  • コードのテスト

について、復習していきたいと思います。


今までのPython3 復習履歴は以下の通り。

  1. 数値・文字列
  2. リスト・タプル
  3. 辞書・集合
  4. if文・while文・for文
  5. 内包表記
  6. 関数
  7. ジェネレータ
  8. デコレータ
  9. 名前空間とスコープ
  10. アンダーバー・アンダースコアの意味
  11. エラー処理:try・except
  12. モジュール・パッケージ
  13. 標準ライブラリ
  14. オブジェクトとクラス①-クラスの定義-
  15. オブジェクトとクラス②-継承-
  16. プロパティ
  17. ポリモーフィズムとダックタイピング
  18. 特殊メソッドとその他諸々
  19. Unicodeとエンコード・デコード
  20. 書式指定
  21. 正規表現
  22. バイナリデータ
  23. ファイル

コードのテスト

一番簡単なプログラムのテスト方法は print() で確認する方法だが、その他にもPythonのライブラリを介して様々なプログラムのテストを行うことができる。

  • pylintによるコードチェック

Pythonでは、一般的にPEP8というコード規約が使用されている。
この規約に準じたコードであるかどうかをチェックをしてくれるのが pylint である。

文法だけじゃなく、表記もチェックしてくれるらしい・・・。便利。


さっそく試してみる。

まずは、pipコマンドでpylintをインストール。

pip install pylint


そしてチェックするコードを書く。
今回はf(x) = x ** n と f(x) の導関数をプロットするコード(言葉の使い方合ってるかな)。

import matplotlib.pyplot as plt
import numpy as np

def main():
    plot(3)

def f(x, idx):
    y = x ** idx
    return y

def f_dash(x, idx):
    y_ = (idx * x) ** (idx-1)
    return y_

def plot(idx):
    x = np.linspace(-30, 30, 100)
    y = f(x, idx)
    y_ = f_dash(x, idx)
    fig, axes = plt.subplots(1, 2, figsize=(10, 8))
    axes[0].plot(x, y, c="indianred")
    axes[0].hlines(0, -25, 25)
    axes[0].vlines(0, min(y), max(y))
    axes[0].set_xlabel("X")
    axes[0].set_ylabel("Y", rotation=0)
    axes[0].set_title("f (x) = x ** {}".format(idx), fontsize=15)
    axes[1].plot(x, y_, c="indianred")
    axes[1].hlines(0, -25, 25)
    axes[1].vlines(0, min(y_), max(y_))
    axes[1].set_xlabel("X")
    axes[1].set_title("f ' (x) = {}x ** {}".format(idx, idx-1), fontsize=15)
    plt.suptitle("Differential Plot", fontsize=20)
    plt.show()

if __name__ == "__main__":
    main()

普通に実行するとこんな感じ。

C:\Users\***\Desktop\Python\Test>python differential.py


f(x)とf'(x)のプロット
Differential Plot


pylintでチェックするには、コマンドプロンプトで以下のようにファイルを実行する。

C:\Users\***\Desktop\Python\Test>pylint differential.py

すると、以下のように出力された。

************* Module differential
differential.py:1:0: C0111: Missing module docstring (missing-docstring)
differential.py:4:0: C0111: Missing function docstring (missing-docstring)
differential.py:7:0: C0103: Function name "f" doesn't conform to snake_case naming style (invalid-name)
differential.py:7:0: C0103: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:7:0: C0111: Missing function docstring (missing-docstring)
differential.py:8:4: C0103: Variable name "y" doesn't conform to snake_case naming style (invalid-name)
differential.py:11:0: C0103: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:11:0: C0111: Missing function docstring (missing-docstring)
differential.py:12:4: C0103: Variable name "y_" doesn't conform to snake_case naming style (invalid-name)
differential.py:15:0: C0111: Missing function docstring (missing-docstring)
differential.py:16:4: C0103: Variable name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:17:4: C0103: Variable name "y" doesn't conform to snake_case naming style (invalid-name)
differential.py:18:4: C0103: Variable name "y_" doesn't conform to snake_case naming style (invalid-name)
differential.py:19:4: W0612: Unused variable 'fig' (unused-variable)

------------------------------------------------------------------
Your code has been rated at 4.81/10 (previous run: 4.81/10, +0.00)


とりあえず最後の部分に注目すると

Your code has been rated at 4.81/10 (previous run: 4.81/10, +0.00)


「お前のコードの評価はmax10のうち4.81だ」


と言われてしまっている・・・。悲しい。


その理由が上の

differential.py:1:0: C0111: Missing module docstring (missing-docstring)
differential.py:4:0: C0111: Missing function docstring (missing-docstring)
differential.py:7:0: C0103: Function name "f" doesn't conform to snake_case naming style (invalid-name)
differential.py:7:0: C0103: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:7:0: C0111: Missing function docstring (missing-docstring)
differential.py:8:4: C0103: Variable name "y" doesn't conform to snake_case naming style (invalid-name)
differential.py:11:0: C0103: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:11:0: C0111: Missing function docstring (missing-docstring)
differential.py:12:4: C0103: Variable name "y_" doesn't conform to snake_case naming style (invalid-name)
differential.py:15:0: C0111: Missing function docstring (missing-docstring)
differential.py:16:4: C0103: Variable name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:17:4: C0103: Variable name "y" doesn't conform to snake_case naming style (invalid-name)
differential.py:18:4: C0103: Variable name "y_" doesn't conform to snake_case naming style (invalid-name)
differential.py:19:4: W0612: Unused variable 'fig' (unused-variable)

この部分。


めちゃくちゃ言われてんな・・・って思ったけど、直すべきことは以下の3つに絞れる。

Missing * * * docstring => モジュールや関数にドキュメントを付けなさい
* * * doesn't conform to snake_case... => スネークケースという表記方法に準拠しなさい
Unused variable * * * => その変数は使用されてないよ


じゃあ一つずつ直していく。

まず、モジュールと関数にドキュメントを付ける部分(本来はこんな適当じゃ絶対ダメだと思うけど)。

"""
f(x) = x ** n と f(x) の導関数をプロットする
"""

import matplotlib.pyplot as plt
import numpy as np

def main():
    """
    メイン関数
    """
    plot(2)

def f(x, idx):
    """
    f(x)の値を返す
    """
    y = x ** idx
    return y

def f_dash(x, idx):
    """
    f'(x)の値を返す
    """
    y_ = (idx * x) ** (idx-1)
    return y_

def plot(idx):
    """
    f(x)とf'(x)をプロットする
    """
    x = np.linspace(-30, 30, 100)
    y = f(x, idx)
    y_ = f_dash(x, idx)
    fig, axes = plt.subplots(1, 2, figsize=(10, 8))
    axes[0].plot(x, y, c="indianred")
    axes[0].hlines(0, -25, 25)
    axes[0].vlines(0, min(y), max(y))
    axes[0].set_title("f (x) = x ** {}".format(idx), fontsize=15)
    axes[1].plot(x, y_, c="indianred")
    axes[1].hlines(0, -25, 25)
    axes[1].vlines(0, min(y_), max(y_))
    axes[1].set_title("f ' (x) = {}x ** {}".format(idx, idx-1), fontsize=15)
    plt.suptitle("Differential Plot", fontsize=20)
    plt.show()

if __name__ == "__main__":
    main()

すると

C:\Users\***\Desktop\Python\Test>pylint differential.py
************* Module differential
differential.py:14:0: C0103: Function name "f" doesn't conform to snake_case naming style (invalid-name)
differential.py:14:0: C0103: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:18:4: C0103: Variable name "y" doesn't conform to snake_case naming style (invalid-name)
differential.py:21:0: C0103: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:25:4: C0103: Variable name "y_" doesn't conform to snake_case naming style (invalid-name)
differential.py:32:4: C0103: Variable name "x" doesn't conform to snake_case naming style (invalid-name)
differential.py:33:4: C0103: Variable name "y" doesn't conform to snake_case naming style (invalid-name)
differential.py:34:4: C0103: Variable name "y_" doesn't conform to snake_case naming style (invalid-name)
differential.py:35:4: W0612: Unused variable 'fig' (unused-variable)

------------------------------------------------------------------
Your code has been rated at 6.67/10 (previous run: 4.81/10, +1.85)

という感じで評価が上がった。


次に、スネークケースという表記方法に変える部分。

ちなみに、スネークケースは one_two_three みたいな アンダーバーで単語を繋ぐ表記方法。
もう一つキャメルケースという表記方法があり、こっちは oneTwoThree のように単語の先頭を大文字にして繋げる

具体的な表記方法の決まりに関してはこちらのサイトを参考にした。

小文字で、かつ単語でアンダーバーを挟む形の変数・関数名にする必要があったみたい。

"""
f(x) = x ** n と f(x) の導関数をプロットする
"""
import matplotlib.pyplot as plt
import numpy as np

def main():
    """
    メイン関数
    """
    plot_function(2)

def function_x(x_val, idx):
    """
    f(x)の値を返す
    """
    y_val = x_val ** idx
    return y_val

def function_dash_x(x_val, idx):
    """
    f'(x)の値を返す
    """
    y_dash_val = (idx * x_val) ** (idx-1)
    return y_dash_val

def plot_function(idx):
    """
    f(x)とf'(x)をプロットする
    """
    x_val = np.linspace(-30, 30, 100)
    y_val = function_x(x_val, idx)
    y_dash_val = function_dash_x(x_val, idx)
    fig, axes = plt.subplots(1, 2, figsize=(10, 8))
    axes[0].plot(x_val, y_val, c="indianred")
    axes[0].hlines(0, -25, 25)
    axes[0].vlines(0, min(y_val), max(y_val))
    axes[0].set_title("f (x) = x ** {}".format(idx), fontsize=15)
    axes[1].plot(x_val, y_dash_val, c="indianred")
    axes[1].hlines(0, -25, 25)
    axes[1].vlines(0, min(y_dash_val), max(y_dash_val))
    axes[1].set_title("f ' (x) = {}x ** {}".format(idx, idx-1), fontsize=15)
    plt.suptitle("Differential Plot", fontsize=20)
    plt.show()

if __name__ == "__main__":
    main()

再度実行すると

C:\Users\***\Desktop\Python\Test>pylint differential.py
************* Module differential
differential.py:34:4: W0612: Unused variable 'fig' (unused-variable)

------------------------------------------------------------------
Your code has been rated at 9.63/10 (previous run: 6.67/10, +2.96)

大分減ったな・・・。なんかゲームみたいで楽しい。


そしてラスト。未使用の変数の部分。

これは該当する変数を削除するか、変数名の前に __ を付けてプライベート変数とすれば警告は出なくなるみたい。

"""
f(x) = x ** n と f(x) の導関数をプロットする
"""
import matplotlib.pyplot as plt
import numpy as np

def main():
    """
    メイン関数
    """
    plot_function(2)

def function_x(x_val, idx):
    """
    f(x)の値を返す
    """
    y_val = x_val ** idx
    return y_val

def function_dash_x(x_val, idx):
    """
    f'(x)の値を返す
    """
    y_dash_val = (idx * x_val) ** (idx-1)
    return y_dash_val

def plot_function(idx):
    """
    f(x)とf'(x)をプロットする
    """
    x_val = np.linspace(-30, 30, 100)
    y_val = function_x(x_val, idx)
    y_dash_val = function_dash_x(x_val, idx)
    __fig, axes = plt.subplots(1, 2, figsize=(10, 8))
    axes[0].plot(x_val, y_val, c="indianred")
    axes[0].hlines(0, -25, 25)
    axes[0].vlines(0, min(y_val), max(y_val))
    axes[0].set_title("f (x) = x ** {}".format(idx), fontsize=15)
    axes[1].plot(x_val, y_dash_val, c="indianred")
    axes[1].hlines(0, -25, 25)
    axes[1].vlines(0, min(y_dash_val), max(y_dash_val))
    axes[1].set_title("f ' (x) = {}x ** {}".format(idx, idx-1), fontsize=15)
    plt.suptitle("Differential Plot", fontsize=20)
    plt.show()

if __name__ == "__main__":
    main()

実行。

C:\Users\***\Desktop\Python\Test>pylint differential.py

-------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 9.63/10, +0.37)


これで とりあえず はバグやスタイル上の問題が解決した。

文法や表記方法は先ほどのpylintで突破したので、次は単体テストを行う。

初めて単体テストという言葉聞いた・・・。この時点で復習じゃない(゜.゜)
調べてみたら、大規模開発する際は必須のテストらしい。
部品ごとにテストをしてバグを取り除いておかないと、全部くっつけたときにどこにバグがあるのか分からなくなってちゃうからかな。

僕のような初心者やいわゆるスクリプトを書くような人とは縁がないのか・・・?

まぁとりあえず軽く触れる程度で(;^ω^)。


手順は

  1. 標準ライブラリのunittestをインポート
  2. unittestのTestCase継承してクラスを定義
  3. テストケース(テストしたいプログラムを試すコードを記述)を定義する

こんな感じだろうか・・・?


注意点は、テストケースを定義する関数名は test で始めなければいけないということ。

公式ドキュメントには以下のようなことが書いてある。

テストランナーはこの命名規約によってテストを行うメソッドを検索します。

ここでいう命名規約というのが test というプレフィックスらしい。


では、以上の注意点を踏まえてコードを書いてみる。
戻り値が None でないかどうかをテストするコード(こんな使い方で合ってるのか分からないが)。

#test_differential.py
import unittest
import differential
import numpy as np

class TestDifferential(unittest.TestCase):

    def setUp(self):
        print("set up.")

    def tearDown(self):
        print("tear down.")

    def test_function_x(self):
        x_val = np.linspace(-30, 30, 100)
        idx = 3
        self.assertIsNotNone(differential.function_x(x_val, idx))

    def test_function_dash_x(self):
        x_val = np.linspace(-30, 30, 100)
        idx = 3
        self.assertIsNotNone(differential.function_dash_x(x_val, idx))

if __name__ == "__main__":
    unittest.main()
C:\Users\***\Desktop\Python\Test>python test_differential.py
set up.
tear down.
.set up.
tear down.
.
----------------------------------------------------------------------
Ran 2 tests in 0.052s

OK


テスト方法は他にも色々あって、等価比較や大小比較などもある。

import unittest

class TestString(unittest.TestCase):

    def test_reverse(self):
        #equal test
        text = "こんにちは"
        expect_txt = "はちにんこ"
        self.assertEqual(expect_txt, text[::-1])

    def test_lower(self):
        #squared_val >= val
        val = 5
        squared_val = 5 ** 2
        self.assertGreaterEqual(squared_val, val)

if __name__ == "__main__":
    unittest.main()
C:\Users\***\Desktop\Python\Test>python test_unit.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK


詳細は公式ドキュメントに書いてあるから、困ったら見に行くことにしよ_(:3」∠)_



これで一応復習が終わった・・・。
次にPythonの復習に関して記事を書くとしたら、作業中にハマったところとかだなぁ。

これからは数学と統計を勉強しつつ、これまで作った何とも言えないアプリの紹介記事とかを書いていこうかな。


以上です。


参考にさせていただいたサイト:

Pylintを利用してPythonのコーディング規約(PEP8)に沿ったコーディングしてみる

PyLint Messages and what they're trying to tell you

単体テスト (UT)

単体テストを書く理由とPythonの単体テストのフレームワークまとめ

【Python入門】unittestでプログラムのテストを実施する方法