matplotlibでグラフ描画
データサイエンス界隈でpythonのよく使うライブラリと言えば、numpy, pandas, matplotlibです。
今回はその matplotlib でもとりわけよく利用するグラフを描画する部分に着目し、どんなことができるのかをまとめました。
インポート
グラフ描画のためにmatplotlibを使用する場合、以下のようにインポートすることが多いです。
import matplotlib.pyplot as plt
・matplotlib
今回解説するライブラリです。
このライブラリを使ってグラフを作っていきます。
・pyplot
matplotlib内のグラフ描画モジュールです。
・plt
matplotlib.pyplotに別名をつけています。
慣例的に、pltと名付けることが多いです。
折れ線グラフ
線グラフを描画する場合にはplot()
を使用します。
plt.plot(X, Y, ls='-',lw='1', color='b',marker = '+',label='ice_sales') plt.legend() plt.show()
引数 | 説明 | 補足 |
---|---|---|
X | X軸 | 必須項目 |
Y | Y軸 | 入力しない場合データ数になります。 |
label | 凡例 | plt.legend()を付けないと表示されません。 |
color | 線の色 | 様々な指定方法が用意されている。 ・アルファベット1文字('b', 'g', 'r', 'c', 'm', 'y', 'k', 'w') ・RGB+透過度αで指定((0.1, 0.2, 0.5, 0.3)) ・名称で指定("orange", "pink", …) 参考:https://matplotlib.org/api/colors_api.html |
ls | 線の種類 | 例)'-' , '--', '-.' , ':' , 'steps' |
lw | 線の太さ | デフォルトは1.0 |
marker | マーカー形状 | 例)'+' , ',' , '.' , "o", '1' , '2' ,'3' ,'4' |
散布図
散布図を記載する場合にはscatter()
を使用します。
plt.scatter(data_frame.high_temp, data_frame.ice_payment,s=20, marker='o', cmap='jet',c=data_frame.ice_payment) plt.colorbar() plt.show()
引数 | 説明 | 補足 |
---|---|---|
X | X軸 | 必須項目 |
Y | Y軸 | 必須項目 |
c | 色 | X,Yの値も指定できます。 |
s | サイズ | デフォルト値:20 |
marker | マーカー形状 | デフォルトはo |
cmap | カラーマップ | plt.colorbar()が必要 |
円グラフ
円グラフを描画する場合にはpie()
を使用します。
x = numpy.array([400, 400, 600, 800, 200]) colors = ["yellow", "red", "blue", "green", "orange"] ex = numpy.array([0.2, 0, 0, 0, 0]) label = ["Carbonara", "Arabian", "Pescatore", "Genovese", "Peperoncino"] plt.pie(x,labels=label,explode = ex,startangle=90,colors=colors,explode = 0.2) plt.show()
引数 | 説明 | 補足 |
---|---|---|
X | X軸 | この値だけでも成立します。 |
label | ラベル | Xの値毎にラベルを張ります。 |
colors | 色 | Xの値毎に色を設定します。 |
explode | 指定したXの値を円の中心から指定値だけ離して表示 | サンプルは0.2離した状態です。 |
棒グラフ
棒グラフを描画する場合にはbar()
を使用します。
X = numpy.array([1, 2, 3, 4, 5]) Y = numpy.array([400, 400, 600, 800, 200]) Y2 = numpy.array([400, 400, 600, 800, 200]) colors = ["yellow", "red", "blue", "green", "orange"] label = ["Carbonara", "Arabian", "Pescatore", "Genovese", "Peperoncino"] plt.bar(X, Y,width = 0.8 , color =colors,tick_label = label,linewidth= 1,edgecolor="#000000")
引数 | 説明 | 補足 |
---|---|---|
X | X軸 | 必須項目 |
Y | Y軸 | 必須項目 |
width | 棒グラフ幅 | デフォルトは0.8 |
tick_label | X軸ラベル | 設定しない場合はX軸が表示されます。 |
color | 色 | 棒の色を設定します。 |
linewidth | 枠線サイズ | 設定することによって、枠線を付けることができます。 |
edgecolor | 枠線色 | colorと同様で配列型での指定も可能です。 |
bottom | 棒グラフの下の値 | 例えば、100にするとすべてのY軸の始点が100になります。また、積み上げグラフ(以下参照)を作成する場合に使われます。 |
積み上げグラフ
パラメータのbottomに下の棒グラフのY値を入れることによって、上にグラフを積み上げることが可能です。
先ほど出力した棒グラフに対して積み上げたパターンが以下です。
X = numpy.array([1, 2, 3, 4, 5]) Y = numpy.array([400, 400, 600, 800, 200]) Y2 = numpy.array([600, 600, 400, 200, 800]) colors = ["yellow", "red", "blue", "green", "orange"] label = ["Carbonara", "Arabian", "Pescatore", "Genovese", "Peperoncino"] plt.bar(X, Y,width = 0.8 , color =colors,tick_label = label,linewidth= 1,edgecolor="#000000") plt.bar(X, Y2,bottom=Y)
まとめ
今回は、主要なグラフの描き方をご紹介しました。
この他にも箱ひげ図やコレログラムなど、統計学でよく用いられるグラフも描画することが出来ますので、引き続き学んでいきたいと思います。
単体テストを学ぼう!③ -テストしたモジュールを1つのシステムにしてみる-
こんにちは、ふじもんです。
健康診断を受けてきました。
腹囲を測ったら去年よりも5cm大きくなってました。
・・・まじか。
さて、前回の記事に引き続き今回もテスト駆動開発で未実装の部分を実装します。
そして、今回はそれらを1つのシステムとして完成させます!
前回のおさらい
前回はどんなシステムを作るか仕様を決め、それに沿ってテスト駆動開発という手法で単体テストを行いながら実装を行なっていました。
そして、前回は1の部分までを実装済みです。
1. 身長(cm)と体重(kg)をキーボードから入力する。 ・身長、体重共に有効な値の範囲は 0 < x < 1000 とし、Trueを返す ・0 以下、もしくは1000以上の数値が入力された場合はFalseを返す ・入力がない場合はFalseを返す ・数値に変換できない文字列が入力された場合はFalseを返す 2. 入力された身長(cm)と体重(kg)からBMIを計算する。
今回は復習も兼ねて2の部分を実装していこうと思います。
BMI計算部分の実装
復習ということでさくさくっと実装していきます!
前回と同じく、テストコードを書いていきます。
2の工程では計算が正しくできていることをテストします。
今回も上記の「入力された身長(cm)と体重(kg)からBMIを計算する」というだけではテストコードを書くのに不十分と気づいたため、仕様を少し詳細に決定しました。
2. 入力された身長(cm)と体重(kg)からBMIを計算する。 ・BMIは、一般的な定義に基づいて算出する。(体重(kg) / 身長(m)^2) ・計算したBMIは小数点第二位で四捨五入し、小数点第一位までを返す。
…早くこういう仕様を最初から決められるようになりたい。
また、入力値は前回まででテスト済みのため、今回のテストでは観点は以下としています。
・取りうる最小の値での計算結果は正しいか
・取りうる最大の値での計算結果は正しいか
・取りうる値の代表値での計算結果は正しいか
・メソッドが呼ばれなかった際の処理は正しいか
この観点でテストコードを書くと以下のようになります。
# bmi_test.py import sys import pathlib sys.path.append('..') current_dir = pathlib.Path(__file__).resolve().parent sys.path.append(str(current_dir) + '/../') from unittest import TestCase from bmi_calculator.BmiCalculator import BmiCalculator class TestBmiCalculator(TestCase): def test_calc_01(self): bmiCalc = BmiCalculator() bmiCalc.set_height("0.1") # 取りうる値の最小値を入力 bmiCalc.set_weight("0.1") self.assertEqual(bmiCalc.calc(), 100000) def test_calc_02(self): bmiCalc = BmiCalculator() bmiCalc.set_height("999.9") # 取りうる値の最小値を入力 bmiCalc.set_weight("999.9") self.assertEqual(bmiCalc.calc(), 10.0) def test_calc_03(self): bmiCalc = BmiCalculator() bmiCalc.set_height("170") # 一般的な日本人男性の身長と体重 bmiCalc.set_weight("60") self.assertEqual(bmiCalc.calc(), 20.8) def test_calc_04(self): bmiCalc = BmiCalculator() bmiCalc.set_height("170") # set_weightメソッドが呼ばれなかった場合 self.assertIsNone(bmiCalc.calc()) def test_calc_05(self): bmiCalc = BmiCalculator() bmiCalc.set_weight("60") # set_heightメソッドが呼ばれなかった場合 self.assertIsNone(bmiCalc.calc()) def test_calc_06(self): bmiCalc = BmiCalculator() # set_weightメソッド、set_heightメソッドが呼ばれなかった場合 self.assertIsNone(bmiCalc.calc())
それぞれのBMIを計算して気づいたのですが、身長と体重が同じでも身長が高ければ高いほど痩せ気味で低ければ低いほど太り気味になるんですね。
計算式見れば当たり前と思う方もいるかもですが、私はここで初めて気づきました。
ではプロダクションコードで実装する前に、テストコードを実行します。
bash-3.2$ python -m unittest bmi_test.py EEEEEE …python (省略) ====================================================================== ERROR: test_calc_06 (bmi_test.TestBmiCalculator) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/XXXX/work/UnitTest/test/bmi_test.py", line 106, in test_calc_06 self.assertIsNone(bmiCalc.calc()) AttributeError: 'BmiCalculator' object has no attribute 'calc' ---------------------------------------------------------------------- Ran 6 tests in 0.001s FAILED (errors=6)
レッドであることを確認できました。
では、テストを通るようにBMI計算部分を実装していきます。
# BmiCalculator.py import re import math class BmiCalculator: INPUT_PATTERN = r"^[\d]+[..]?[\d]?$" def __init__(self): self.height = None self.weight = None self.bmi = None def set_height(self, h): if not re.match(self.INPUT_PATTERN, h): return False h = float(h) if not 0 < h < 1000: return False self.height = h return True def set_weight(self, w): if not re.match(self.INPUT_PATTERN, w): return False w = float(w) if not 0 < w < 1000: return False self.weight = w return True def calc(self): if(self.height is None or self.weight is None): return None return round(self.weight / (self.height / 100) ** 2, 1)
新たにcalc
関数を追加し、height
やweight
に値が入っていない場合は何も返さず、値が設定されていた時のみBMIを計算して返します。
また、__init__
関数で、明示的にそれぞれの初期値をNoneに設定しておきます。
そしてテストコードを実行。
bash-3.2$ python -m unittest bmi_test.py ...... ---------------------------------------------------------------------- Ran 6 tests in 0.001s OK
エラーなし!失敗なし!
これで全ての機能のテストと実行が完了しましたやったーーーーー!!!
システムを完成させよう
と、ここまでやったら満足しがちな私ですが、
これだとそれぞれの機能のモジュールを作っただけになってしまいます。
せっかくテストして動作確認を行なったコードが書けたのだから、
システムとして動かせるようにしたい。
最後に作成したモジュールを繋げるmain.py
を書いて、ちゃんとシステムとして完成させましょう!
# main.py import sys import pathlib sys.path.append('..') current_dir = pathlib.Path(__file__).resolve().parent sys.path.append(str(current_dir) + '/../') from bmi_calculator.BmiCalculator import BmiCalculator if __name__ == '__main__': # BmiCalculatorの生成 bmi = BmiCalculator() # isHeightSuccessの初期値設定 isHeightSuccess = False # isHeightSuccessがTrueになる(有効値が入力される)まで繰り返す while(not isHeightSuccess): print('Input height(cm) = ') height = input() isHeightSuccess = bmi.set_height(height) # isWeightSuccessの初期値設定 isWeightSuccess = False # isWeightSuccessがTrueになる(有効値が入力される)まで繰り返す while(not isWeightSuccess): print('Input weight(kg) = ') weight = input() isWeightSuccess = bmi.set_weight(weight) # BMIの計算 bmi_value = bmi.calc() # BMIが正しく計算されなかった場合はメッセージを表示して終了 if(bmi_value is None): print("Please input height and weight") exit() # 入力された身長・体重と、計算されたBMIを表示 print('height(cm): {} weight(kg): {}'.format(height, weight)) print('BMI: {}'.format(bmi_value))
こんな感じでmain.py
を書いてみました。
身長・体重ともに、有効値を入力したらTrue, 無効値を入力したらFalseを返す仕様にしていたため、有効値が入力されるまで入力メソッドをループさせます。
また、もし今後仕様変更があった場合を考慮し、BMIが正しく計算されなかった場合の処理を入れています。
これが動けば、どんなに単純なものでも立派なシステムよ…!
期待と不安の入り混じった気持ちで実行。
bash-3.2$ python main.py Input height(cm) = 157.8 Input weight(kg) = 53.8 height(cm): 157.8 weight(kg): 53.8 BMI: 21.6
動いたアアアアアアアアアア!!!!!!!!!!!!!!
最後に
テストのテの字も知らずスクリプトを組んでは不安になっていた私ですが、今回テストのことを勉強して、その不安の払拭方法を知ることができました。
テスト…偉大だ…。
また、テスト駆動開発なるテストファーストな手法で行うと、今どんなシステムを作ろうとしているのか、機能は何が必要なのかを明確化でき、開発途中で迷子にならずに最後までシステムが構築できたかと思います。
何よりテストをすることにより不安が払拭され心理的負担が減るのが本当に大きいです。
今回例に出したシステムは非常に単純なシステムだったので、どんな観点でテストを行うかが考えやすかったのですが、
現場に出るともっと大規模なシステムや複雑な処理を要するプログラムだったりして、
・何が必要なのか
・どのような要件なのか
が迷子になりがちだったりします。
そのため、常に念頭に置きながらスクリプトを組むと、
おのずとテストの観点もわかり自分のスクリプトに安心できるのではないかなあと思いました。
お知らせ
株式会社スカイウイルでは、2021年度の新卒採用を行います。
私たちは、独自の教育研修制度によって、学習したことを自信をもって社内外に発信できる。
そんなエンジニア集団です。
弊社採用サイトやマイナビ2021にて情報を公開しておりますので、
興味のある方は是非一度、会社説明会へお越しください!
・採用サイト
株式会社スカイウイル 採用サイト
単体テストを学ぼう!② -テスト駆動開発で単体テストをしてみる-
こんにちは、ふじもんです。
前回の記事では開発におけるテスト、単体テストの種類、テスト駆動開発について触れました。
では、今回はいざ実践!
テスト駆動開発で実際に単体テストを実施していきます!!
テスト駆動開発の流れ
テスト駆動開発は、以下の手順を繰り返し行い、テストコードとプロダクションコードを作成していきます。
1. レッド :まず最初に、実装要件に基づいたテストコードを書く 2. グリーン :テストが成功するプロダクションコードを書く 3. リファクタリング:テストが成功する状態のコードを綺麗にしていく
テスト駆動開発はテストファーストの開発手法なので、
まずはどんなテストを行うか決め、テストコードを書いてから実装していくんですね。
システムの仕様の決定
まずはどんなシステムを作るかを決めないと、開発も何もありません。
じゃあ何作ろっかなー、んーーーー。
そろそろ健康診断受けなきゃだし、健康に付随してBMIを計算するシステムとかにしてみよう。
1. 身長(cm)と体重(kg)をキーボードから入力する。 2. 入力された身長(cm)と体重(kg)からBMIを計算する。 3. 計算結果を出力する。
こんな感じのシンプルなシステムを作ってみます。
いざテストコード記述
よし、穴だらけかもしれないけど仕様は決まった!
いよいよテスト駆動開発とやらをやってみるぞ!!
今回はPythonで開発を行い、テストはPythonの標準モジュールであるunittestで行っていこうと思います。
ディレクトリ構成は以下のようにしました。
UnitTest ├ bmi_calculator (プロダクションコード格納場所) │ └ BmiCalculator.py │ └ test (テストコード格納場所) └ bmi_test.py
テスト駆動開発ではまず実装要件に基づいたテストコードを書くのが最初でしたね。
最初のテストコードは、こんな感じで書いてみます。
# bmi_test.py import sys import pathlib sys.path.append('..') current_dir = pathlib.Path(__file__).resolve().parent sys.path.append(str(current_dir) + '/../') from unittest import TestCase from bmi_calculator.BmiCalculator import BmiCalculator class TestBmiCalculator(TestCase): pass
3~7行目でプロダクションコードのパスを追加し、プロダクションコードのモジュールをインポートできるようにします。
そして11~12行目の実際のテスト部分ですが、とりあえず何もしない状態。
これで一度テストコードを実行してみます。
bash-3.2$ python -m unittest bmi_test.py E ====================================================================== ERROR: bmi_test (unittest.loader._FailedTest) ---------------------------------------------------------------------- ImportError: Failed to import test module: bmi_test Traceback (most recent call last): File "/Users/XXXX/anaconda3/lib/python3.6/unittest/loader.py", line 153, in loadTestsFromName module = __import__(module_name) File "/Users/XXXX/work/UnitTest/test/bmi_test.py", line 4, in <module> from bmi_calculator.BmiCalculator import BmiCalculator ImportError: cannot import name 'BmiCalculator' ---------------------------------------------------------------------- Ran 1 test in 0.000s FAILED (errors=1)
BmiCalculator
モジュールがないと怒られましたが、まだ作ってないので怒られて当然ですね。
これがテスト駆動開発のレッドの状態です。
では、これをグリーンにすべくテストコードが通るプロダクションコードを書きます。
とりあえずエラー内容に従い、モジュールを作成します。
# BmiCalculator.py class BmiCalculator: pass
とりあえず作っただけ。
いざ実行。
bash-3.2$ python -m unittest bmi_test.py ---------------------------------------------------------------------- Ran 0 tests in 0.000s OK
OKでました!!これがグリーンの状態です。
こんな感じでひたすらテストコードとプロダクションコードを書いていくのが、テスト駆動開発のやり方のようです。
単体テストをしてみる
引き続きゴリゴリ書いていきましょう。
実装したいのは以下の3点でした。
1. 身長(cm)と体重(kg)をキーボードから入力する。 2. 入力された身長(cm)と体重(kg)からBMIを計算する。 3. 計算結果を出力する。
ではまず1. 身長(cm)と体重(kg)をキーボードから入力する。
のテストコードを記述していきます。
ブラックボックステストは、システムの中身に関係なくインプットに対するアウトプットを確認するテストでしたね。
じゃあどのインプットに対し、どのアウトプットを正とするのか。
それを決めるためにもうちょっと仕様を詳しく決めます。
1. 身長(cm)と体重(kg)をキーボードから入力する。 ・身長、体重共に有効な値の範囲は 0 < x < 1000 とし、Trueを返す ・0以下、もしくは1000以上の数値が入力された場合はFalseを返す ・入力がない場合はFalseを返す ・数値に変換できない文字列が入力された場合はFalseを返す
有効値が入力された場合はTrueを返し、無効値が入力された場合はFalseを返すようにします。
さらに、この条件のテストを行うことにより、ホワイトボックステストの分岐網羅・条件網羅もテスト可能です。
では、この仕様に合ったテストコードを書いていきます。
また、身長・体重ともに同じ仕様のため、今回は身長部分の実装のみを記載しています。
# bmi_test.py import sys import pathlib sys.path.append('..') current_dir = pathlib.Path(__file__).resolve().parent sys.path.append(str(current_dir) + '/../') from unittest import TestCase from bmi_calculator.BmiCalculator import BmiCalculator class TestBmiCalculator(TestCase): def test_set_height_01(self): bmiCalc = BmiCalculator() self.assertTrue(bmiCalc.set_height("60")) # 正常値 def test_set_height_02(self): bmiCalc = BmiCalculator() self.assertTrue(bmiCalc.set_height("999.9")) # 正常値 def test_set_height_03(self): bmiCalc = BmiCalculator() self.assertFalse(bmiCalc.set_height("1000.0")) # 無効値 def test_set_height_04(self): bmiCalc = BmiCalculator() self.assertTrue(bmiCalc.set_height("0.1")) # 正常値 def test_set_height_05(self): bmiCalc = BmiCalculator() self.assertFalse(bmiCalc.set_height("0")) # 無効値 def test_set_height_06(self): bmiCalc = BmiCalculator() self.assertFalse(bmiCalc.set_height("-1")) # 無効値 def test_set_height_07(self): bmiCalc = BmiCalculator() self.assertFalse(bmiCalc.set_height("")) # 無効値 def test_set_height_08(self): bmiCalc = BmiCalculator() self.assertFalse(bmiCalc.set_height("abc")) # 無効値
test_set_height_01
で正常値を確認、
test_set_height_02
〜test_set_height_04
で境界値分析、
test_set_height_05
〜test_set_height_08
で無効値を確認しています。
また、正常値と無効値を複数入力することで同値分割の確認もできます。
先にテストの条件を書いておくと、頭の中が整理されて何をすべきかわかって良いですね。
さてさて、テスト駆動開発の手順に沿ってテストコードを実行。
bash-3.2$ python -m unittest bmi_test.py … (省略) ====================================================================== ERROR: test_set_height_08 (bmi_test.TestBmiCalculator) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/XXXX/work/UnitTest/test/bmi_test.py", line 42, in test_set_height_08 self.assertFalse(bmiCalc.set_height("abc")) # 無効値 AttributeError: 'BmiCalculator' object has no attribute 'set_height' ---------------------------------------------------------------------- Ran 8 tests in 0.001s FAILED (errors=8)
うん、想定通り。
BmiCalculator
の中にset_height
がないと怒られました。
だって、まだ作ってn(以下略)
では、テストが通るようにset_height
を作ります。
とりあえず数値かどうか判別して無効値を除外したい。
# BmiCalculator.py import re class BmiCalculator: def __init__(self): pass def set_height(self, h): if re.match(r"[\d]+[..]?[\d]?", h): h = float(h) return True else: return False
こんな感じでどうだ!
上記のコードではif re.match(r"^[\d]+[..]?[\d]?$", h)
で小数点第一位までの数字かどうかを判別しています。
正規表現ムズカシーでもベンリー
いざテストコード実行!
bash-3.2$ python -m unittest bmi_test.py ..F.F... ====================================================================== FAIL: test_set_height_03 (bmi_test.TestBmiCalculator) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/XXXX/work/UnitTest/test/bmi_test.py", line 22, in test_set_height_03 self.assertFalse(bmiCalc.set_height("1000.0")) # 無効値 AssertionError: True is not false ====================================================================== FAIL: test_set_height_05 (bmi_test.TestBmiCalculator) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/XXXX/work/UnitTest/test/bmi_test.py", line 30, in test_set_height_05 self.assertFalse(bmiCalc.set_height("0")) # 無効値 AssertionError: True is not false ---------------------------------------------------------------------- Ran 8 tests in 0.002s FAILED (failures=2)
test_set_height_03
とtest_set_height_05
のテストが失敗しています。
0を入力した時と1000を入力した時に無効値であるときの返り値であるFalseが返ってきてないとのこと。
正規表現で数値以外の無効値が判別できただけで満足しきってました。あぶないあぶない。
追加しましょう。
# BmiCalculator.py import re class BmiCalculator: def __init__(self): pass def set_height(self, h): if re.match(r"^[\d]+[..]?[\d]?$", h): h = float(h) self.height = h return True if 0 < h < 1000: self.height = h return True else: return False else: return False
if 0 < h < 1000
の条件を追加し、有効値を絞りました。
さて、set_height
を修正したところでもう一度テストコードbmi_test.py
を実行してみましょう。
どきどきどきどき。
bash-3.2$ python -m unittest bmi_test.py ........ ---------------------------------------------------------------------- Ran 8 tests in 0.001s OK
オールグリーン!発進準備整いました!
リファクタリングしてみよう
グリーンとなったところで、リファクタリングをしてコードを読みやすくします。
今のプロダクションコードはこんな感じです。
# BmiCalculator.py import re class BmiCalculator: def __init__(self): pass def set_height(self, h): if re.match(r"^[\d]+[..]?[\d]?$", h): h = float(h) self.height = h if 0 < h < 1000: self.height = h return True else: return False else: return False
これをリファクタリングしていきます。
ざっとコードを見返して、もうちょっと綺麗にできそうと思ったところは以下2点。
・match関数の第一引数に条件を直書きしていて雑多に見える。
・条件分岐のネストが深い。
上記2点を考慮し、ソースをリファクタリングしたものが以下になります。
# BmiCalculator.py import re class BmiCalculator: INPUT_PATTERN = r"^[\d]+[..]?[\d]?$" def __init__(self): pass def set_height(self, h): if not re.match(self.INPUT_PATTERN, h): return False h = float(h) if not 0 < h < 1000: return False self.height = h return True
ちょっとすっきり。見やすくなりました。
でもソースコード変えちゃったので、動くか心配…。
というところで、もう一度テストコードを実行してみましょう!
bash-3.2$ python -m unittest bmi_test.py ........ ---------------------------------------------------------------------- Ran 8 tests in 0.001s OK
圧倒的安心感。
まとめ
今回の記事では以下のことを学習しました。
・テスト駆動開発で開発をし、ブラックボックステストを行う。
・テスト駆動開発の流れは、1.レッド→2.グリーン→3.リファクタリングの順に行う。
・まず何のテストを行うかを整理することで、プロダクションコードでの実装に抜け漏れがなくなる。
・単体テストを行い確実にシステムの仕様通りのコードであることを確認。
・ソースコードに変更を加えても、テストコードを実行することにより動作の保証ができる。
テスト項目を整理し、それに合わせて実装、テストをすることにより、自分のコードが正しく動く自信が持てますね。
こういうことをちゃんと普段から意識するようにしなければ。
逆にちゃんと意識するようになれば、もうあんな不安な日々は過ごさなくて良いと!
次回は引き続き残りの部分を実装し、システムを完成させます!
お知らせ
株式会社スカイウイルでは、2021年度の新卒採用を行います。
私たちは、独自の教育研修制度によって、学習したことを自信をもって社内外に発信できる。
そんなエンジニア集団です。
弊社採用サイトやマイナビ2021にて情報を公開しておりますので、
興味のある方は是非一度、会社説明会へお越しください!
・採用サイト
株式会社スカイウイル 採用サイト
Django+Javascriptでプログレスバーを実装する
hashiです。
外部ツールを使いにくい環境でプログレスバーを実装する必要に駆られてしまいました。
そこで、Django+Javascriptでプログレスバーを実装しましたので、その方法をまとめてみました。
全体の処理の流れ
処理の流れは以下のようになっています。
進捗状況を管理するテーブルをDB上に作成し、随時その値を確認することで進捗状況をリアルタイムで取得することができます。
環境構築
では、これから実際に実装していくための、開発環境を構築しましょう。
前提
以下の環境であることを前提とします。
プロジェクト用ディレクトリ作成
以下のコマンドを実行して適当な場所にDjangoプロジェクトを格納するためのディレクトリを作成し、その中に入ります。
mkdir progress_bar # ディレクトリ作って cd progress_bar # その中に入る
仮想環境の作成
既にインストールされているパッケージの影響を受けることを避けるため、新しく作った仮想環境内で1から環境構築していきます。
今回はパッケージ管理にpipenv
を使用します。入っていない場合は、
pip install pipenv # pipでインストール
を実行してください。その後、
export PIPENV_VENV_IN_PROJECT=true # 仮想環境をプロジェクトディレクトリ配下に作るように設定 pipenv shell # 仮想環境作成!
これでまっさらな環境が作成できました。
これ以降のコマンドはpipenv shell
を実行して仮想環境に入った状態で実行することを前提とします。
Djangoのインストール
以下のコマンドを実行してDjangoをインストールします。
pipenv install django
これで、基本的な開発環境は整いました!
Djangoの動作確認
プロジェクトを作成して、開発のベースとなるガワを作っていきます。
プロジェクトの作成
以下のコマンドを実行してprojectを作成します。
django-admin startproject config . #今のディレクトリにprojectを作成
現在のファイル構成は以下のようになります。
progress_bar |--.venv |--config | |--__init__.py | |--settings.py | |--urls.py | |--wsgi.py |--manage.py |--Pipfile |--Pipfile.lock
アプリの作成
manage.py
と同階層で以下のコマンドを実行します。
mkdir apps #アプリ用ディレクトリ作成 cd apps #その中に… python ../manage.py startapp example_app #アプリ作成
作成したアプリを利用できるようにするためにconfig/settings.py
に以下の内容を追記します。
# config/settings.py INSTALLED_APPS = +["apps.example_app.apps.ExampleAppConfig"]
続けてapps/example_app/apps.py
を書き換えます。
# apps/example_app/apps.py from django.apps import AppConfig class ExampleAppConfig(AppConfig): # name = 'example_app' name = "apps.example_app"
トップページのviewの作成
アプリのビューを以下のように記述します。
# apps/example_app/views.py from django.shortcuts import render def index(request): """基本となるページ""" return render(request, "example_app/index.html")
テンプレートの作成
テンプレートにプログレスバーを埋め込む部分を用意しておきます。
Bootstrapはプログレスバーの装飾に使用します。
<!-- apps\example_app\templates\example_app\index.html --> <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8" /> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <!-- jQuery,Popper.js,Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.4.1.min.js" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <title>プログレスバーサンプル</title> </head> <body> <div class="container"> <div class="card"> <div class="card-header"> プログレスバーサンプル </div> <div class="card-body"> <div id="progress">プログレスバー表示部分</div> <div id="result">処理結果表示部分</div> <div class="row"> <button class="btn btn-primary col-12" id="start_button"> 処理の実行 </button> </div> </div> </div> </div> </body> </html>
urlの設定
まずアプリのurls.pyを作成します。
# apps/example_app/urls.py from django.urls import path from . import views urlpatterns = [ path("", views.index, name="index"), ]
続けてプロジェクト本体のurls.pyにアプリのurlパターンを読み込ませます。
# config/urls.py from django.contrib import admin from django.urls import path, include from apps.example_app import urls as example_app_url urlpatterns = [ path("", include(example_app_url)), path("admin/", admin.site.urls), ]
表示確認
表示の確認のためmanage.py
と同階層で以下のコマンドを実行したのち、ブラウザからhttp://127.0.0.1:8000/
にアクセスして動作確認します。
python ../manage.py runserver #サーバ起動
これが表示されていればOKです。
これで、ガワは完成しました。
ここから、プログレスバーのロジックを実装していきます。
サーバサイド側
時間のかかる処理
サンプルとして時間のかかる処理を行う関数を作成します。
0.1秒ごとに1ステップ進み、10秒後に処理が終了する関数です。
10ループごとに実行されるmake_progress_func()
が行う処理は後ほど説明します。
# apps\another_app\do_something.py import time def slow_function(make_progress_func): """裏側で動いている時間のかかる処理""" for i in range(100): time.sleep(0.1) if i % 10 == 0: make_progress_func() return "処理完了"
早速この関数を、views.pyから呼び出せるようにしておきます。
# apps/example_app/views.pyに以下の内容を追記 from ..another_app.do_something import slow_function
進捗管理モデルの作成(Django)
進捗状況を管理するためのモデルをアプリ内のmodels.pyで定義します。
# apps/example_app/models.py from django.db import models class Progress(models.Model): """進捗管理モデル""" now = models.IntegerField("現在の進捗", default=0) total = models.IntegerField("全ステップ数", default=100)
早速、このモデルをviews.pyにインポートしておきます。
# apps/example_app/views.pyに以下の内容を追記 from .models import Progress
進捗管理インスタンス作成部分(Django)
時間のかかる処理を実行する前に呼び出される関数を定義します。
この関数はmodels.pyで定義したProgressのインスタンスを作成し、そのプライマリーキーを返します。
この関数が返すプライマリーキーをもとに様々な処理を行うことになります。
# apps/example_app/views.pyに以下の内容を追記 from django.shortcuts import HttpResponse, render, get_object_or_404 def setup(request): """進捗管理インスタンスを作成する""" progress = Progress.objects.create() return HttpResponse(progress.pk)
プログレスバー表示部分(Django)
プログレスバーの表示にかかわる関数を定義します。
この関数はGETパラメータのprogress_pk
に紐づく進捗管理インスタンスを取得し、その進捗度合いをパーセント換算してプログレスバーのテンプレートに渡します。
# apps/example_app/views.pyに以下の内容を追記 def show_progress(request): """時間のかかる関数を実行する""" if "progress_pk" in request.GET: # progress_pkが指定されている場合の処理 progress_pk = request.GET.get("progress_pk") progress = get_object_or_404(Progress, pk=progress_pk) persent = str(int(progress.now / progress.total * 100)) + "%" return render(request, "example_app/progress_bar.html", {"persent": persent}) else: # progress_pkが指定されていない場合の処理 return HttpResponse("エラー")
進捗を進める部分(Django)
プログレスバーの進捗度合いを進めるための関数を定義します。
この関数は引数に紐づく進捗管理インスタンスを取得し、progress.now
を10増やして更新します。
# apps/example_app/views.pyに以下の内容を追記 def make_progress(pk): """引数のプライマリーキーに紐づく進捗を進める""" progress = get_object_or_404(Progress, pk=pk) progress.now += 10 progress.save()
重い処理を呼び出す部分(Django)
時間のかかる処理を呼び出す部分を定義します。
functools
を用いて引数を固定したmake_progress(pk)
を引数としてslow_function()
を呼び出します。
# apps/example_app/views.pyに以下の内容を追記 import functools def set_hikisuu(pk): """引数を固定する""" return functools.partial(make_progress, pk=pk) def do_something(request): """時間のかかる関数を実行する""" if "progress_pk" in request.GET: # progress_pkが指定されている場合の処理 progress_pk = request.GET.get("progress_pk") result = slow_function(set_hikisuu(progress_pk)) return render(request, "example_app/result.html", {"result": result}) else: # progress_pkが指定されていない場合の処理 return HttpResponse("エラー")
追加分のurl設定
ここまでで定義した関数に紐づくURLを設定します。
# apps/example_app/urls.pyに以下の内容を追記 urlpatterns += [ path("setup/", views.setup, name="setup"), path("show_progress/", views.show_progress, name="show_progress"), path("do_something/", views.do_something, name="do_something"), ]
フロントエンド側
ブラウザの表示部分をJavascriptで実装していきます。
Djangoと表示の橋渡し
JavaScript部分を追記します。処理のフローは以下の通りです。
・画面上の処理開始ボタンが押される。
・/setup
にリクエストし進捗管理インスタンスのプライマリーキーを取得する。
・/show_progress
に定期的にリクエストしプログレスバーを取得し、画面上に反映しつづける。
・/do_something
にリクエストし処理結果を取得し、画面上に反映する。
<!-- apps\example_app\templates\example_app\index.htmlに追記 --> <script> //プログレスバー表示部分の初期状態 const progresshtml = '<div id="progress">プログレスバー表示部分</div>'; //処理開始ボタンが押された時の処理 $("#start_button").on("click", function (event) { console.log("start") let timer_id = 0; let url = "{% url 'setup' %}" $("#start_button").attr({ "disabled": true }) //進捗管理インスタンス作成部分 $.get(url, {}, function (data) { console.log("get") let pk = data console.log("Data Loaded: " + data); //プログレスバーを3秒ごとに取得開始 timer_id = setInterval(function () { ShowProgressBar(pk) }, 3000) //時間のかかる処理を開始 GetResult(pk) } ); //プログレスバーの取得 function ShowProgressBar(progress_pk) { $.get("{% url 'show_progress' %}", { progress_pk: progress_pk }, function (data) { console.log("Data Loaded: " + data); $("#progress").replaceWith(data) } ); } //時間のかかる処理 function GetResult(progress_pk) { $.get("{% url 'do_something' %}", { progress_pk: progress_pk }, function (data) { console.log("Data Loaded: " + data); //プログレスバー更新をやめる clearInterval(timer_id); //プログレスバー部分を元の状態に戻す $("#progress").replaceWith(progresshtml) //処理結果表示 $("#result").replaceWith(data) $("#start_button").attr({ "disabled": false }) alert("処理完了!") } ); } }); </script>
プログレスバー表示部分(テンプレート)
Bootstrap4のドキュメントを参考に、プログレスバー部分を構築します。
<!-- apps\example_app\templates\example_app\progress_bar.html --> <div id="progress"> <div class="progress"> <div class="progress-bar" style="width:{{persent}}"></div> </div> </div>
重い処理の処理結果部分(テンプレート)
この記事のアプリでは単にresult
の値を出力するだけのものにしておきます。
<!-- apps\example_app\templates\example_app\result.html --> <div id="result"> {{result}} </div>
動作確認
現在のファイル構成は以下のようになっているはずです。
|--apps | |--__init__.py | |--__pycache__ | |--another_app | | |--__init__.py | | |--__pycache__ | | |--do_something.py | |--example_app | | |--__init__.py | | |--__pycache__ | | |--admin.py | | |--apps.py | | |--migrations | | |--models.py | | |--templates | | | |--example_app | | | | |--index.html | | | | |--progress_bar.html | | | | |--result.html | | |--tests.py | | |--urls.py | | |--views.py |--config | |--__init__.py | |--__pycache__ | |--settings.py | |--urls.py | |--wsgi.py |--db.sqlite3 |--manage.py |--Pipfile |--Pipfile.lock
manage.py
と同階層で以下のコマンドを実行します。
python manage.py migrate #DBのマイグレーション python manage.py runserver #サーバ起動
ブラウザからhttp://127.0.0.1:8000/
にアクセスして「処理の実行」ボタンを押すと少しずつプログレスバーが進み、右端に到達するあたりで処理結果が表示されるはずです。
終わりに
時間のかかる処理を扱う関数と進捗を進める関数の結合を弱めるため、進捗を進める関数の中身を意識せずに済むように意識しました。
これによってテスト時に考慮すべき必要なケース数が少なくて済むはずです。
単体テストを学ぼう!① -自分のプログラムに自信を持ちたい-
こんにちは、ふじもんです。
朝、お布団からでるのが辛い季節になってきましたね。
いつのまにか出勤時に乗る電車が前よりも20分遅くなっていました。
遅刻ギリギリ。
さてさてお話は変わりまして。
私は業務でプログラムを書くときは、分析データの整形など、分析の前処理のスクリプトが主です。
そしてプログラムを組んでいるときに常々不安を抱えています。
それは
私のプログラム、本当に合ってる???
って。
自分で組んだプログラムが想定通りの挙動をしているかいつも不安です。
毎回処理途中のデータや最終的なアウトプットをちまちまと目視で確認はしているけど、
何せプログラムで処理したいデータなんて少ないはずがない。
それはもう膨大。
さて、どうしよっかな。
この不安感、なんとかして解消できないものか。
そう思い、上司に相談してみました。
上司「最近現場どう?」
私 「現場は順調なんですけど、ちょっと悩みが…」
上司「え。どしたの」
私 「自分でスクリプト書くじゃないですか。それを実行してみて、一見思い通りに動いてるように見えるんですけど、コレ本当に全てのデータに対して正しく動いてるのかなって…。」
上司「それならテストコード書けばいいんじゃない?」
私 「テストコード」
上司「…ふじもんって開発業務とかにおけるテストってどういうことするかわかる?」
私 「ばなな。」
上司「わからないなら素直に言おうか。じゃあまずテストとは何なのかを調べてみようか。そうしたら、自分のプログラムがちゃんと想定通りに動いてるか調べる方法とかもわかるかもね。」
テストってなんだ?
上司に言われるがままに、開発経験ゼロの私は早速テストについて調べてみました。
そもそも、開発業務に置けるテストには、大きく分けて以下の3種類があるようです。
・単体テスト
プログラム1つが期待した通りに動作するかテストを行う。
・結合テスト
複数のプログラムを組み合わせ、それらが一体として期待したとおり動作するかテストを行う。
・総合テスト
システムの全機能を最初から最後まで一通り運転させ、期待したとおり動作するのかテストを行う。
ふむなるほど。
プロジェクトによってどのテストを行うかはまちまちだそうですが、上記3つは大体行われるようです。
また、テストの範囲はプログラムに限らずモジュールや関数などにも適用され、厳密には決まっていません。
システム開発ではこれらを行うことにより、仕様通りに動くことを確かめてからリリースします。
つまり・・・?
私の悩みは自分の書いたプログラム1つが正しい挙動をしているか確認したいので、
上記の3種類の中で必要なテストは「単体テスト」に当たります。
単体テストってどうやるの!!! はやく!はやくこの不安感から解放されたいんだ!!
単体テストってどうやるの?
逸る気持ちで単体テストについて調べていると、単体試験の中にも2種類あって、観点が違うもよう。
確かに、プログラムを確認する観点を決めていなかったな…
漠然と入出力を見て間違いがないかを確認していたな…
それで、確認しても不安が残っていたのか。。。
これは重大な気付きです!!!
ブラックボックステスト
テスト対象の入出力に着目し、入力に対して期待した出力になるかを検証する。
その観点は、
同値分割
起こりうる全ての事象をいくつかのグループに分け、各グループから代表値を選んでテストを行う。
境界地分析
同値分割によって分けられた各グループの境界値付近をテストを行う。
ホワイトボックステスト
テスト対象の内部構造に着目し、条件分岐や繰り返しなどの各部分を確実に検証する。
その観点は、
命令網羅(C0)
テスト対象となるプログラムの命令文それぞれが、1回以上実行されるようにテストを行う。
分岐網羅(C1)
テスト対象となるプログラムに含まれる判定条件について、真となるケース、偽となるケースそれぞれが、1回以上実行されるようにテストを行う。
条件網羅(C2)
テスト対象となるプログラムの条件文について、真となるケース、偽となるケースそれぞれが、1回以上は実行されるようにテストを行う。
なるほど!!!
ブラックボックステストは、ソースコードはどうあれ入力に対して出力が正しいかを確認して、
ホワイトボックステストは、ソースコードの中身の挙動が正しいかを確認するってことですね。
お、確かにこの観点でプログラムの挙動をチェックしていけば、「なんとなく大丈夫そう」よりは圧倒的に不安が取り除かれる気がする…!
これは不安から解放される日も近いのでは!
テスト基準のモノづくり -テスト駆動開発-
よーし、じゃあさっそくテスト…って思ったけど、テストするにもテスト対象のモノがない。
なら作れば良いじゃない。
じゃあテストをするための簡単なシステムを作ろう。
せっかくテストのことを理解するなら、テストしやすいものを作りたい。
何をどんな風に作ろうかなと調べていると、テスト駆動開発という言葉を発見しました。
テスト駆動開発(TDD:Test-Driven Development)
テストファーストという考えに基づいた開発手法。
テストファーストとは、まず本番で使用するプロダクションコードを書く前に何をテストするかを決め、テストコードを先に書くのが特徴。
そのテストファーストという考え方を組み込んだテスト駆動開発は、最低限必要なテストを積み重ねていき、少しずつ確実に実装できるそう。
そして、この手法の大きなメリットとして確実に動くコードを読みやすく綺麗に書けること。
よく自分の書いたコードがあとで読めなくなったり、学生時代に「コードの汚さは頭の中の汚さ」とゼミの先生に言われていた私にとって、もうこんな最適な開発手法あったの?もっと早く知りたかった。
確実に動くコードを書けると、心理的負担も減りますしね!
他にもデバッグの時間が短くなったり、実装コードの修正があった場合に修正と無関係な部分を破壊していないかをチェックするリグレッションテストにも役立つメリットもあるようです。
まとめ
今回の記事では以下のことについて調べて記載しました。
開発におけるテストには以下の種類がある。
・単体テスト
・結合テスト
・総合テスト
それぞれを行うことにより、一つのシステムのバグの検証や確実性を高めてからリリースできる。
単体テストは以下の種類がある。
・ブラックボックステスト
・ホワイトボックステスト
それぞれを行うことにより、プログラムの入出力からソースコードの挙動までを確認することができる。
テストファーストの開発「テスト駆動開発」というものがあり、プロダクションコードよりテストコードを先に書くことにより、
確実に動くコードを綺麗に書けるメリットがある。
次回の記事ではテスト駆動開発でプログラムを実装しながら、テストについてより理解を深めていきます!
SKYWILLハッカソン2019冬の陣
今回のお題は【フレッシュ】
初めて参加される方もいらっしゃいましたが、雑談しながら楽しく開催することができました。
チーム 1:写真共有アプリのフレーム作成
・API の知識を深める
・node + express + mongodb の知識を深める
API 設計
URL | メソッド | 内容 |
---|---|---|
/ | GET | 全ユーザ取得 |
/:id | GET | ユーザー取得 |
/ | POST | ユーザー登録 |
/:id | PUT | ユーザー更新 |
/:id | DELETE | ユーザー削除 |
例
$curl -X POST http://localhost:3000/ -H "Content-type: application/json" -d '{"userid":"1","username":"sugi","password":"password"}' {"user":{"_id":"5dd535f8c45a380d95a7555b","userid":1,"username":"sugi","password":"password"}} $curl -X GET http://localhost:3000/ -H "Content-type: application/json" [{"_id":"5dcfa5eadebf7f5a6f912840","userid":2,"username":"ryu","password":"password","__v":0},{"_id":"5dd535f8c45a380d95a7555b","userid":1,"username":"sugi","password":"password","__v":0}] $curl -X GET http://localhost:3000/1 -H "Content-type: application/json" {"_id":"5dd535f8c45a380d95a7555b","userid":1,"username":"sugi","password":"password","__v":0} $curl -X PUT http://localhost:3000/1 -H "Content-type: application/json" -d '{"username":"ryu"}' {"_id":"5dd535f8c45a380d95a7555b","userid":1,"username":"ryu","password":"password","__v":0} $curl -X DELETE http://localhost:3000/1 -H "Content-type: application/json"
使用技術
・JS
・Node
・Express
・MongoDB
・Docker
感想
・新しい言語を触って知識を深められた。
・ペアプロスキルが向上した。
・API の知識が深まった。
チーム 2:フレッシュな Web サービス
・あなたのフレッシュ度を判定する心理テスト
5 つの質問に答えると、フレッシュ度を判定してくれるスグレモノ。
・画像のフレッシュ度を判定する深層学習
画像のフレッシュ度を計算してくれる。
※画像はぱくたそ様よりお借りしました。(モデルリリース取得画像)
photo by すしぱく / model by yumiko
・フレッシュな気持ちになれる Google Chrome 拡張機能
背景など特定の要素だけフレッシュな色に変える魔法。
使用技術
・Python, Bottle, Jinja2
・HTML5, JavaScript, CSS
・TensorFlow, Keras
感想
・深層学習は1時間でできた(精度無視)けど、まだフロントで躓くことが多い。
・自分の技術力を見直せた。
・ゼロからモノづくりしたことがなかったので、いい経験が出来た。
チーム 3:音楽データのパート分割・加工・合成
・最新の音楽素材分離エンジン「spleeter」を用いて、最新技術の使用を体験する。
・音声データを「ボーカル、ベース、ドラム、ピアノ、その他」の5分割をした。
・「pyaudio」ライブラリを用いて音の高さ・速度を変化させた。
使用技術
感想
・最新の技術なので記事が3つ程度しかなかったが、プログラムの構造を見て自分で考える良い機会となった。
・最新の技術を用いて音声データの分割・合成を楽しめるいい機会となった。
・ピッチの変更、instrumental や 8bit 曲の作成など個人使用の範囲で楽しめる可能性を感じた。
チーム 4:いつも通る道のりに、寄り道を加えて案内してくれるアプリ
いつもと違う道のりを歩いてみることで新しい発見を促す。
使用技術
・HTML5(GeoLocationAPI)
・AWS(S3)
・Google Maps API
・geolib
・ServiceWorker
感想
・PWA 化をするため、ServiceWorker, Manifest ファイル配置などを行ったが、うまく動かなかった。(ホーム画面に追加ボタンでホームに追加されない…)
・HTML5 の位置情報を取得する API(GeoLocationAPI)が、GoogleChrome では HTTPS 接続でないと動作しなかった。
全体を通して
各チーム、発表時に動くもの、今後のベースなど作成できていて安心しました。
短い時間で何かを作ることはとても大変なことです。
チームのスキルセットを把握、役割分担し、作るものを決める。
やりたいこと、好きなことができるわけではないですが、チームとして手や頭を動かす必要があります。
苦労することもありますが、非日常的、刺激、チーム開発体験などハッカソンに参加する良さはたくさんあります。
今回は社内イベントとして開催しましたが、今後もconnpassなどで募集して社外向けイベントとして開催することもあると思います。
これを機にハッカソン参加してみようかな~と思ってくださったら幸いです。
参加してくださったみなさま、本当にありがとうございましたー!
データエンジニアとしての第一歩を終えて
皆様お疲れ様です。
tsudaです。
新卒の研修を5月中旬に終え, そのまま現場に配属され早5か月が立ちました。
配属された現場も丁度今月で離任ということで, 研修と現場での業務について所感を述べます。
自己紹介
大学ではグラフ理論, 数理計画法, 統計学, 数値解析等を学んでいました。
プログラミング経験はCとPython, どちらも基礎文法レベルですが習得しました。
現場ではデータ分析ソフトウエアの環境構築, 運用に携わっていました。
研修で学んだこと
主にPythonを用いて"データ分析"の一連の流れをハンズオンで学びました。
手法の中身について詳しく掘り下げるというより, とにかく"データ分析"を流れでやってみるという部分に重点を置いており, 体系的に学ぶことでデータ分析業務に必要な能力が研修を学ぶ前よりも明確にすることができました。
その他にはLinuxコマンドの基礎を学び, 現場では構築作業の際に非常に役に立ちました。
携わった業務
PostgreSQLのユーザ権限設定
PostgreSQLを用いてユーザー, データベースの作成, 削除も行っていましたが,
「どのユーザーがどのデータベースのどのスキーマのテーブルを参照できればいいのか」
というアクセス権限の設計を主に行っておりました。
トライ&エラーで試すことができる環境もあり, パズルのようで楽しかったです。
運用ツールの作成
主に下記のツールの作成&単体試験を行いました。
bash:データベースのバックアップ, バキューム
VBA:csvファイルからグラフを自動生成
PowerShell:S3からテーブルデータ取得, インポート
現場で学べたこと
5か月と短い期間であったにもかかわらず, 上に述べたように様々な体験をすることができました。
これらの体験を通して重要さを痛感し勉強になったものが以下の2つでした。
わかりやすいコードを書く
現場に入るまでに書いたスクリプトは基本的にその日の内に完結するので, 最低限必要な動作を保証したうえで変数や関数の名前, コメントによる動作の説明等は便宜的に書いていました。
しかし現場ではそれが通用しませんでした。
後日仕様変更が起きていざ修正しようと思った時に, 謎の変数"s"や, なにかのユーザを取ってきているであろう"get_user"関数がなんの説明もなしに置かれているのです。
結局そのコードを理解するのに, 作成するのと同じくらいの時間をかけてしまいました。
それ以来, 引き継ぐ相手の為, 何より自分の為に後から見ても理解しやすいコードを書くことを意識するようになりました。
再現性のある手順書を書く
基本的に手順書というものは, 誰かに引き継ぐ, 共有するために存在しています。
「その手順書を読めば, 誰もが同じ結果を再現できる」こと,
つまりは再現性が必要ということが, 手順書の作成を通してわかりました。
次への意気込み
「知識とは球体である」と誰かは言いました。
知識を蓄えると球は膨らみ球の体積, つまり「知っていること」は増えるが, 球の外側と触れる面である表面積, つまり「知らないこと」も増えていく。
研修, 現場を通して様々な「知らないこと」を知ることができました。
次の現場でも今の知識と技術を生かし, 増やし, 成長していきたいと思います。