VS-Codeでpyenv環境のpylint, pycodestyleを使う

## はじめに
意外と探して答えにたどり着かなかったので設定方法をメモします。

### 環境
* Ubuntu 18.04 LTS (Windows 10; WSL)
* Python 3.8.8 (pyenv)

## 設定方法
まずは結論だけ書く。
VS-Code の設定は User / Remote / Workspace とあるが、今回は Remote に設定をした。
Remote環境(WSL)共通の設定なので。

### 1. pythonコマンドのパスを設定する
最初にプロジェクトを開いたときにPythonをどれを使うか聞かれますが、そのときに適切なものを選択している場合は、設定済みとなります。

未設定か設定が正しくない場合の設定方法を記載します。
pythonコマンドのインストール先は環境依存なので、Remote タブで設定を加えます。
`python.pythonPath` (紛らわしい)の項目にPythonコマンドのパスを設定します。shell で `type python` とか叩くと絶対パスは分かる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326797/c689ef64-9ddc-6e0e-3058-b737f55daa3c.png)

[Using Python Environments in Visual Studio Code](https://code.visualstudio.com/docs/python/environments#_where-the-extension-looks-for-environments)によると、標準のパスは /usr/local/bin, /usr/sbin, /sbin から探すとある。

### 2. フォルダ階層
次のような階層でコードを想定した設定例を示す。

(top)
　├ .vscode/setting.json ... Workspaceの設定ファイル
　├ src/ ... **ソースコード置き場**
　├ tests/ ... **テストコード置き場**
　├ .env ... **環境変数設定ファイル**
　├ requirements.txt ... 依存パッケージ
　└ LICENSE ... ライセンスファイル(今回は関係ない)

.envファイルの例

```
PYTHONPATH="src:tests"
```

requirements.txtの例(pytest)

```
pycodestyle>=2.7.0
pylint>=2.6.0
pytest>=6.2.2
pytest-cov>=2.11.1
autopep8>=1.5.6
```

requirements.txtの例(unittest)

```
autopep8>=1.5.6
coverage>=5.5
pycodestyle>=2.7.0
pylint>=2.6.0
```

* 独自のPYTHONPATHを設定したいなら、`.env` ファイルにPYTHONPATHを定義すると良い
* `.env` というファイル名が気に入らなければ、 `python.envFile` でファイル名を変更することができる

### 3. pipパッケージのインストール

```shell-session
python -m pip install -r requirements.txt
```

### 4. Lint関連の設定

#### 4.a. pycodestyle を有効にする
次のことをする。

* pycodestyleをインストールする  
  `python -m pip install pycodestyle` とか(`pip install pycodestyle`だとパスに他の pipがあると誤爆することがある)
* VS-Codeで Pycodestyle を有効にして、pycodestyle の絶対パスを入れる  
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326797/c6c89cfa-a7ee-59e9-9e56-606f48b616e1.png)

#### 4.b. pylint を有効にする

* pylintをインストールする  
  `python -m pip install pylint` とか
* VS-Codeで Pylintを有効にして、pylint の絶対パスを入れる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326797/36395c8d-8dba-e753-b669-bb4785385e1c.png)
* code で開くパス直下に `.pylintrc` を作る  
  `pylint --generate-rcfile > .pylintrc` みたいに作れる


### 5. テストの設定
大雑把に次の違いがある。(VS-Codeだとどっちと相性が良いのか…)

* unittest  
  Python標準。良くも悪くもシンプルであり、失敗時のエラーは簡易。
* pytest  
  サードパーティー(MITライセンス)。歴史はある。失敗時のエラーが詳細に表示できる。

テスト対象のコード例: **src**/calc.py

```python
"""Calc functions."""


def minus(op_a, op_b):
    """minus function."""
    if op_a < op_b:
        return 0
    return op_a - op_b
```

### unittestの場合
次の設定をします。

* `python.testing.unittestEnabled` ...　チェックを入れる

テストコードの例

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Sample of unittest."""
import unittest
import calc


class TestCalc(unittest.TestCase):
    """Test calc module."""

    def test_calc_1(self):
        """test calc.minus."""
        ret = calc.minus(3, 1)
        assert ret == 1  # OK: ret == 2

    def test_calc_2(self):
        """test calc.minus."""
        ret = calc.minus(1, 2)
        assert ret == 0


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

# EOF
```

実行例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326797/af4b8a65-82b0-7a34-832a-313a8c82c990.png)

### pytestの場合
次の設定をします。

* `python.testing.pytestEnabled` ...　チェックを入れる
* `python.testing.pytestPath` ... 絶対パスを入れる

テストコードの例

```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Sample of pytest."""
import calc


def test_calc_1():
    """test calc.minus."""
    ret = calc.minus(3, 1)
    assert ret == 1  # OK: ret == 2


def test_calc_2():
    """test calc.minus."""
    ret = calc.minus(1, 2)
    assert ret == 0
```

実行例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326797/c8a9628b-5d36-6395-cd99-784d8cd86dfa.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326797/3a8044eb-5a96-048a-e6d8-59c5e952f19e.png)


## 解説

### pycodestyle / pylint

pyenvでインストールするとき、 `.bashrc` に pyenv のパスを追加して `pyenv init -` の結果を eval する。
`pyenv init -` で生成されるシェルスクリプトでは `export PATH="$HOME/.pyenv/shims:${PATH}"` あたりで `pycodestyle` や `pylint` のパスが追加される。
動的に追加されるパスなので VS-Code としては `pycodestyle` や `pylint` の格納場所を知りようがないから絶対パスが必要。

## 余談
pycodestyleから先に試していたら、英語で書いてある通りで full path を入れれば有効化されるので迷わなかった気がする。

## 参考
* [Linting Python in Visual Studio Code](https://code.visualstudio.com/docs/python/linting)
* [Use of the PYTHONPATH variable | Using Python Environments in Visual Studio Code](https://code.visualstudio.com/docs/python/environments#_use-of-the-pythonpath-variable)