未経験出身エンジニア、早くも勤務から3年目になりましたosです。
今回は私が新人プログラマ時代にあるプロジェクトにおいて作成したJUnitテストソース作成時に、
ドツボにはまってしまっていた現象について共有をしたいと思います。
今回私がドツボにハマった現象は、”static”修飾子について正しく理解ができていなかったために発生した現象です。
この現象のおかげで”static”修飾子と向き合うことに成功し、今では正しく理解ができました。
もし興味がございましたら最後までご閲覧いただけますと幸いです。
今回発生した現象
未経験出身だと、プログラムの勉強のために参考書やサイトを調べて勉強する機会が多くあると思います。
その中で、参考資料等に記載されているサンプルのメソッドには大体”static”修飾子が一緒に記載されているケースが多くある印象です。
特に初心者がJavaの勉強を始めて一番最初に見るメソッドはかなりの高確率でmainメソッドだと思われます。
mainメソッドにはstaticが必ず必要なので、メソッドの書き方をmainメソッドの書き方で覚えてしまう傾向が多くあると思います。
恥ずかしながら私も当時、”static”について正しい理解ができていなかったため、
よくプログラムで使うBeanのフィールド変数等に”static”をつけてしまう悪いクセがありました。
例としては以下のような感じです。
class MyObj{ private static int cnt; public String addCnt(){ cnt++; } public int getCnt(){ return cnt; } }
上記ソースの2行目のint cnt の前にある”static”が今回私を苦しめる要因となった忌々しき”static”となります。
上記ソースで行いたかった機能は次のような動作を行っています。
1.MyObjクラスのインスタンスを作成した外部メソッドから、addCnt() を実行する度にprivate変数のcntを
1ずつカウントアップさせたい。
2.cntの数値を取得するためのgetter、getCnt()を用意。
さて、私が関わったプロジェクトでは、単体テストとして作成したプログラムに対してJUnitの単体テストソースも作成する必要がありました。
作成したテストソースのサンプルは以下のような内容です。
@Test void testMethod(){ MyObj obj = new MyObj(); obj.addCnt(); assertEquals(1, obj.getCnt()); }
上記テストソースの確認内容は次の内容です。
・obj.addCnt() が1回だけ実行されたので、private変数のcntの値は「1」であるか
また、単体テストソースの他にもテストスイートクラスも作成する必要もありました。
テストスイートとは簡単にまとめますと「複数のテストケースを一括りにしたもの」です。
テストスイートクラスをカバレッジで実行すると、テストスイートクラスで指定したテストソースを一括で実行して動作確認できます。
上記ソースはMAVENプロジェクトで作成していたので、作成したソースをビルドする際に、
作成したテストソースを全て実行して、作成した全テストケースが成功してからビルドが行われるようになっていました。
このとき、上記テストソースクラスとテストスイートクラスは別クラスだったので、作成したテストソースが実質2回ずつ起動されているような状況となっていました。
(1回目:作成したテストソースそのもの 2回目:テストスイートクラスから実行させられた同テストソース)
粗方テストソースの作成が完了したので、テストソースもテストスイートクラスもを一括で実行してみました。
その結果…
1回目のテストソースそのものでは成功するのに、
2回目のテストスイートから実行させられた同じテストメソッドtestMethod が失敗していました。
2回目実行時のエラー原因は「expected<1> but obj.getCnt() is <2>」と記載されていました。
直訳すると「obj.getCnt()の実行結果が”1″想定なのに”2″になっている」エラーです。
どうやらテストソースの中で作成したMyObj クラスのインスタンスのcntが想定と異なる結果になっているようです。
1回目が成功しているということもあって、2回目だけで失敗する理由がどれだけ調べても分かりませんでした。
(当時、JUnitについても正しい理解ができていなかったため、JUnit側に原因があると思い込んでしまっていたのもドツボにハマった理由です…)
事象発生の原因
さて、上記の原因は結論から言うと、初めの話に戻りますが、
MyObj のフィールド変数cnt につけていた”static” が原因でした。
“static”があるとcntはどうやら違うオブジェクトの数値としては扱かってくれないようで、
cnt++; が1回目のテストソースで1上がった後、
2回目のテストスイート実行時に動いたcnt++; で更にもう1プラスされてしまい、
結果、obj.getCnt() は「2」と表示されるようになっていたみたいでした。
“static”を削除すると無事に想定通りの結果となり、単体テストも無事に完了しました。
まとめ
今回の現象を踏まえて思ったことですが、
“static”は未経験の人に向けた参考書のサンプルソース等で基本的に書かれていることが多いので、
未経験出身だとつい自作メソッドのpublic や private の後にオマケで書いてしまいがちだと思います。
ですが、そんな”static”も正しく理解せずに記載をしていると、今回の事例のように予期しない動作を招いてしまうんだなと痛感した出来事でした。
“static”とは、訳すと「静的」の意味を持っています。
処理開始と同時にメモリ上にデータを保管する場所を確保して、その確保した陣地に値を入れて保管しているようです。
staticで宣言された変数やメソッドは、処理開始時に確保した陣地を一回使い終わっても解放しないため、
その結果”static” cnt は、1回目のテストソース実行後も中に値を残したままの状態だったみたいです。
もっと言うと、今回の「static int cnt;」のようにcntにstaticをつけてしまった場合は、
//上記サンプルのaddCnt()をstaticメソッドに変更した場合 MyObj.addCnt();
ようにクラス名.addCnt();をした場合と、
MyObj obj = new MyObj(); obj.addCnt();
上記のようにインスタンスを生成後、インスタンス名.addCnt();をした場合、
どちらも同じint cnt に対して+1してしまっているようです。
ですので、
MyObj.addCnt(); MyObj obj = new MyObj(); obj.addCnt();
と記載してから、
obj.getCnt();
を実行すると、得られる結果は「2」になります。
static こわい…
以上、”static”に振り回された未経験出身エンジニアのぼやきでした。
余談ですが、この”static”に関する知識はJava Silver 試験の問題にも出題されていることが多いです。
ですので、もしJava Silver の受験を考えておられる方は
MyObj.addCnt(); MyObj obj = new MyObj(); obj.addCnt(); System.out.println(obj.getCnt());
obj.getCnt()の出力結果が「2」だということは覚えていて損はないと思います。
ここまでのご閲覧、誠にありがとうございました。