ベース記事はこちら(最新版もこちらにございます。)
http://qiita.com/yharada/items/b238750d21c9fb63928e
0.前座
ScalikeJDBCを使ってのMySQL操作で遊んでみました。
Play Frameworkのプラグインもあり、導入コストもかなり低く重宝しています。
導入の仕方についての記事は、他の方々がよりよい感じで書いてくれてるとは思うので割愛。
プロダクトコードだと、テストでは本番のDBにつないでほしくない!
そういった方のための記事です。
設定はこんな感じです。
build.sbt
//前略 scalaVersion := "2.11.1" //中略 libraryDependencies ++= Seq( "org.scalikejdbc" %% "scalikejdbc" % "2.1.4", "org.scalikejdbc" %% "scalikejdbc-config" % "2.1.4", "org.scalikejdbc" %% "scalikejdbc-interpolation" % "2.1.4", "org.scalikejdbc" %% "scalikejdbc-test" % "2.1.4" % "test", "org.scalikejdbc" %% "scalikejdbc-play-plugin" % "2.3.2", "org.scalikejdbc" %% "scalikejdbc-play-fixture-plugin" % "2.3.2", "mysql" % "mysql-connector-java" % "5.1.33", //その他ライブラリは省略 )
plugins.sbt
//mysql libraryDependencies += "mysql" % "mysql-connector-java" % "5.1.33" //DBコード自動生成 addSbtPlugin("com.github.seratch" %% "scalikejdbc-mapper-generator" % "[1.6,)")
project/play.plugins
10000:scalikejdbc.PlayPlugin 11000:scalikejdbc.PlayFixturePlugin
conf/application.conf
# Database configuration db.default.driver="com.mysql.jdbc.Driver" db.default.url="jdbc:mysql://localhost:3306/hoge" db.default.user="hogeUser" db.default.password="hogePassWord" # Connection Pool settings db.default.poolInitialSize=10 db.default.poolMaxSize=20 db.default.poolConnectionTimeoutMillis=1000
1.scalikejdbc-gen直後だと動かない!?
ScalikeJDBCのプラグインscalikejdbc-genは非常に楽で、コマンド一発でテスト書いてくれます。
$ #play Framework 2.3で試したので、SBT使う人はsbtと読み替えてくださいね) $ ./activator "scalikejdbc-gen [TableName]"
が、このままだとTest - Failもしくはerrorしてしまいます。
なぜ動かないのかを説明こみで、動くテストをつくっていきましょう。
SampleSpec編集前
package dbAccess.hoge import scalikejdbc.specs2.mutable.AutoRollback import org.specs2.mutable._ import scalikejdbc.SQLInterpolation._ class SampleSpec extends Specification { val s = Sample.syntax("s") "Sample" should { "find by primary keys" in new AutoRollback { val maybeFound = Sample.find(1L) maybeFound.isDefined should beTrue } "find all records" in new AutoRollback { val allResults = Sample.findAll() allResults.size should be_>(0) } "count all records" in new AutoRollback { val count = Sample.countAll() count should be_>(0L) } "find by where clauses" in new AutoRollback { val results = Sample.findAllBy(sqls.eq(s.id, 1L)) results.size should be_>(0) } "count by where clauses" in new AutoRollback { val count = Sample.countBy(sqls.eq(s.id, 1L)) count should be_>(0L) } "create new record" in new AutoRollback { val created = Sample.create(id = 1L, name = "MyString") created should not beNull } "save a record" in new AutoRollback { val entity = Sample.findAll().head val updated = Sample.save(entity) updated should not equalTo(entity) } "destroy a record" in new AutoRollback { val entity = Sample.findAll().head Sample.destroy(entity) val shouldBeNone = Sample.find(1L) shouldBeNone.isDefined should beFalse } } }
importライブラリの誤り?
sqlsのインポートがオカしいっぽいので修正します。
コンパイル通らない系の単純な誤りなので、修正のみ。
修正前
import scalikejdbc.SQLInterpolation._
修正後
import scalikejdbc._
コンフィグの初期化が存在しない!
- DBs.setupAllを実行する。
テストはひとつのテスト内で完結することが望ましいので、できれば1つ1つに書くほうがいいとかんがえますが、
何度も書くとめんどくさいのでコンストラクタで書いてしまえばいいと思います。
コンフィグ初期化
scalikejdbc.config.DBs.setupAll //コンフィグのセットアップ //Append DB切り替えでコンフィグが決まってる場合にはこちらでも。 //scalikejdbc.config.DBs.setup('test) //db.test.* の読み込みのみの場合はこちら
アップデートがアップデートしていないテストに!
よくよくみるとアップデートしてないやーん!というテストになってます。
アップデート変更前
"save a record" in new SampleAutoRollbackWithFixture { val entity = Sample.findAll().head val updated = Sample.save(entity) updated should not equalTo (entity) }
なので、アップデートさせましょう
アップデート変更後
"save a record" in new SampleAutoRollbackWithFixture { val entity = Sample.findAll().head val updated = Sample.save(entity.copy(name = "Changed")) updated should not equalTo (entity) }
空のテーブルだと1行目がないからうごかない
当たり前ですよね;レコードのないテーブルでfindしても…という話です。
ただ、テストのためだけに1行追加するのもバカバカしい。
AutoRollBackとFixtureを使おう!
ScalikeJDBCはテストにおいても非常に優れており、AutoRollBackとFixtureという機能があります。
それを使ってコードを追加します。
やり方は簡単で、AutoRollbackを継承して「fixture」をオーバーライドします。
SampleAutoRollbackWithFixture
trait SampleAutoRollbackWithFixture extends AutoRollback{ override val fixture = { //ダミー用のDB作成コードを書く SQL("insert into sample values (?, ? ,?)").bind(1, "MyString", "http://test.com").update.apply() } }
そして、今まで new AutoRollbackとしてたところを新しく作ったTraitに変更します。
//"find by primary keys" in new AutoRollback { "find by primary keys" in new SampleAutoRollbackWithFixture { //省略
主キーが重複
上記で作成した自前のAutoRollbackを使用すると主キー重複でテストがこけてしまうので、追加するレコードと異なる主キーの値を設定します。
(このテストのみ、自前のAutoRollbackを使用しないのも手です)
クリエイト(インサート)変更前
"create new record" in new SampleAutoRollbackWithFixture { val created = Sample.create(id = 1L, name = "MyString") created should not beNull }
クリエイト(インサート)変更前
"create new record" in new SampleAutoRollbackWithFixture { val created = Sample.create(id = 2L, name = "MyString") created should not beNull }
3. DBを切り替えよう!
テスト用のDB設定を追加
ScalikeJDBCを調べているとDBの切り替え方はテストで使用できそうなDBの切り替え方は2種類あるっぽいので両方記載します。
Aパターン
(任意名).db.default.* で定義する方法
conf/application.conf(Aパターン)
# テスト用サンプルA # Database configuration test.db.default.driver="com.mysql.jdbc.Driver" test.db.default.url="jdbc:mysql://localhost:3306/hogeTest" test.db.default.user="hogeTestUser" test.db.default.password="hogeTestPassWord" # Connection Pool settings test.db.default.poolInitialSize=10 test.db.default.poolMaxSize=20
Bパターン
db.(任意名).* で定義する方法
conf/application.conf(Bパターン)
# テスト用サンプルB # Database configuration db.test.driver="com.mysql.jdbc.Driver" db.test.url="jdbc:mysql://localhost:3306/hogeTest" db.test.user="hogeTestUser" db.test.password="hogeTestPassWord" # Connection Pool settings db.test.poolInitialSize=10 db.test.poolMaxSize=20 db.test.poolConnectionTimeoutMillis=1000
切り替え方
Aパターン
Aパターンの場合は、コンフィグの初期化で切り替えます。
2章で記載した「scalikejdbc.config.DBs.setupAll」を別のコンフィグで読み込むメソッドに置き換えます。
コンフィグ初期化
//test.db.default.*を読み込む scalikejdbc.config.DBsWithEnv("test").setupAll
Bパターン
チュートリアルどおりですが、AutoRollbackの継承で定義します。
SampleAutoRollbackWithFixture
trait SampleAutoRollbackWithFixture extends AutoRollback { // db.test.*を読み込む override def db = NamedDB('test).toDB }
切り替え方まとめ
DBアクセス部だけのテストではパターンBでいい気もします。
ただ、running(FakeApplication())やBeforeExample、AfterExampleを使用してDBアクセスのテストをする場合には、Aパターンの方が取り回しがいい気がします。
まとめ
僕自身はパターンAを採用しましたので、パターンAでのやり方の解法の一例をあげておきます。
多少は修正が必要なモノの、AutoGenerateはテストまで自動生成してくれるので非常に便利です。
SampleSpec編集後(Aパターン)
package dbAccess.hoge import scalikejdbc.specs2.mutable.AutoRollback import org.specs2.mutable._ import scalikejdbc._ sealed trait SampleAutoRollbackWithFixture extends AutoRollback { override def fixture(implicit session: DBSession) { SQL("insert into sample values (?, ? ,?)").bind(1, "MyString", "http://test.com").update.apply() } } class SampleSpec extends Specification { val s = Sample.syntax("s") config.DBsWithEnv("test").setupAll "Sample" should { "find by primary keys" in new SampleAutoRollbackWithFixture { val maybeFound = Sample.find(1L) maybeFound.isDefined should beTrue } "find all records" in new SampleAutoRollbackWithFixture { val allResults = Sample.findAll() allResults.size should be_>(0) } "count all records" in new SampleAutoRollbackWithFixture { val count = Sample.countAll() count should be_>(0L) } "find by where clauses" in new SampleAutoRollbackWithFixture { val results = Sample.findAllBy(sqls.eq(s.id, 1L)) results.size should be_>(0) } "count by where clauses" in new SampleAutoRollbackWithFixture { val count = Sample.countBy(sqls.eq(s.id, 1L)) count should be_>(0L) } "create new record" in new SampleAutoRollbackWithFixture { val created = Sample.create(id = 2L, name = "MyString") created should not beNull } "save a record" in new SampleAutoRollbackWithFixture { val entity = Sample.findAll().head // UPDATE内容は自分で記載が必要 val updated = Sample.save(entity.copy(name = "Changed")) updated should not equalTo (entity) } "destroy a record" in new SampleAutoRollbackWithFixture { val entity = Sample.findAll().head Sample.destroy(entity) val shouldBeNone = Sample.find(1L) shouldBeNone.isDefined should beFalse } } }