pythonでpytestでtestを書き始めてみた時のメモ

# はじめに
学生と社会人の最も大きな違いはテストを知っているかどうかとREADMEをしっかり書けるか，パッケージを作れるか
（たくさんあります）
なのではないかと自分なりに考えてみたので（もちろんコーディング力等の差もありますが．．．計算時間気にするとか）
まず，テストについていろいろ調べてその時のメモです
二番煎じどころの騒ぎじゃないですが，同じところに詰まっている人がいれば

# testとは
そもそもtestってなに？って話です
自分が書いたプログラムがあっているか確認するものらしいですが，
調べた感じ

- 自分の書いたものの入力と出力が明確に意識できるようになる
- この部分間違っていないと言い切れる（研究成果が出ないときとかにつよい）
- 大きい機能を持たせすぎに分割して小さく設計できるようになる

この3つが大きいかなと
社会人は利益をあげたりしないといけない気がするので時間に必ず終わりがありますが，学生であれば時間は無限にある？はずなので，時間がかかるとかその他もろもろのデメリットはないと思いました

このあたりが参考に
- https://qiita.com/ta_ta_ta_miya/items/427de7237cf109c9c39b
- https://mynavi-agent.jp/it/geekroid/2014/05/post-10.html
- https://www.infiniteloop.co.jp/blog/2014/03/beginners_test/

確かに研究でうまくいかないときに，プログラムが間違ってるのかそもそも理論の実装が間違ってるのかわからないのはよくあることだと思います
特にロボット系とかだと．．．

# testの書き方
さて，どうやって書けばいいんでしょうか？というわけです
そもそも自分の書いたものをどうやってテストするかってことが一番僕としてはひっかかりました

いろいろ見てると基本的な考え方としては

**適当な入力をいれて想定しているものが返ってくるか**

**想定外のものを入れてエラーがでるか**

の2つではないのかなと思います

つまり，整数の足し算をやりたいときに，1と2入れたら3が返ってくるのを確認するのはそうですが，
小数点付きのものでいれたらERRORがでるようになっているのもある意味で言えば正しく実装されていることを証明できます

例えば，具体的に事象別に考えてみると

- 簡単な計算系
自分の手計算と同じ答えがでるか
型も同じか

- 行列系
手計算できる場合は手計算の答えと同じか
行列の形，行数列数があっているか
型も同じか

- 機械学習の逆伝播
数値微分してみた結果と比較する（手計算は無理なので）

- 画像系
セルの値が0-255になっているか

とかですかね．．．
これ考えるのもなかなか難しい
ただ思ったのは先ほどの通り，テストできるところでカプセルかというか，入力と出力の関係を作ってあげておかないと，テストができなくなります．つまり，大きい機能を持たせることができない＝オブジェクト指向っぽい感じになるのかなと勝手に思いました

# testのライブラリと基本
まず，ライブラリとかimportしなくても使えるは

```py
expected_A = 1
A = 1
assert expected_A = A, 'Not equal expected_A'
```

ですね
条件式がTrueでないときにERRORをはいて止まってくれます
こいつを実際のプログラムの中に組み込めばいいわけです

参考
- https://qiita.com/nannoki/items/15004992b6bb5637a9cd

これが入り口なのだとは思いますが，これはぐちゃぐちゃになりそうなので，testを別で書きたくなります

pythonだとtestはunittestがライブラリとして提供されています．
これがpythonでたくさん提供されているほかのtest用のライブラリの基礎となっているようなのですが，いろいろあってpytestを使うことになったので，ここではpytestの話をします

参考
- https://qiita.com/kg1/items/4e2cae18e9bd39f014d4
- https://www.qoosky.io/techs/bdd40188f7

testを別のプログラムとして書く場合は
まず自分のかいたものからimportをしてきます

そして関数の前にtestっていれます

```py
def test_*():
...
    assert A == 0
```

とするのが定石みたいです
testクラスも

```py
class TestArrays():
```

こんな感じが定石ぽい
というかこうしないと認識されません
ただクラスの場合は，

```py
class Test*():
    def test_*(self):
    ...
       assert A == 0
    # testの内容を記載



if __name__ == '__main__':
    pytest.main()
```

としておいてください
これでpytestしたときに実行されます

pytestは，カレントディレクトリ以下の中にある
test_
Test
がつくものを実行して確認してくれます

なんかtest.pyだとだめらしい

また，大切なのは，最後にassertすることです
これで，条件式以外の場合にエラーがでるようになります

基本的な内容はここまでだと思うので
ここでは，円を書くというプログラムを書いたとします
自分なりに考えてみると，

入力は

円の中心のx, y, と半径

出力は

matplotlibで出力されたimg

です

ここでこのプログラムは2つのもので構成されていることが分かります

- センター座標をもらって，円を書くもの
- 画像として出力するもの

です

なのでこの場合はtestが2つ必要になります．

検証としては
円かどうかを調べるのは骨が折れそうですが

- 値が半径±座標を超えないか
- 中心からの距離がすべての点で半径以上か

plotできたかどうかのものは
save figしてimg(png file)ができているのかを確認すればよさそうです

とりあえずまず書いてみます

```circle.py
import numpy as np
import matplotlib.pyplot as plt
import math

def circle_make(center_x, center_y, radius):
    '''
    :param center_x 円の中心x座標
    :param center_y 円の中心y座標
    :param size 円の半径
    '''
    point_num = 100 # 分解能

    circle_x = []
    circle_y = []

    for i in range(point_num + 1):
        circle_x.append(center_x + radius * math.cos(i*2*math.pi/point_num))
        circle_y.append(center_y + radius * math.sin(i*2*math.pi/point_num))

    return circle_x, circle_y

def plot_singles(x, y):
    '''
    :param x プロットするもののx成分
    :param y プロットするもののy成分
    '''
    fig = plt.figure()
    axis = fig.add_subplot(111)

    axis.plot(x, y)

    plt.savefig('sample.png')

if __name__ == '__main__':
    x = 1.0
    y = 1.0
    radius = 1.0

    xs, ys = circle_make(x, y, radius)
```


続いてtestです


```test_circle.py
import pytest
import math
import glob
from utils.circle import circle_make, plot_singles

def test_cicle_make():
    x = 1.0
    y = 2.0
    radius = 5.1
    xs, ys = circle_make(x, y, radius)
    dis_check = False
    
    for i in range(len(xs)):
        dis = math.sqrt((xs[i] - x)**2 + (ys[i] - y)**2)
        if round(dis, 3) > radius:
            dis_check = True

    assert len([i for i in xs if i > x + radius]) == 0 # 最大xを確認 
    assert len([i for i in ys if i > y + radius]) == 0 # 最大yを確認
    assert len([i for i in xs if i < x - radius]) == 0 # 最小xを確認
    assert len([i for i in ys if i < y - radius]) == 0 # 最小yを確認
    assert not dis_check # 距離を確認

def test_plot_singles():
    x = range(100)
    y = range(100)

    plot_singles(x, y)

    # imgがあればおっけ
    assert glob.glob('./tests/*.png') is not None
```


./tests/ へいどうしてから実行してみると

なおフォルダ構成はこうなってます

```
Utils
 ├─utils
 |  └─ circle.py
 |  
 └─tests
    └─ test_circle.py
```

```
(base) C:\Users\...\Utils\tests>pytest
============================= test session starts =============================
platform win32 -- Python 3.6.5, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
rootdir: C:\Users\...\Utils\tests, inifile:
plugins: remotedata-0.2.1, openfiles-0.3.0, doctestplus-0.1.3, arraydiff-0.2
collected 2 items

test_circle.py ..                                                        [100%]

========================== 2 passed in 0.71 seconds ===========================
```

ほぇこんな感じか

matplotのtestは不十分気もしますが，とりあえずこんな感じでしょうか．．．

別件ですがこのutilsは拡張していこうと思います
お絵かきとかにも使えるように！

次は書き溜めてたやつとか整理してみようっと
パッケージ作ってみたいですね！

github : https://github.com/Shunichi09/Utils