読者です 読者をやめる 読者になる 読者になる

196の日記

完全に開発メモと雑談、その他忘れそうな計算式などを書き溜める場所になっています!

scalatest Mockテスト

scala play 勉強

playframeworkでのモックテストを実装することになって色々わからなくて苦しんだのでメモ。

そもそも

DI(依存性の注入)をしっかり意識して実装しないと、ユニットテストが行いにくい(ユニットテストのためにIDを意識すると言っても大丈夫なくらい)

モックオブジェクト

モックオブジェクトを作ることで、テストを行いたいメソッド内の不必要な処理を置き換えることが可能。

テストクラス

ユーザとメールアドレスを紐付けるUserMailLinkerをテストするクラスを作る。MockitoSugerを使用することでMockオブジェクトを作成可能に。

class UserMailLinkerTest extends FlatSpec with Matchers with MockitoSugar

今回はUserMailLinker内に実装した以下のメソッドをテスト(正確にはUserMailLinkerはtraitとなっており、それをextendsする形でUserMailLinkerImplクラスを実装、その中にこのメソッドを定義している)。メソッド内のloginMailDALはデータベースにアクセスする感じのメソッドが格納されている。メソッド外クラス内に変数として定義されている。

val loginMailDAL = new LoginMailDAL() //ちょっと雑に書いたのでここ違うかも。とにかく、変数として実装しておくのがミソ
override def checkEmailExist(email: String) = {
    loginMailDAL.getUserByEmail(email).map(_ match {
      case Some(user) =>{
        logger.info(s"Email:${user.email} is already exist")
        true
      }
      case None => false
    })
  }

このメソッドをテストするには、loginMailDALの結果を明確にこちらで操作できると都合が良い。同時に、テストするときにDBにアクセスするのは良くないし、依存がどーのこーのとID面から見てもよろしくない。そこで、使用するのがモックオブジェクト。UserMailLinkerTest内に以下のように実装する。

  val mockLoginMailDal:LoginMailDAL = mock[LoginMailDAL]
  val linker = new UserMailLinkerImpl(){
       override val loginMail = mockLoginMailDal
 }

これまた少し雑に書いたけれど、こんな感じで、モックオブジェクトを生成してそれでloginMail内の変数を上書きしてしまうという技。後は上書きに使用した、mockLoginMailDal内のメソッドを弄ってやると、うまくテストが可能。loginMailDal内のgetUserByEmail(email:String)を書き換えるには、UserMailLinkerTest内に以下の文を追加。

Mockito.when(mockLoginMailDal.getUserByEmail("email@exist.com")).thenReturn(loginMail) //loginMailはユーザ情報を保持する

これで、先ほど生成したモックオブジェクトが持つgetUserByEmail()は、”email@exist.com”という文字列を受け取ったら他の処理は何もせずに、ただloginMailをリターンするだけのメソッドになる。こうすることで、UserMailLinker内のcheckEmailExist()に実装されているものも上書きされ、メソッドのテストのみに集中できる。

/** 既に登録してあるアドレスならtrueが帰ってくる */
  "checkEmailExist" should "check Email is Already Registered" in {
    val f:Future[Boolean] = linker.checkEmailExist("email@exist.com").run()
    ScalaFutures.whenReady(f){ f=>
      assert(f == true)
    }
  }

ちなみに、例外をthrowするものをテストしたい場合はintercept[T]()を使用する。これを使わないと意図させて起こした例外も、結局ただの実行中に起こったエラーとして扱われてしまう。以下は、メアドとパスワードが違った場合、エラーを投げるgetLoginMailByEmailAndPassword()メソッドをテストしている。

"getLoginMailByEmailAndPassword" should "Error by incorrect information" in{
    intercept[Exception]( linker.getLoginMailByEmailAndPassword("email@nonExist.com", "Password1").run())
  }

ユニットテスト

ユニットテスト

www.techmatrix.co.jp


単体テストユニットテスト)は、プログラムを構成する比較的小さな単位(ユニット)が個々の機能を正しく果たしているかどうかを検証するテスト。関数やメソッドがそのテストの対象となる。プログラムが全体として正しく動作しているかを検証する結合テストより断然早い段階で行われる。

利点

実装時にテストを行うため、問題の特定や修正が容易になる。
実装した人がその直後におそらくテストケースを実装するため、妥当性の高いテストケースを残せる。

欠点

実装者に負担。
実装者自身にもある程度の知識が要求される。
当然時間を取られる。また時間の関係でテストが省かれた場合、プロジェクト全体でテストが省略されたり実装されてたりと、バラバラになる可能性がある。

種類

ホワイトボックステスト
テスト対象のメソッドor関数の内部に着目。条件分岐や繰り返しなどの各部分を確実にテストする。

ブラックボックステスト
テスト対象のメソッドor関数の入出力に着目。期待する機能を満たしているかをテストする。

Dependency Injection 依存性の注入

まずここで概念を掴んで
qiita.com


このページでもう少し掘り下げると、すんなりと概要が頭に入ってきます。
itpro.nikkeibp.co.jp

ざっと掴んだ感じ、、

Aが必要とするBというものを、A内に含めずに、実行時に動的にAをBに渡そうという思考(多分)
似た挙動をAで使用する時、渡すものをBではなくCにすれば動くようにしておけば、実装も楽だしどこを変更すれば良いかもわかりやすく、なおかつ使い回しも可能でいいよねって感じ?(使い回しは副産物で、使いまわせるぐらい機能が分離されていてメンテナンスを含めて使用が行いやすいってことかな)

IDのメリットデメリット

コードが簡潔になり開発期間が短くなる <-> スタートアップ時のコード量(仕事量)が増える
階層構造が綺麗に分離された状態になる <-> クラスやファイルがたくさん生成される
特定のフレームワークへの依存が少なくなるため、柔軟になる

git 随分前にforkしたローカルリポジトリをfork元の最新の状態に更新する

github

blog.local-c.com

qiita.com

slick3を使用するときにお世話になったサイト

scala slick play

qiita.com