4장 테스팅 도구
들어가며....
모든 예제는 avtivator 에서 sample 로 제공 하는 test-patterns-scala 프로젝트 를 기준으로 작성 되었으며 책의 내용과 맥락 을 같이합니다.
DSL 란?
스칼라의 테스팅 기법에서 DSL(Domain Specific Language) 이란 도메인 명세 언어로서 도메인을 통하여 모두가 이해할수 있는 언어 입니다 DSL 의 3대 요소에는 단순함,간결함,유창함 있으며 개발자나 클라이언트 DSL 로 작성된 스크립트를 보고 의미를 쉽게 파락해야 하고(단숨함) 간단하면서 표현력 있는 문법을 사용해야 합니다(간결함) 그리고 이해할수 있는 문맥이 존재한다면 설명이 없이도 의미를 빠르게 파악할수 있습니다(유창함)
BDD 란?
Dan North 의 BDD(Behaviour-Driven Development) 행도 주도 개발 은 도메인의 요구 사항에 집중한 테스팅 기법으로 기존 TDD 테스트 주도 개발 과 다른 점은 기능에 초점을 준 단위 테스트 기법 인지, 행동에 집중한 도메인 관점 테스트 인지 의 차이 입니다.
BDD 는 문장 템플릿 기반 으로 서술에 집중한 테스트 기법 으로 책 뒷 부분에 예제가 나옵니다 여기서 는 어떤 자연어 규칙으로 테스트 기법을 작성 하는지에 대한 포인트만 작성 하겠습니다.
[x] 로 하여금 (AS a)
[Z] 를 얻게 하기 위하여 (so that)
[Y] 가 필요하다 (I want)
Given 조건 이 주어짐.
When 만일 사건이 발생 했을때.
Then 그러면 어떤 일이 수행된다.
에릭 에반스 의 DDD 에서는 도메인 전문가 와 개발자의 공통언어(ubiquitous language) 에 대하여 설명 합니다 개념은 비개발자와 개발자 간의 모델 기반 언어를 통해 설계 해야 그 코드가 시스템에 잘 녹아 들어간다는 것 입니다 Dan North 는 이러한 사상을 바탕으로 공통 언어 템플릿을 정의 하였습니다.
스칼라 테스트 시작
activator 를 설치한 디렉토리로 이동 커맨드 창에서 아래 내용 입력
> activator ui
- test-patterns-scala 프로젝트 검색후 생성 -> 왼쪽 Code 클릭 -> Browse Code 의 ... 클릭 -> create IntelliJ project 생성
- IntelliJ 실행후 import project 통해 생성된 프로젝트를 로드한다
ScalaTest
테스트 예제는 샘플에 모두 구현되어 있으므로 중요한 요점만 표시
- 테스트 구동
- > activator
- > test-only 실행 클래스 이름 (이름 예: scalatest.Test01.scala)
- 테스트 연속 모드
- 테스트 연속 모드는 저장될 때마다 SBT 에서 테스트를 자동으로 구동 합니다
- > ~test-only scalatest.test02
Example 1
package scalatest
import org.scalatest.FunSuite
// == 비교문에 = 한번더 추가시 에러내용을 표기해 준다
class Test02 extends FunSuite {
test("pass") {
assert("abc" === "abc")
}
test("fail and show diff") {
assert("abc" === "abcd") // 에러 발생 및 내용 표시
}
}
Example 2
ScalaTest 를 JUnit 테스트로 실행 하기 위하여 설정이 필요하다
- build.sbt
libraryDependencies ++= Seq(
...
...
"junit" % "junit" % "4.11" % "test", // 의존성 추가
...
)
- 테스트 작성시 @RunWith(classOf[JUnitRunner]) 추가해 준다
package scalatest
import org.junit.runner.RunWith
import org.scalatest.{BeforeAndAfter, FunSuite}
import org.scalatest.junit.JUnitRunner
@RunWith(classOf[JUnitRunner])
class MyTestSuite extends FunSuite with BeforeAndAfter {
var sb : StringBuilder = _
before{
sb = new StringBuilder("scala is")
}
test("테스트 시작 해요"){
sb.append("easy !")
//("ScalaTest is easy!" === sb.toString())
assert(!sb.isEmpty)
println("ddd")
}
}
- 클래스 선택후 -> 마우스 우클릭 -> Run -> 녹색 막대 확인
Example 3
BDD 스타일 테스팅
package scalatest
import org.scalatest.FeatureSpec
import org.scalatest.GivenWhenThen
import scala.collection.mutable.Stack
//Taken from http://www.scalatest.org/getting_started_with_feature_spec
//Several test styles supported by ScalaTest
//GivenWhenThen 은 deprecate 상태로 앞 글자가 대문자로 변경 되었으며 내부적으로도 변경된 함수를 호출 하고 있다
class Test06 extends FeatureSpec with GivenWhenThen {
feature("The user can pop an element off the top of the stack") {
//사용자는 스택의 선두로부터 요소를 팝 할 수 있습니다.
info("As a programmer")
//프로그래머로 하여금
info("I want to be able to pop items off the stack")
//나는 스택에서 항목을 팝 할수있게 원합니다
info("So that I can get them in last-in-first-out order")
//나는 후입 선출 순서 로 그것을 얻게 하기 위하여
scenario("pop is invoked on a non-empty stack") {
// 팝 은 비어 있지 않는 스택에 저장됩니다
given("a non-empty stack")
//빈 스택
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
val oldSize = stack.size
when("when pop is invoked on the stack")
//스택 에서 팝을 호출 할 때
val result = stack.pop()
then("the most recently pushed element should be returned")
//가장 최근에 푸시 요소를 반환해야합니다
assert(result === 2)
and("the stack should have one less item than before")
//스택은 이전보다 1 적은 아이템을 가지고 있어야합니다
assert(stack.size === oldSize - 1)
}
scenario("pop is invoked on an empty stack") {
//빈 스택에서 팝을 호출 합니다
given("an empty stack")
val emptyStack = new Stack[Int]
when("when pop is invoked on the stack")
//스택 에서 팝을 호출 할때
then("NoSuchElementException should be thrown")
//NoSuchElementException 이 발생 되어야 합니다
intercept[NoSuchElementException] {
emptyStack.pop()
}
and("the stack should still be empty")
//스택이 비어 있어야 합니다
assert(emptyStack.isEmpty)
}
}
}
BDD 테스팅은 직관적 인 테스트 로서 테스트 자체가 문서화 가능하며 도입 부분에 설명했던 공통언어 가 잘 드러나 있습니다
Example 4
일반 문장에 가까운 형태로 assertion 테스트
package scalatest
import org.scalatest._
import org.scalatest.matchers.ShouldMatchers
//Matchers 로 변경됨
//class Test07 extends FlatSpec with ShouldMatchers {
class Test07 extends FlatSpec with Matchers {
"This test" should "pass" in {
true should be === true
}
}
Matchers 자세한 내용은 아래 링크를 참조하세요! http://www.scalatest.org/user_guide/using_matchers
Example 5
스칼라 워크시트 추가
- 인텔리J -> new -> Scala Worksheet Worksheet 를 활용 하면 테스트 실패시 분활된 화면으로 즉시 메시지를 표시해 줍니다
package scalatest
import org.scalatest._
object ShouldWork extends FlatSpec with Matchers {
true should be === false
}
Worksheet 테스트시 워크시트 평과 과정에서 실패가 도출되면 즉시 멈추며 문제가 생긴 부분을 해결해야만 다음 테스트가 가능합니다
Example 6
Selenium 테스팅 도구 (http://www.seleniumhq.org)
ackage scalatest
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import org.scalatest.selenium.WebBrowser
import org.openqa.selenium.htmlunit.HtmlUnitDriver
import org.openqa.selenium.firefox.FirefoxDriver
import org.openqa.selenium.WebDriver
class Test08 extends FlatSpec with Matchers with WebBrowser {
// 드라이버 변경으로 여러 환경 테스트 가능 (파폭,크롬,사파리 등등)
implicit val webDriver: WebDriver = new HtmlUnitDriver
go to "http://www.amazon.com"
click on "twotabsearchtextbox"
textField("twotabsearchtextbox").value = "Scala"
submit()
pageTitle should be ("Amazon.com: Scala")
pageSource should include("Scala Cookbook: Recipes")
}
- Selenium 의 UI 테스트는 초기 학습 비용이 들고 테스트 작성이 까다롭다 그리고 테스트 시나리오 작성이 모호합니다
- Selenium 테스트 대해 모두 생각해 봤으면 좋겠습니다...
- 자세한 내용은 selenium 테스트 검색 키워드로 많은 정보를 얻을수 있습니다
Example 6
ScalaMock 모킹하기
- 스칼라 에서는 트레잇과 같은 자바에는 없는 요소가 있어 자바 기반의 모킹 프레임워크로는 부족하여 scalaMock(www.scalamock.org) 이 등장하게 되었 습니다 *
- mock 객체 는 행위를 검증하기 위한 가짜 객체를 말합니다 mock 테스트 를 말할때 빠질수 없는 개념인 테스트 더블(test double) 대역을 나타내는 스턴트 더블에서 차용된 말로 오리지널 객체로 테스트가 힘들경우 이를 대신해 준다는 개념 입니다
start
- 의존성 추가 (참고: scalaMock 공식 페이지 접속시 2014년 부로(scalaMock3) 더이상 업데이트가 없으며 2016년에 scalaMock4 로 업데이트 예정)
"org.scalamock" %% "scalamock-scalatest-support" % "3.2.2" % "test",
Example 7
ScalaCheck(https://www.scalacheck.org)
테스트 환경이 완전한 형태를 갖추기 힘든 경우 또는 테트스 범위가 너무 커서 테스트 데이터를 만드기 힘든 경우 scalaCheck 는 자동화된 속성기반 테스트 를 만들수 있다
//build.sbc 의존성 주입
resolvers += Resolver.sonatypeRepo("releases")
"org.scalamock" %% "scalamock-scalatest-support" % "3.0.1" % "test",
//test 코드 작성
package se.chap4
import org.scalacheck.Properties
import org.scalacheck.Prop.forAll
object StringSpecification extends Properties("String") {
//Properties 큰 묶음표현
property("startsWith") = forAll{ (a: String, b: String) =>
//property 단위 표현
(a+b).startsWith(a)
}
property("concatenate") = forAll { (a: String, b: String) =>
(a+b).length > a.length && (a+b).length > b.length
}
property("substring") = forAll { (a: String, b: String, c: String) =>
(a+b+c).substring(a.length, a.length+b.length) == b
}
}
//결과
+ String.startsWith: OK, passed 100 tests.
! String.concatenate: Falsified after 0 passed tests.
> ARG_0: ""
> ARG_1: ""
+ String.substring: OK, passed 100 tests.
Process finished with exit code 1
scalaCheck 는 테스트 데이터 값을 무작위로 생성하여 속성이 만족하는지에 대한 테스트를 한다
그외 많은 예제를 확인하려면 (http://booksites.artima.com/scalacheck/examples/index.html)
Example 8
package se.chap4
import org.scalacheck._
import Arbitrary._
import Prop.forAll
object StringSpecification extends Properties("String") with Currency{
//첫번째 테스트 하나의 임의 키값 생성
val currencies = Gen.oneOf("EUR","GBP","SEK","JPY","error")
//두전째 해당 리스트 만큼
lazy val conversions : Gen[(BigDecimal,String,String)] = for {
//정수인지 양수인지 필터링
amt <- arbitrary[Int] suchThat {_ >= 0}
//임의값 맵핑
from <- currencies
//임의값 맵핑
to <- currencies
}yield(amt,from,to)
//첫번째 테스트
property("정의...") = forAll (currencies) { c:String =>
val amount = BigDecimal(200)
//계산된 정보
val convertedAmount = convert(amount,c,c)
//비교
convertedAmount == amount
}
//두번째 테스트
property("정의...") = forAll(conversions) { c =>
//튜플 값
val convertedAmount = convert(c._1,c._2,c._3)
//비교
convertedAmount >= 0
}
}
그외....
java -> scala 변환기