Cucco’s Compute Hack

コンピュータ関係の記事を書いていきます。

Python サブモジュールのディレクトリ構造とunittestの話

プログラムとunittestを同じディレクトリに置くパタンのテスト。
パッケージ(hello_submodule)の同列にtestディレクトリを作って、unittestのファイルを置く構成が標準らしいが、パスの設定関係がとても面倒なので。

プログラムとunittestを同じディレクトリに置くパタンの場合、カレントディレクトリがhello_submoduleにあるものとしてfromなどを記述すればよさそう。

ディレクトリ構造
─hello_submodule
  │  program_main.py
  │  program_main_test.py
  │  
  ├─.vscode
  │      launch.json
  │      settings.json
  │      
  ├─sub_module_1
  │      program_submod1.py
  │      program_submod1_test.py
  │      __init__.py
  │          
  └─sub_module_2
          program_submod2.py
          program_submod2_test.py
          __init__.py
ソースコード

1. program_main.py

from sub_module_1 import program_submod1
from sub_module_2.program_submod2 import Class_Submod2

def main_func():
    x = program_submod1.func1()
    y = Class_Submod2()
    z = y.func2()

    print("this function is program_main.main_func()")
    print(f"x={x}, y={y},z={z}")

    return 3

if __name__ == '__main__':
    main_func()

2. program_main_test.py

import unittest
import program_main

class Testprogram_main(unittest.TestCase):

    def test_main_func(self):
        self.assertEqual(program_main.main_func(), 3)


if __name__ == '__main__':
    unittest.main()

3. program_submod1.py

def func1():
    print("this function is sub_module_1.func1()")
    return (1)

4. program_submod1_test.py

import unittest
from sub_module_1 import program_submod1

class Testprogram_submod1(unittest.TestCase):

    def test_func1(self):
        self.assertEqual(program_submod1.func1(), 1)

if __name__ == '__main__':
    unittest.main()

5. program_submod2.py

class Class_Submod2:
    def __init__(self):
        self.v = 2
        pass

    def func2(self):
        print("this function is sub_module_2.func2() in Class_Submod2")
        return (self.v)

6. program_submod2_test.py

import unittest
from sub_module_2 import program_submod2

class Testprogram_submod2(unittest.TestCase):

    def test_func1(self):
        x=program_submod2.Class_Submod2()
        self.assertEqual(x.func2(), 2)

if __name__ == '__main__':
    unittest.main()

7. __init__.py
空のファイル。

8. setting.json(vscodeの設定ファイル)

{
    "python.testing.unittestArgs": [
        "-v",
        "-s",
        ".",
        "-p",
        "*_test.py"
    ],
    "python.testing.pytestEnabled": false,
    "python.testing.nosetestsEnabled": false,
    "python.testing.unittestEnabled": true
}
コマンドラインでのテスト実行例
(base) C:\dev\SampleCodes\hello_submodule>python -m unittest discover -p "*_test.py"
this function is sub_module_1.func1()
this function is sub_module_2.func2() in Class_Submod2
this function is program_main.main_func()
x=1, y=<sub_module_2.program_submod2.Class_Submod2 object at 0x0000021BF07B07C8>,z=2
.this function is sub_module_1.func1()
.this function is sub_module_2.func2() in Class_Submod2
.
----------------------------------------------------------------------
Ran 3 tests in 0.005s

OK
||>

*** パスの設定関係がとても面倒な理由
unittestファイル側から、テスト対象の関数が書いてある.pyファイルをimportしないといけないが、親ディレクトリをたどってimportする設定が手間なため。
全てのunittestファイルに以下を記載してもよいが手間。lynterが問題として検出することもある。

>||
import sys
sys.path.append(r"<パッケージのディレクトリ>")