단언문과 테스트는 작성한 소프트웨어가 제대로 동작하는 지 확인할 수 있는 방법

 

 

14.1 단언문

: assert 메소드를 호출하는 방식으로 단언문 작성

: "assert(조건)" - 조건을 만족하지 않을 경우 AssertError 발생

: "assert(조건, 설명)" - 조건을 만족하지 않을 경우 설명을 포함한 AssertError 발생 

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)

}

 

< 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 인자는 술어 함수를 받는다.

: 술어 함수가 True를 반환하면 메소드 결과를 그대로 반환하고 False를 반환하면 AssertionError 발생

: "_"는 술어가 갖는 유일한 인자, 메소드가 반환해야 하는 결과가 들어간다.

: widen의 결과(Element)에 ensuring을 호출하고 있는 것 같지만 Element를 암시적으로 변환한 타입에 대해 ensuring 호출 = 암시적 변환을 사용하기 때문에 어떤 결과 타입이든 적용 가능

 

: JVM에 -ea나 -da 명령행 옵션을 사용하면 assert, ensuring 동작을 켜거나 끌 수 있다.

 

 

14.2 스칼라에서 테스트하기

: 스칼라는 스칼라테스트, 스펙스2, 스칼라체크 테스트 도구 존재

 

< 스칼라 테스트 >

: 가장 유연한 스칼라 테스트 프레임워크 => 테스트 관련 트레이트/클래스를 통해 쉽게 커스터마이징이 가능해 팀의 요구에 맞는 테스트 스타일 사용 가능

- 트레이트 Suite : 테스트들을 실행하기 위해 사전에 준비된 '생명 주기' 메소드가 선언되어 있음

- 스타일 트레이트 : 다른 테스트 스타일을 지원하기 위해 트레이트 Suite를 확장해 생명 주기 메소드 오버라이드

- 믹스인 트레이트 : 특별한 테스트 요구를 해결하기 위해 생명 주기 메소드 오버라이드

=> 스타일 트레이트와 믹스인 트레이트를 통해 테스트 클래스를 정의해 테스트 스위트(테스트 집합)를 만든다.

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)

    }

}

: FunSuite 스타일 트레이트는 test 메소드 안에 테스트 코드 작성

: test는 이름에 의한 호출 파라미터로 주 생성자에서 테스트 코드를 나중에 실행하기 위해 등록

: 인터프리터에서 단순히 execute를 호출함으로써 테스트 실행 

scala> (new ElementSuite).execute()

ElementSuite:

  - elem result should have passed width

 

14.3 충분한 정보를 제공하는 실패 보고

< 기본 내장 assert >

   val a = 3

   assert(a==2) 

< scalatest assert >

   val a = 3

   assert(a==2)

: 스칼라 테스트는 컴파일 시점에 각 assert 호출에 전달된 식 분석 => 상세한 정보 및 서술적인 오류 메시지 출력

: 실제와 기대치가 다르다는 사실을 강조하고 싶다면 assertResult 사용

   val width = 3

   assertResult(2) { width }

: 어떤 메소드가 발생시킬 수 있는 예외를 검사하고 싶다면 assertThrows 사용

assertThrows[ArithmeticException] {

   throw new AbstractMethodError

}

: assertThrows 는 정상적인 예외에 대해 Succeeded라는 단순한 결과를 반환하지만 intercept는 예외 자체를 반환

 

 

14.4 명세로 테스트하기

- 동작 주도 개발( BDD - behavior-driven development ) 테스트 스타일 : 기대하는 코드의 동작을 사람이 읽을 수 있는 명세로 작성하고 코드가 그 명세에 따라 작동하는지 확인하는 테스트를 작성하는 데 중점(요구사항[스펙, 명세]이 테스트 코드가 되고 이 테스트 코드를 기반으로 기능 구현)

import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
import Element.elem

class ElementSpec extends FlatSpec with ShouldMatchers {

  "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 "throw an IAE if passed a negative width" in {
    an [IllegalArgumentException] should be thrownBy {
      elem('x', -2, 3)
    }  
  }
}

: FlatSpec 스타일 트레이트를 사용하면 BDD 테스트 스타일로 작성 가능

: 형식 => 테스트 주제 should(must, can) 테스트 설명 in { 테스트 코드 }

: it 은 가장 최근에 언급된 테스트 주제 의미 => it은 "A UniformElement"

: "should be", "in", "should be thrownBy" 문법은 ShouldMatchers(연결자 도메인 특화 언어) 믹스인 트레이트에서 제공

              => should 보다 must를 선호한다면 MustMatchers를 믹스인 

              => 연결자 DSL을 통해 커스텀하게 단언문 작성 가능

  scala> (new ElementSpec).execute()
  A UniformElement
  - should have a width equal to the passed value
  - should throw an IAE if passed a negative width

: 실행 시 사람이 읽기 더 좋은 출력을 만든다.

 

 

- BDD는 소프트웨어 시스템을 만들지 결정하는 사람, 그 소프트웨어를 구현하는 사람, 소프트웨어가 잘 마무리되어 동작하는 지 결정하는 사람 사이의 의사소통을 테스트가 도와주도록 해야 한다. 

import org.scalatest._

class TVSetSpec extends FeatureSpec with GivenWhenThen {
  feature("TV power button"){
    scenario("User presses power button when TV is off"){
      Given("a TV set that is switched off")
      When("the power button is pressed")
      Then("the TV sould switch on")
      pending
    }
  }
}

 : FeatureSpec 스타일 트레이트는 의사소통을 돕기 위해 설계

: 구체적인 특징(feature)을 밝혀야 하고 그에 대한 시나리오(scenario)를 명시한다.

: Given, When, Then 은 구체적인 개별 시나리오에 대한 대화에 초점을 맞춘다.

: pending 호출은 테스트나 실제 동작이 아직 구현되지 않았다는 사실 명시

 

 

14.5 프로퍼티 기반 테스트

: 스칼라 체크는 각 프로퍼티에 대해 테스트 데이터를 생성한 다음, 프로퍼티를 잘 지키는지 검사하는 테스트를 실행

import org.scalatest.WordSpec
import org.scalatest.prop.PropertyChecks

import org.scalatest.MustMatcers._
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)
        }
      }
    }
  }
}

: elem 요소가 지켜야 하는 한 가지 프로퍼티 검사

: whenever은 왼쪽 편에 있는 절이 true일 때마다 오른쪽에 있는 식이 true가 되어야함을 명시한다.

: forAll 내부에 테스트 데이터를 인자로 받고 단언문을 수행하는 함수값 명시 => 스칼라 체크가 테스트 데이터를 임의로 수 백개 생성 => 모든 값을 만족하는 경우 테스트를 통과 그렇지 않는 경우 실패 원인이 된 값이 들어있는 TestFailedException을 던지고 종료

 

 

14.6 테스트 조직과 실행

: 스칼라 테스트는 스위트 안에 스위트를 포함시킴으로써 큰 테스트를 조직 => 트리 구조를 형성해 루트 Suite 객체를 실행하면 트리 전체의 Suite를 실행한다.

: 수동 또는 자동으로 스위트 포함 가능

   - 수동 : nestedSuites 메소드 오버라이드 또는 생성자에 Suite 전달

 import org.scalatest.Suite

 class ASuite extends Suite
 class BSuite extends Suite
 class CSuite extends Suite

 class AlphabetSuite extends SuperSuite(
   List(
     new ASuite,
     new BSuite,
     new CSuite
   )
 )

 

 scala> (new AlphabetSuite).run()
 

   - 자동 : 스칼라 테스트의 Runner에 패키지 이름 전달 

$ scalac -cp scalatest.jar TVSetSpec.scala

$ scala -cp scalatest.jar org.scalatest.run TVSetSpec

 

+ Recent posts