# CRMデータ連携 データ取得処理 ECSタスク

## 前提事項

### ツールのバージョン

- Python 3.9.x
- PipEnv(Pythonの依存関係管理用モジュール)

### 開発環境

- Visual Studio Code

## 開発環境構築

※下記の操作は基本的にVSCode上で行います。

- [ファイル]-[フォルダーを開く]から、当フォルダを選択して開く

- [Wiki | Pythonの環境構築](https://nds-tyo.backlog.com/alias/wiki/1874930)にて、pyenvの導入まで完了させる
  - **pyenvの導入はマストではないが、Pythonのバージョンが前提のバージョンと同一であることを確認して開発を進めてください**
  - **確認しながら開発するのは煩わしいため、導入を強く推奨します。**

- ローカルのPythonでPipEnvをインストールする

  ```sh
  pip install pipenv
  ```

- pipenvの仮想環境と依存パッケージをインストール。このとき、初回実行にはpythonの仮想環境のパスがターミナルに表示されるため、控えておく

  ```sh
  # 開発用パッケージも含めてインストール
  pipenv install --dev
  ```

- VSCodeのコマンドパレットを[表示]-[コマンドパレット]から開き、`Python: Select interpreter`を選択して実行する
- Pythonの実行環境を聞かれるため、先に控えたパスと一致するものを選択する
  - 出てこない場合、一度VSCodeを閉じて再度開き直す

- 当フォルダ直下の`.vscode`フォルダ内にある`recommend_settings.json`をコピーし、同フォルダ内に`settings.json`を作成する

## ローカルでの起動方法

- 当フォルダ直下の`.vscode`フォルダ内に`launch.json`を作成する
- 以下のJSONを入力して保存する

  ```json
  {
    "version": "0.2.0",
    "configurations": [
      {
        "name": "Python: Current File",
        "type": "python",
        "request": "launch",
        // エントリーポイントのファイルに変更すること
        "program": "<エントリーポイントになるファイル>",
        "console": "integratedTerminal",
        "justMyCode": true,
        // 環境変数が必要な場合に読み込む環境変数ファイル
        "envFile": "${workspaceFolder}/.env",
      }
    ]
  }
  ```

- 環境変数が必要な場合、直接設定するか、上記JSONの`"envFile"`に設定されたパスに`.env`ファイルを作成し、環境変数を入力する
- キーボードの「F5」キーを押して起動する
  - デバッグモードで実行されるため、適当なところにブレークポイントを置いてデバッグすることができる

## ファイル/フォルダ構成

`[〇〇処理]モジュール`と記載されているファイルは、設計書に記載のシートと一致したPythonファイルです

```text
.
├── Dockerfile                                  -- Dockerイメージを作成するためのファイル
├── Pipfile                                     -- Pipenv(Pythonの仮想環境管理モジュール)で、依存関係を管理するためのファイル
├── Pipfile.lock                                -- Pipenvでインストールされた依存関係のバージョン固定ファイル
├── README.md                                   -- README
├── main.py                                     -- CRMデータ取得処理のエントリーポイント
├── src/                                        -- プロダクトコード置き場
│   ├── aws/                                    -- AWSのリソース操作関連のモジュール置き場
│   ├── backup_crm_csv_data_process.py          -- [CSVバックアップ処理]モジュール
│   ├── backup_crm_data_process.py              -- [CRM電文データバックアップ処理]モジュール
│   ├── check_object_info_process.py            -- [オブジェクト情報形式チェック処理]モジュール
│   ├── config/                                 -- 設定ファイル関連のモジュール置き場
│   ├── controller.py                           -- [コントロール処理]モジュール
│   ├── convert_crm_csv_data_process.py         -- [CSV変換処理]モジュール
│   ├── converter/                              -- CSV変換処理で実際に変換を行うモジュール置き場
│   ├── copy_crm_csv_data_process.py            -- [CSVアップロード処理]モジュール
│   ├── error/                                  -- 処理エラー発生時カスタム例外モジュール置き場
│   ├── fetch_crm_data_process.py               -- [CRMデータ取得処理]モジュール
│   ├── parser/                                 -- JSON設定ファイル読み込み処理モジュール置き場
│   ├── prepare_data_fetch_process.py           -- [データ取得準備処理]モジュール
│   ├── salesforce/                             -- SalesforceのAPIリクエストモジュール置き場
│   ├── set_datetime_period_process.py          -- [データ取得期間設定処理]モジュール
│   ├── system_var/                             -- 環境変数と定数ファイル置き場
│   ├── upload_last_fetch_datetime_process.py   -- [前回取得日時ファイル更新処理]モジュール
│   ├── upload_result_data_process.py           -- [取得処理実施結果アップロード処理]モジュール
│   ├── util/                                   -- ユーティリティモジュール置き場
│   │   ├── counter_object.py                   -- リトライ判定のためのカウントアップクラス
│   │   ├── dict_checker.py                     -- 辞書型値オブジェクトの設定値チェック用クラス
│   │   ├── execute_datetime.py                 -- 取得処理開始年月日時分秒の管理クラス
│   │   └── logger.py                           -- ログ管理クラス
│   └── writer/                                 -- ファイル書き込みモジュール置き場
└── tests/                                      -- テストコード置き場
    ├── test_utils/                             -- テストコードで共通的に使用できる関数群
    ├── aws/                                    -- AWS操作モジュールのテスト
    ├── ...                                     -- src配下のモジュール構成と同じ階層にテストコードを追加していく
    ├── conftest.py                             -- pytestのフィクスチャやフックを管理するファイル
    └── docstring_parser.py                     -- pytest-htmlのレポート出力用のヘルパー
```

## 単体テストについて

### 前提

- Pytestを使用する
  - <https://pypi.org/project/pytest/>
- カバレッジも取得したいため、pytest-covも使う
  - <https://pypi.org/project/pytest-cov/>
- レポートを出力するため、pytest-htmlを使う
  - <https://pypi.org/project/pytest-html/>
- S3をモック化したいため、motoをつかう
  - <https://www.learnaws.org/2020/12/01/test-aws-code/>
- CRMはテスト用の環境を使いたいため、newdwh_opeのアドレスでDeveloper組織を登録する

### テスト環境構築

- Pipenvの仮想環境下で、以下のコマンドを実行する

```sh
pipenv install --dev
```

- `.env.example`をコピーし、同じ階層に`.env`を作成する
- `.env`の以下に示す環境変数の値をDeveloper組織のものに書き換える
  - CRM_AUTH_DOMAIN
  - CRM_USER_NAME
  - CRM_USER_PASSWORD
  - CRM_USER_SECURITY_TOKEN
- 以下のコマンドを実行して単体テストを起動する

```sh
pipenv run test:cov
```

#### 一気通貫テストを実行する場合の設定

- `.env`の以下に示す環境変数の値をメルク様提供のFullSandboxのものに書き換える
  - CRM_AUTH_DOMAIN
  - CRM_USER_NAME
  - CRM_USER_PASSWORD
  - CRM_USER_SECURITY_TOKEN
- 以下のコマンドを実行して単体テストを起動する

```sh
pipenv run test:walk-through
```

#### 拡張機能：Python Test Explorer UIを導入する場合

- VSCodeの拡張機能メニューから、「Python Test Explorer for Visual Studio Code」をインストール
- コマンドパレットから「Python Configure Tests」を選択、「Pytest」を選択
- テストメニュー(フラスコのマーク)から、テストを実行することができるようになる

#### 各コマンドの説明

- `pipenv run test`
  - pytestを使用してテストを実行する
  - `tests`フォルダに配置されているテストモジュールを対象に、単体テストを実行する
- `pipenv run test:cov`
  - pytestのテスト終了時にカバレッジを収集する
  - 標準出力とカバレッジファイル(`.coverage`)に出力される
- `pipenv run test:report`
  - pytestのテスト終了時にテスト結果をHTMLで出力する
  - `.report/unit_test/test_result.html`が出力される
- `test:walk-through`
  - 一気通貫テストが実行される
  - 上記のテスト以外はスキップされる
  - `.report/walk_through/test_result.html`に結果のHTMLが出力される

## 単体テストの追加方法

- `tests`フォルダ内に、`src`フォルダの構成と同じようにフォルダを作り、`test_<テスト対象のモジュール名.py>`というファイルを作成する
  - 例：`src/aws/s3.py`をテストする場合は`tests/aws/test_s3.py`というファイル名にする
- テスト関数はクラスにまとめて、テストスイートとする
- テスト関数にはドキュメントコメントを付け、テスト観点・準備作業・期待値を記載すること
  - 上記が出力されるスニペットを用意してある。
  - `""""""`と入力し、「Test docstring (User Snippets)」を選択し、ドキュメントコメントを挿入できる

```python

from src.aws.s3 import S3Resource
class TestS3Resource:
    def test_get_object(self, s3_test, s3_):
        """
        Cases:
            S3からオブジェクトが取得できるか
        Arranges:
            - S3をモック化する
            - 期待値となるファイルを配置する
        Expects:
            オブジェクトが取得でき、期待値と正しいこと
        """
        # more code...
```
