Research libraries & Apply for play-scala unit test
Updated:Categories: Playframework
Tags: #Scala #TDD #Play framework
OverviewPermalink
Play framework v2.5 for scala 에서 unit test를 적용을 위한 라이브러리 조사 및 초기 적용 방법 소개
유닛 테스트(unit test)는 컴퓨터 프로그래밍에서 소스 코드의 특정 모듈이 의도된 대로 정확히 작동하는지 검증하는 절차 From wiki
LibrariesPermalink
ScalaTestPermalink
- ScalaTest는 Scala 에코 시스템에서 가장 유연하고 유명한 testing tool
- 테스트할 수 있는 대상: Scala, Scala.js, Java code
Spec2Permalink
- Specs2 in Play2:
libraryDependencies += specs2 % Test
in build.sbt
scalacheck-shapelessPermalink
- Github
libraryDependencies += "com.github.alexarchambault" %% "scalacheck-shapeless_1.13" % "1.1.5"
in build.sbt
scalatestplus-playPermalink
- Github
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "1.5.1" % Test
in build.sbt
ConditionPermalink
아래 조건들은 테스트 케이스를 적용할 때에 고민한 지극히 개인적인 의견임.
- A.
DAO
함수들을 테스트할 경우, 연결되어 있는 개발 DB에 저장하고 싶지 않다.- 개발 DB의 역할을 생각해보면, 굳이 테스트할 때 개발 DB에 저장하는 것을 피할 필요는 없다고 생각
- 하지만,
DAO
의 함수 기능 테스트에서 개발 DB에 저장되다면 개발 서버 API에 문제 발생하여 협업 문제 발생.Service
단에서 여러DAO
함수들을 함께 사용하기 때문.
- B. 테스트 데이터는 지정된 값만을 테스트하는 것이 아니라, 각 테스트하는 함수의 제한 조건에 충족하는 데이터들을 랜덤 생성하고 싶다.
- 이름이 50자 이하면 유의미한 이름을 넣는 것이 아니라, 길이가 50 이하인 랜덤 String 생성
- 지정된 값만 테스트할 경우, 예외 상황이 생기는 것을 검사할 수 없을 것 같음
ImplementationPermalink
조건 A
를 만족하기 위해, DB config을 바꾼 서버 어플리케이션(fakeApp
)을 실행하여, 테스트.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import play.api.db.slick.DatabaseConfigProvider | |
import play.api.inject.guice.GuiceApplicationBuilder | |
import scala.reflect.ClassTag | |
// Apply to the other object by using Cake Pattern | |
trait Inject { | |
// Switch DB config for testing | |
private val testDBConfig = Map( | |
"slick.dbs.default.driver" -> "slick.driver.H2Driver$", | |
"slick.dbs.default.db.driver" -> "org.h2.Driver", | |
"slick.dbs.default.db.url" -> "jdbc:h2:mem:db_scheme_name;DATABASE_TO_UPPER=false;MODE=MYSQL;INIT=runscript from './test/db/create.sql'\\;runscript from './test/db/req_init.sql'" | |
) | |
lazy val appBuilder: GuiceApplicationBuilder = new GuiceApplicationBuilder().configure( | |
testDBConfig.toSeq: _* | |
) | |
lazy val injector = appBuilder.injector() | |
lazy val fakeApp = appBuilder.build() | |
lazy val dbConfigProvider: DatabaseConfigProvider = injector.instanceOf[DatabaseConfigProvider] | |
def inject[T : ClassTag]: T = injector.instanceOf[T] | |
} |
- test할 DAO들을 가지고 있는
TestDaos
.Inject
를 Mixin 함
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kim.jungbin.daos.Dao1 | |
import kim.jungbin.daos.Dao2 | |
import play.api.Application | |
// fakeApp은 trait Inject을 Mixin하여 사용 | |
trait TestDaos extends Inject { | |
// Test할 DAO 객체 fakeApp에 inject | |
// trait Inject의 fakeApp은 DB설정이 dao unit test를 위한 환경으로 변해있음 | |
lazy val testDao1: Dao1 = Application.instanceCache[Dao1].apply(fakeApp) | |
lazy val testDao2: Dao2 = Application.instanceCache[Dao2].apply(fakeApp) | |
} |
조건 B
를 만족하기 위해, 테스트 함수의 입력 데이터 랜덤 생성- 실제 적용시, 함수 입력 값 중 하나가 Scala Enumeration으로 되어 있었는데, 그 중 하나 값에서 에러 발생하는 경우가 있었음. 그래서 테스트할 때 Success, Fail이 랜덤하게 발생. 테스트를 한번만 해봐서는 알 수 없다는 것도 느낌.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kim.jungbin.models.Function1InputModel | |
import org.scalacheck.{Arbitrary, Gen} | |
class Dao1ArbitraryModels { | |
def function1InputModel: Arbitrary[Function1InputModel] = Arbitrary { | |
for { | |
name <- Gen.listOfN(50, Gen.alphaNumChar) | |
width <- Gen.choose(1, 500) | |
height <- Gen.choose(1, 500) | |
sizeType <- Gen.oneOf(SizeTypeEnum.values.toSeq) // SizeTypeEnum | |
} yield Function1InputModel( | |
name = name.mkString, | |
width = width, | |
height = height, | |
size_type = sizeType | |
) | |
} | |
} |
- 테스트 통과/실패 조건 설정
- Given/When/Then 구조를 이용한 테스트
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.specs2.mock.Mockito | |
import play.api.test.PlaySpecification | |
import scala.concurrent.ExecutionContext.Implicits.global | |
import scala.concurrent.Future | |
class Dao1Spec extends PlaySpecification with Mockito with TestDaos { | |
// Inject random generate data models for Dao1 | |
lazy val dao1ArbitraryModels = inject[Dao1ArbitraryModels] | |
"DAO1" >> { | |
"function1" >> { | |
// Given random generate data | |
val fakeFunction1Input = dao1ArbitraryModels.function1InputModel.arbitrary.sample.get | |
// When | |
val futureRes = testDao1.function1(fakeFunction1Input) | |
val res = await(futureRes) | |
// Then | |
s"return $res" >> { | |
res must not be empty | |
} | |
} | |
} | |
} |
- 테스트 실행
# 모든 테스트 클래스 실행
$ sbt test
# 테스트하고 싶은 테스트 클래스만 실행
$ sbt testOnly *TestClassName
Comments