Databricks Workspace 上で pytest によるテストケース作成とテスト実行を行う方法

## 概要
Databricks Workspace ファイル機能を利用することで、Databricks Workspace 上で pytest によるテストケース作成とテスト実行が可能であることが判明したため、検証結果を共有します。pytest をベースに記述した py 形式のファイルのテストケースを pytest で実行できるようになり、実行結果としては次のようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/1fb74c7a-f6b4-089a-5add-0f2ba93e1153.png)

コードは次のリンク先に確認できます。

https://github.com/manabian-/databricks_tecks_fro_qiita_2/blob/main/pytest_test/tests/test_runner.py


## 検証の準備

### ディレクトリ構成

```text
|--README.md
|--src
|  |--utilities.py
|--tests
|  |--test_cases
|  |  |--test_cases__utilities.py
|  |--test_runner <-- テストを実行するノートブック
```

### src/utilities.py

```python
# ２つの引数を加算する関数を定義
def add_num(a, b):
    return a + b
```

### tests/test_cases/test_cases__utilities.py

```python
import pytest
from pytest_test.src.utilities import add_num

def test__add_num__001():
    """successes（正常系テスト）"""
    assert add_num(1, 1) == 2

def test__add_num__002():
    """successes（異常系テスト）"""
    # 文字を引数とすることでエラーとなる想定
    with pytest.raises(TypeError) as e:
        add_num(1, 'ABC')

def test__add_num__003():
    """failures"""
    assert add_num(1, 1) == 1

def test__add_num__004():
    """failures(errors)"""
    assert add_num(1 + "a") ==1

@pytest.mark.skip(reason='スキップ用')
def test__add_num__005():
    """skipped"""
    assert a

@pytest.mark.xfail
def test__add_num__006():
    """XFAIL"""
    assert add_num(1, 1) == 1

@pytest.mark.xfail
def test__add_num__007():
    """XPASS"""
    assert add_num(1, 1) == 2
```

## 実行検証

### 1. pytest のインストール

```python
# install pytest
%pip install pytest -q
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/be332e74-1676-b8d8-48e8-818385bcb692.png)


### 2. カレントディレクトリを Repo のルートに設定

```python
# カレントディレクトリを Repo のルートに設定
import os
 
notebook_path = dbutils.notebook.entry_point.getDbutils().notebook().getContext().notebookPath().get()
repo_root = os.path.dirname(os.path.dirname(notebook_path))
repo_dir = f'/Workspace{repo_root}'
os.chdir(repo_dir)
os.chdir('../')
print(os.getcwd())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/49c9433b-0767-c2c1-4514-c70a5dfd10c6.png)

### 3. __pycache__ を作成しないように設定

```python
# __pycache__ を作成しないように設定
import sys
sys.dont_write_bytecode = True
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/f1cbbf06-0e16-d406-3aa0-b36b3c996fdd.png)

### 4. ディレクトリを指定

```python
# テストケースを保持しているディレクトリを指定
test_path = 'pytest_test/tests/test_cases'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/3484479f-f5af-e4f9-e07a-8fcfd011310c.png)

### 5. テストを実行

エラーとなるテストケースが含まれているため、ノートブックの実行がエラーとなります。

```python
# テストを実行
import pytest
pytest_pre_args = []
pytest_discovery_args = [
    "-v",
    "-rsx",
    "--showlocals",
    "--tb=short",
    "-s",
    "--pyargs",
    test_path,
]
pytest_pre_args.extend(pytest_discovery_args)
ut_result = pytest.main(pytest_pre_args)
 
assert ut_result == 0, "The test is failed."
```

```text
============================= test session starts ==============================
platform linux -- Python 3.9.5, pytest-7.3.1, pluggy-1.0.0 -- /local_disk0/.ephemeral_nfs/envs/pythonEnv-30674075-e389-498e-8621-1c5d9acba497/bin/python
cachedir: .pytest_cache
rootdir: /Workspace/Repos/Shared/databricks_tecks_fro_qiita_2
collecting ... collected 7 items

pytest_test/tests/test_cases/test_cases__utilities.py::test__add_num__001 PASSED
pytest_test/tests/test_cases/test_cases__utilities.py::test__add_num__002 PASSED
pytest_test/tests/test_cases/test_cases__utilities.py::test__add_num__003 FAILED
pytest_test/tests/test_cases/test_cases__utilities.py::test__add_num__004 FAILED
pytest_test/tests/test_cases/test_cases__utilities.py::test__add_num__005 SKIPPED
pytest_test/tests/test_cases/test_cases__utilities.py::test__add_num__006 XFAIL
pytest_test/tests/test_cases/test_cases__utilities.py::test__add_num__007 XPASS

=================================== FAILURES ===================================
______________________________ test__add_num__003 ______________________________
pytest_test/tests/test_cases/test_cases__utilities.py:16: in test__add_num__003
    assert add_num(1, 1) == 1
E   assert 2 == 1
E    +  where 2 = add_num(1, 1)
______________________________ test__add_num__004 ______________________________
pytest_test/tests/test_cases/test_cases__utilities.py:20: in test__add_num__004
    assert add_num(1 + "a") ==1
E   TypeError: unsupported operand type(s) for +: 'int' and 'str'
=========================== short test summary info ============================
SKIPPED [1] pytest_test/tests/test_cases/test_cases__utilities.py:22: スキップ用
XFAIL pytest_test/tests/test_cases/test_cases__utilities.py::test__add_num__006
========= 2 failed, 2 passed, 1 skipped, 1 xfailed, 1 xpassed in 2.44s =========
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/24031/6d05dff5-c522-5ce7-1157-b0e9f59211e0.png)