신규 블로그를 만들었습니다!
Chapter 14. 단언문과 테스트
스칼라에서 단언문과 테스트를 작성하기 위한 여러 방법을 소개
14.1. 단언문
scala
에서는 assert
(메소드)를 통해서 단언문(assertion
)을 작성.
- 단언문은 조건을 만족하지 못하면
AssertionError
를 발생
인자 2개를 받는 assertion
도 존재하는데, "조건"과 "설명"을 받는다.
- 조건: 조건에 만족하지 못하는 경우,
AssertionError
을 발생 - 설명: 설명을 포함하여
AssertionError
를 발생 시킴.Any
타입을 가짐
assert("조건", "설명")
설명은 Any
타입을 가지기 때문에, assert
는 문자열 설명을 얻기 위해 toString
메소드를 호출한다.
def above(that: Element): Element = {
val this1 = this widen that.width
val that1 = that widen this.width
assert(this1.width == that1.width)
elem(this1.contents ++ that1.contents)
}
위와 같이 검사가 가능한데, 더 간결하게 하고 싶다면 Predef
에 있는 ensuring
이라는 도우미 메소드를 사용할 수 있다.
private def widen(w: Int): Element =
if (w <= width)
this
else {
val left = elem(' ', (w - width) / 2, height)
var right = elem(' ', w - width - left.width, height)
left beside this beside right
} ensuring (w <= _.width)
ensuring
메소드는 암시적 변환을 사용하기 때문에, 어떠한 결과 타입이든 적용 가능하다.
ensuring
은 인자 하나를 받는데, 그 인자는 술어함수(predicate function
)이다.
- 술어: 메소드 결과의 타입을 받아서
Boolean
을 반환하는 함수
술어란?
w <= _.width
: 술어_
(밑줄)은 술어가 갖는 유일한 인자.widen
메소드의 (Element
타입의) 결과
흐름 순서
ensuring
은 술어에게 메소드 결과를 넘긴다.- 술어는
true
또는false
를 반환한다.- 술어가
true
를 반환하면,ensuring
도 그대로true
를 반환한다. - 반면,
false
를 반환하면ensuring
은AssertionError
를 발생시킨다.
- 술어가
JVM 에서 -ea 나 -da 명령행 옵션을 이용하여 assert
나 ensuring
동작을 켜거나 끌 수 있다.
단위 테스트는 스스로 데이터를 제공하며, 애플리케이션과 독립적으로 실행할 수 있다.
14.2. 스칼라에서 테스트하기
테스트를 할 때, 여러가지 테스트 도구가 존재한다.
JUnit
,TestNG
,ScalaCheck
등등
스칼라 테스트는 가장 유연한 스칼라 프레임워크로서, 쉽게 커스터마이징이 가능하다.
유연하다는 뜻은 팀 (취향)에 맞게 어떠한 테스트 스타일도 사용 할 수 있다는 뜻.
JUnit
에 익숙한 팀원들은 FunSuite
를 이용할 수 있다.
예: FunSuite로 테스트 작성하기
import org.scalatest.FunSuite
import Element.elem
class ElementSuite extends FunSuite {
test("elem result should have passed width") {
val ele = elem('x', 2, 3)
assert(ele.width == 2)
}
}
스칼라테스트에서 중요한 개념은 suite
이다.
test
는 시작해서 성공 or 실패 or 대기(pending
) or 취소와 같이 끝날 수 있다.
트레이트 Suite
는 테스트를 실행하기 전에 사전에 준비된 '생명주기(Life Cycle
)' 메소드들을 선언한다.
- 스타일 트레이트(
styletrait
): 다른 테스트 스타일을 지원하기 위해Suite
를 확장하고 생명 주기 메소드를override
하거나, 믹스인 트레이트(mixin trait
)가 가능.
14.3. 충분한 정보를 제공하는 실패 보고
단언문이 실패를 하면, 파일이름, 단언문의 줄 번호, 그리고 정보가 담긴 오류 메시지가 오류보고에 포함된다.
scala> val width = 3
width: Int = 3
scala〉 assert(width == 2)
org.scalatest.exceptions.TestFailedException:
3 did not equal 2
DiagrammedAssertions
더욱 상세한 정보를 다이어그램 형식으로 보고싶을때, DiagrammedAssertions
을 이용할 수 있다.
scala〉 assert(List (1, 2, 3).contains(4)) org.scalatest.exceptions.TestFailedException:
assert(List(1, 2, 3).contains(4))
| | | | | |
| 1 2 3 false 4
List(123)
assertResult
실제와 기대치가 다르다는 사실을 강조하고 싶다면, assertResult
를 이용한다.
assertResult (2) { // 중괄호 안의 값이 2가 되기를 기대
ele.width // 만약 코드값이 3인 경우, "Excepted 2, but got 3"
}
assertThrows
만약 예외를 검사하고 싶다면, assertThrows
메소드를 이용한다.
- 다른 예외를 발생시키거나 예외가 안일어난다면,
TestFailedException
이 발생한다.
assertThrows[IllegalArgumentException] {
elem(,x,, -1, 3)
}
// 도움이 될만한 오류 메시지
// Expected IIlegalArgumentException to be thrown,
// but NegativeArraySizeException was thrown.
intercept
더 나아가 어떤 예외가 발생했는지 알고 싶을때는 intercept
메소드를 사용한다. assertThrows
와 동일하게 동작하지만, 예상한대로 예외가 발생하는 경우 intercept
만 그 예외를 반환한다.
val caught =
intercept[ArithmeticException] { 1 / 0 }
assert(caught.getMessage == "/ by zero")
SummaryDiagrammedAssertions
: 다이어그램assertResult
: 기대값assertThrows
: (기대하는) 예외intercept
: assertThrows 와 동작은 같지만, 예외 반환
14.4. 명세로 테스트하기
동작 주도 개발 (BDD
, Behavior-driven development
) 테스트 스타일은 코드의 동작을 사람이 읽을 수 있는 명세로 작성하고, 그 명세에 따라 작동하는 확인하는 방법
FlatSpec
명세 절을(specifier clause
) 을 사용해 테스트를 작성
import org.scalatest.FlatSpec
import org.scalatest.Matchers
import Element.elem
class ElementSpec extends FlatSpec with Matchers {
"A UniformElement" should
"have a width equal to the passed value" in {
val ele = elem('x', 2, 3)
ele.width should be (2)
}
it should "have a height equal to the passed value" in {
val ele = elem('x', 2, 3)
ele.height should be (3)
}
it should "throw an IAE if passed a negative width" in {
an [IllegalArgumentException] should be thrownBy {
elem('x', -2, 3)
}
}
}
- 테스트 할 주제(
subject
)에 대해 이름을 붙인다.
class ElementSpec extends FlatSpec with Matchers {
"A UniformElement" ... // 테스트 할 주제
...
- 그 뒤에
should
(또는must
또는can
)을 넣는다.
...
class ElementSpec extends FlatSpec with Matchers {
"A UniformElement" should // 주제 다음에 should
...
- 그 뒤에 해당 주제의 작동을 설명하고
in
이 온다.
...
"A UniformElement" should
"have a width equal to the passed value" in // 작동 설명
...
in
뒤에는 중괄호안에 테스트할 코드를 넣는다.
... in { // in 다음에
// 테스트 할 코드들
val ele = elem('x', 2, 3)
ele.width should be (2)
}
... in {
val ele = elem('x', 2, 3)
ele.height should be (3)
}
...
위와같이 테스트 코드를 작성하고, 실행하면
scala> (new ElementSpec).execute()
A UniformElement
- should have a width equal to the passed value
- should have a height equal to the passed value
- should throw an IAE if passed a negative width
연결자(matcher
) 도메인 특화 언어(DSL
, domain-specific language
)
Matchers
트레이트를 혼합하면, 자연어처럼 잘 읽을 수 있는 단언문을 작성할 수 있다.
// 예1
should be
// 예2: an[ ... ] should be thrownBy { ... }
...
an [IllegalArgumentException] should be thrownBy {
elem('x', -2, 3)
}
만약, must
를 선호 한다면? MustMatchers
를 혼합하여 사용하면 됨.
result must be >= 0
map must contain key 'c'
// 'c'를 포함하지 않는 경우 결과
Map('a' -> 1, 'b' -〉 2》 did not contain key 'c'
스펙스2(specs2
)
스칼라 오픈소스 도구인 스펙스2(specs2
)테스트 프레임워크도 BDD
스타일을 지원한다. (문법은 조금 다름)
- http://specs2.org 에서
specs2
내려받을 수 있음
import org.specs2._
import Element.elem
object ElementSpecification extends Specification {
"A UniformElement" should {
"have a width equal to the passed value" in {
val ele = elem('x', 2, 3)
ele.width must be_==(2)
}
"have a height equal to the passed value" in {
val ele = elem('x', 2, 3)
ele.height must be_==(3)
}
"throw an IAE if passed a negative width" in {
elem('x', -2, 3) must throwA[IllegalArgumentException]
}
}
}
이러한 테스트들은...
이러한 테스트들의 장점은 사람 사이의 의사소통을 테스트가 도와줄 수 있다는 점이다.
- 스칼라테스트, 스펙스2 둘다 가능하지만, 스칼라테스트의
FeatureSpec
은 이런 목적으로 설계된 것이다.
import org.scalatest._
class TVSetSpec extends FeatureSpec with GivenWhenThen {
feature("TV power button") { // 전원 버튼
scenario ("User presses power button when TV is off") {
// 사용자가 TV 전원을 끄는 버튼을 눌렀다.
// (그렇다면 구체적으로 어떻게?)
Given ("a TV set that is switched off") // TV의 전원 스위치를 off
When("the power button is pressed") // 전원 버튼을 눌렀을 때,
Then("the TV should switch on") // 단, TV 전원 스위치가 on 이어야 함
pending // 대기
}
}
}
참고로 GivenWhenThen
트레이트가 Given
, When
, Then
을 제공함.
14.5. 프로퍼티 기반 테스트
스칼라체크 ScalaCheck
는 스칼라로 만들어진 또 다른 유용한 테스트 도구다.
- 테스트할 코드가 준수해야 하는 프로퍼티를 명시.
- 각 프로퍼티에 대해 테스트 데이터를 생성한 다음, 프로퍼티를 잘 지키는지 검사하는 테스트를 실행.
PropertyChecks 트레이트와 WordSpec
import org.scalatest.WordSpec
import org.scalatest.prop.PropertyChecks
import org.scalatest.MustMatchers
import Element.elem
class ElementSpec extends WordSpec with PropertyChecks {
"elem result" must {
"have passed width" in {
forAll {
(w: Int) => whenever (w > 0) {
elem('x', w, 3).width must equal (w)
}
}
}
}
}
스칼라 체크의 테스트
스칼라체크는 프로퍼티에 맞지 않는 값을 찾기 위해, w 에 들어갈 수 있는 값을 수백 개 생성하고 테스트 한다.
- 스칼라체크가 시도하는 모든 값을 프로퍼티가 만족하는 경우, 테스트를 통과.
- 만족하지 않으면,
TestFailedException
을 즉시 던지면서 테스트 종료.
14.6. 테스트 조직과 실행
스칼라테스트에서는 스위트(suite
) 안에 스위트를 포함시킴으로써 큰 테스트를 조직화한다.
어떤 Suite가 실행되면, 그 안에 있는 테스트, 내부에 있는 Suite의 테스트도 실행된다.
즉, 트리구조의 루트 Suite
객체를 실행하면 트리 전체의 Suite
를 실행하게된다.
수동 처리
만약, 수동으로 처리하려면 nestedSuites
메소드를 오버라이드하거나, 포함시키고 싶은 스위트를 Suite 클래스의 생성자에 전달한다.
자동 처리
스칼라테스트의 Runner
에 패키지 이름을 전달 하면 된다.
실행자(Runner
)가 자동으로 스위트를 찾아내서 루트 스위트 안에 그 스위트들을 넣고 루트 스위트를 실행한다.
전반적인 내용을 알기 위해서는 각 프레임워크 문서를 확인할 필요가 있다.
빌드툴을 이용한 Runner 실행
명령행에서 실행하거나 sbt,maven, ant 같은 빌드 툴을 통해 스칼라테스트의 Runner 애플리케이션을 호출할
- 명령행에서 가장
Runner
를 쉽게 호출하는 방법은org.scalatest.run
어플리케이션을 이용하는 방법
# 컴파일
$ scalac -cp scalatest.jar TVSetSpec.scala
# 실행
$ scala -cp scalatest.jar org.scalatest.run TVSetSpec
-cp scalatest.jar
:-cp
옵션을 통해JVM
의 클래스 경로에 스칼라테스트.jar
파일을 추가org.scalatest.run
: 애플리케이션의 전체 경로TVSetSpec
: 실행할 스위트
'Language > Scala' 카테고리의 다른 글
Scala 기본 타입과 연산 (Chapter 05) (0) | 2019.11.10 |
---|
최근댓글