sbt로 스칼라 프로젝트 빌드하고 테스트하기
sbt로 스칼라 프로젝트 빌드하고 테스트하기 (Building and Testing Scala Projects with sbt)
코드를 작성만 해서는 실제 프로그램으로 동작하지 않아요. 어느 시점에는 그 코드를 컴파일하고, 의존성을 관리하고, 테스트를 돌려야 하죠. 이 절에서는 스칼라 프로젝트에서 흔히 쓰는 도구 두 가지를 살펴볼게요.
- sbt — 빌드 도구
- ScalaTest — 소스 코드 테스트 프레임워크
먼저 sbt로 스칼라 프로젝트를 빌드하는 방법을 보여드리고, 그다음 sbt와 ScalaTest를 함께 사용해 테스트하는 방법을 보여드릴게요.
Scala 2 코드를 Scala 3로 마이그레이션하는 데 도움이 되는 도구를 배우고 싶다면, 스칼라 3 마이그레이션 가이드(Scala 3 Migration Guide)를 참고하세요.
출처: Scala 3 Book
본문
sbt로 스칼라 프로젝트 빌드하기
스칼라 프로젝트를 빌드하는 데는 Ant, Maven, Gradle, Mill 등 여러 도구를 쓸 수 있어요. 하지만 sbt는 스칼라를 위해 특별히 만들어진 최초의 빌드 도구예요.
sbt를 설치하려면, sbt 다운로드 페이지나 이 사이트의 시작하기(Getting Started) 페이지를 참고하세요.
"Hello, world" 프로젝트 만들기
sbt "Hello, world" 프로젝트는 몇 단계만 거치면 만들 수 있어요. 먼저 작업할 디렉터리를 만들고, 그 디렉터리로 이동해요.
$ mkdir hello
$ cd hello
hello 디렉터리 안에 project 하위 디렉터리를 만들어요.
$ mkdir project
project 디렉터리에 다음 내용을 담은 build.properties 파일을 만들어요.
sbt.version=1.10.11
그다음 프로젝트 루트 디렉터리에 이 한 줄을 담은 build.sbt 파일을 만들어요.
scalaVersion := "3.9.0"
이제 Hello.scala 같은 이름의 파일을 만들어요(이름의 앞부분은 중요하지 않아요) — 이 한 줄을 담아서요.
@main def helloWorld = println("Hello, world")
이게 전부예요. 그러면 프로젝트 구조는 다음과 같아야 해요.
$ tree
.
├── build.sbt
├── Hello.scala
└── project
└── build.properties
이제 다음 sbt 명령어로 프로젝트를 실행해요.
$ sbt run
출력은 다음과 비슷하게 보이는데, 프로그램의 "Hello, world" 출력도 포함돼 있어요.
$ sbt run
[info] welcome to sbt 1.5.4 (AdoptOpenJDK Java 11.x)
[info] loading project definition from project ...
[info] loading settings for project from build.sbt ...
[info] compiling 1 Scala source to target/scala-3.0.0/classes ...
[info] running helloWorld
Hello, world
[success] Total time: 2 s
sbt 런처 — sbt 명령줄 도구 — 는 project/build.properties 파일에 설정된 sbt 버전을 로드하고, build.sbt 파일에 설정된 스칼라 컴파일러 버전을 로드하며, Hello.scala 파일의 코드를 컴파일하고, 그 결과 바이트코드를 실행해요.
디렉터리를 들여다보면 sbt가 target 디렉터리를 만들었다는 걸 볼 수 있어요. 이것들은 sbt가 사용하는 작업 디렉터리예요.
보시다시피, sbt로 작은 스칼라 프로젝트를 만들고 실행하는 건 몇 단계면 충분해요.
더 큰 프로젝트에서 sbt 사용하기
작은 프로젝트라면 sbt가 실행에 요구하는 건 그게 전부예요. 소스 코드 파일이 많고, 의존성이 있고, sbt 플러그인이 필요한 더 큰 프로젝트라면, 체계화된 디렉터리 구조를 만드는 게 좋아요. 이 절의 나머지 부분은 sbt가 사용하는 구조를 보여드려요.
sbt 디렉터리 구조
Maven처럼 sbt도 표준 프로젝트 디렉터리 구조를 사용해요. 좋은 점은 이 구조에 익숙해지면 다른 스칼라/sbt 프로젝트에서도 쉽게 작업할 수 있다는 거예요.
가장 먼저 알아야 할 것은, sbt는 프로젝트의 루트 디렉터리 아래에 다음과 같은 디렉터리 구조를 기대한다는 거예요.
.
├── build.sbt
├── project/
│ └── build.properties
├── src/
│ ├── main/
│ │ ├── java/
│ │ ├── resources/
│ │ └── scala/
│ └── test/
│ ├── java/
│ ├── resources/
│ └── scala/
└── target/
원한다면 루트 디렉터리 아래에 lib 디렉터리를 추가해, 관리되지 않는 의존성(unmanaged dependencies) — JAR 파일 — 을 프로젝트에 넣을 수도 있어요.
스칼라 소스 코드 파일과 테스트가 있는 프로젝트를 만들 거지만, Java 소스 코드 파일은 쓰지 않고, "리소스"(내장 이미지, 설정 파일 등)도 필요하지 않다면, src 디렉터리 아래에는 사실 이것만 있으면 충분해요.
.
└── src/
├── main/
│ └── scala/
└── test/
└── scala/
sbt 디렉터리 구조로 "Hello, world" 만들기
이 디렉터리 구조를 만드는 건 간단해요. 여러분 대신 해 주는 도구도 있지만, Unix/Linux 시스템을 쓰고 있다고 가정하면 다음 명령어로 첫 sbt 프로젝트 디렉터리 구조를 만들 수 있어요.
$ mkdir HelloWorld
$ cd HelloWorld
$ mkdir -p src/{main,test}/scala
$ mkdir project target
이 명령어들을 실행한 뒤 find . 명령을 실행하면, 이런 결과가 보여야 해요.
$ find .
.
./project
./src
./src/main
./src/main/scala
./src/test
./src/test/scala
./target
이 결과가 보이면 다음 단계를 진행할 준비가 된 거예요.
sbt 프로젝트의 파일과 디렉터리를 만드는 다른 방법도 있어요. 그중 하나는 sbt new 명령을 쓰는 건데, scala-sbt.org에 문서화되어 있어요. 여기서 그 방법을 보여주지 않는 이유는, 그 명령이 만드는 일부 파일들이 이런 소개 수준에서는 필요 이상으로 복잡하기 때문이에요.
첫 build.sbt 파일 만들기
이 시점에 "Hello, world" 프로젝트를 실행하려면 딱 두 가지만 더 있으면 돼요.
build.sbt파일Hello.scala파일
이런 작은 프로젝트의 build.sbt 파일에는 scalaVersion 항목만 필요해요. 하지만 흔히 보는 세 줄을 추가해 볼게요.
name := "HelloWorld"
version := "0.1"
scalaVersion := "3.9.0"
sbt 프로젝트는 표준 디렉터리 구조를 사용하므로, sbt가 필요한 나머지 모든 것을 스스로 찾아요.
이제 작은 "Hello, world" 프로그램만 추가하면 돼요.
"Hello, world" 프로그램
큰 프로젝트에서는 모든 스칼라 소스 코드 파일이 src/main/scala와 src/test/scala 디렉터리 아래에 들어가요. 하지만 이렇게 작은 샘플 프로젝트에서는 소스 코드 파일을 프로젝트의 루트 디렉터리에 둘 수 있어요. 따라서 루트 디렉터리에 다음 내용을 담은 HelloWorld.scala 파일을 만들어요.
@main def helloWorld = println("Hello, world")
이 코드는 실행될 때 "Hello, world"를 출력하는 Scala 3 "main" 메서드를 정의해요.
이제 sbt run 명령으로 프로젝트를 컴파일하고 실행해요.
$ sbt run
[info] welcome to sbt
[info] loading settings for project ...
[info] loading project definition
[info] loading settings for project root from build.sbt ...
[info] Compiling 1 Scala source ...
[info] running helloWorld
Hello, world
[success] Total time: 4 s
sbt를 처음 실행하면 필요한 것을 모두 다운로드하느라 몇 순간이 걸릴 수 있지만, 그다음부터는 훨씬 빨라져요.
또한 이 첫 단계가 동작하게 되면, sbt를 대화형으로 실행하는 게 훨씬 빠르다는 걸 알게 될 거예요. 그러려면 먼저 sbt 명령만 실행해요.
$ sbt
[info] welcome to sbt
[info] loading settings for project ...
[info] loading project definition ...
[info] loading settings for project root from build.sbt ...
[info] sbt server started at
local:///${HOME}/.sbt/1.0/server/7d26bae822c36a31071c/sock
sbt:hello-world> _
그다음 이 sbt 셸 안에서 run 명령을 실행해요.
sbt:hello-world> run
[info] running helloWorld
Hello, world
[success] Total time: 0 s
이렇게 하니 훨씬 빠르죠.
sbt 프롬프트에서 help를 입력하면 실행할 수 있는 다른 명령어 목록을 볼 수 있어요. 하지만 지금은 exit(또는 CTRL-D)을 입력해 sbt 셸을 떠나도록 하세요.
프로젝트 템플릿 사용하기
프로젝트 구조를 수동으로 만드는 건 지루할 수 있어요. 다행히 sbt가 템플릿을 바탕으로 직접 만들어 줘요.
템플릿에서 Scala 3 프로젝트를 만들려면, 셸에서 다음 명령을 실행해요.
$ sbt new scala/scala3.g8
sbt가 템플릿을 로드하고 몇 가지 질문을 한 다음, 하위 디렉터리에 프로젝트 파일을 만들어요.
$ tree scala-3-project-template
scala-3-project-template
├── build.sbt
├── project
│ └── build.properties
├── README.md
└── src
├── main
│ └── scala
│ └── Main.scala
└── test
└── scala
└── Test1.scala
Scala 2와 크로스 컴파일되는 Scala 3 프로젝트를 만들고 싶다면, scala/scala3-cross.g8 템플릿을 사용해요.
$ sbt new scala/scala3-cross.g8
sbt new와 프로젝트 템플릿에 대해 더 배우려면 sbt의 문서를 참고하세요.
스칼라용 다른 빌드 도구
sbt가 널리 쓰이긴 하지만, 스칼라 프로젝트를 빌드하는 데 쓸 수 있는 다른 도구도 있어요.
- Ant
- Gradle
- Maven
- Mill
- Coursier
참고로 Coursier는 Maven, Ivy와 기능이 비슷한 "의존성 해석기"(dependency resolver)예요. 스칼라로 처음부터 새로 작성되었고, "함수형 프로그래밍 원리를 받아들이며", 병렬로 아티팩트를 다운로드해 빠른 속도를 제공해요. sbt는 대부분의 의존성 해석을 처리할 때 이를 사용하며, 명령줄 도구로는 sbt, Java, Scala 같은 도구를 시스템에 쉽게 설치할 수 있게 해 줘요(시작하기 페이지에서 볼 수 있어요).
런치 웹 페이지의 이 예제는 cs launch 명령으로 의존성에서 애플리케이션을 실행할 수 있다는 걸 보여줘요.
$ cs launch org.scalameta::scalafmt-cli:2.4.2 -- --help
scalafmt 2.4.2
Usage: scalafmt [options] [<file>...]
-h, --help prints this usage text
-v, --version print version
more ...
자세한 내용은 Coursier의 런치 페이지를 참고하세요.
sbt와 ScalaTest 사용하기
ScalaTest는 스칼라 프로젝트의 주요 테스트 라이브러리 중 하나예요. 이 절에서는 ScalaTest를 사용하는 스칼라/sbt 프로젝트를 만들기 위해 필요한 단계를 보여드릴게요.
1) 프로젝트 디렉터리 구조 만들기
이전 수업과 마찬가지로, HelloScalaTest라는 프로젝트의 sbt 프로젝트 디렉터리 구조를 다음 명령으로 만들어요.
$ mkdir HelloScalaTest
$ cd HelloScalaTest
$ mkdir -p src/{main,test}/scala
$ mkdir project
2) build.properties와 build.sbt 파일 만들기
다음으로, 프로젝트의 project/ 하위 디렉터리에 이 한 줄을 담은 build.properties 파일을 만들어요.
sbt.version=1.10.11
다음으로, 프로젝트의 루트 디렉터리에 다음 내용을 담은 build.sbt 파일을 만들어요.
name := "HelloScalaTest"
version := "0.1"
scalaVersion := "3.9.0"
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.19" % Test
)
이 파일의 첫 세 줄은 이전 예제와 본질적으로 같아요. libraryDependencies 줄은 ScalaTest를 포함하는 데 필요한 의존성(JAR 파일)을 포함하라고 sbt에 알려줘요.
ScalaTest 문서는 늘 충실했고, 그 줄들이 어떻게 생겨야 하는지에 대한 최신 정보는 Installing ScalaTest 페이지에서 항상 찾을 수 있어요.
3) 스칼라 소스 코드 파일 만들기
다음으로, ScalaTest를 시연하는 데 쓸 스칼라 프로그램을 만들어요. 먼저 src/main/scala 아래에 math라는 디렉터리를 만들어요.
$ mkdir src/main/scala/math
----
그다음 그 디렉터리 안에 다음 내용을 담은 MathUtils.scala 파일을 만들어요.
package math
object MathUtils:
def double(i: Int) = i * 2
이 메서드는 ScalaTest를 시연하는 간단한 방법을 제공해요.
4) 첫 번째 ScalaTest 테스트 만들기
ScalaTest는 매우 유연해서 테스트를 작성하는 여러 가지 방법을 제공해요. 시작하기 좋은 간단한 방법은 ScalaTest의 AnyFunSuite를 사용해 테스트를 작성하는 거예요. 먼저 src/test/scala 디렉터리 아래에 math라는 디렉터리를 만들어요.
$ mkdir src/test/scala/math
----
다음으로, 그 디렉터리에 다음 내용을 담은 MathUtilsTests.scala 파일을 만들어요.
package math
import org.scalatest.funsuite.AnyFunSuite
class MathUtilsTests extends AnyFunSuite:
// test 1
test("'double' should handle 0") {
val result = MathUtils.double(0)
assert(result == 0)
}
// test 2
test("'double' should handle 1") {
val result = MathUtils.double(1)
assert(result == 2)
}
test("test with Int.MaxValue") (pending)
end MathUtilsTests
이 코드는 ScalaTest의 AnyFunSuite 방식을 보여줘요. 몇 가지 중요한 점이 있어요.
- 테스트 클래스는
AnyFunSuite를 상속해야 해요. - 보시다시피 각 테스트에 고유한 이름을 지어 만들 수 있어요.
- 각 테스트의 끝에서는 조건이 충족됐는지 확인하기 위해
assert를 호출해야 해요. - 테스트를 쓰고 싶다는 건 알지만 지금은 쓰고 싶지 않을 때는, 보여진 문법으로 테스트를 "pending"(보류) 상태로 만들어요.
이렇게 ScalaTest를 사용하는 것은 JUnit과 비슷해서, Java에서 Scala로 넘어온 사람이라면 익숙하게 느껴질 거예요.
이제 sbt test 명령으로 이 테스트들을 실행할 수 있어요. 출력의 첫 몇 줄은 건너뛰면, 결과는 이렇게 보여요.
sbt:HelloScalaTest> test
[info] Compiling 1 Scala source ...
[info] MathUtilsTests:
[info] - 'double' should handle 0
[info] - 'double' should handle 1
[info] - test with Int.MaxValue (pending)
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 1
[info] All tests passed.
[success] Total time: 1 s
모든 게 잘 동작한다면, 그런 출력이 보일 거예요. sbt와 ScalaTest로 스칼라 애플리케이션을 테스트하는 세계에 온 걸 환영해요.
다양한 유형의 테스트 지원
이 예제는 xUnit 테스트 주도 개발(TDD) 스타일 테스트와 비슷하면서도, 행동 주도 개발(BDD) 스타일의 장점도 몇 가지 가진 테스트 방식을 보여줘요.
앞서 언급했듯이 ScalaTest는 유연해서, Ruby의 RSpec과 비슷한 스타일 같은 다른 스타일로도 테스트를 작성할 수 있어요. 모의 객체(mock objects)를 쓰거나, 프로퍼티 기반 테스트(property-based testing)를 하거나, ScalaTest로 Scala.js 코드를 테스트할 수도 있어요.
사용 가능한 다양한 테스트 스타일에 대한 자세한 내용은 ScalaTest 웹사이트의 User Guide를 참고하세요.
다음에 어디로 갈까
sbt와 ScalaTest에 대한 더 많은 정보는 다음 리소스를 참고하세요.
더 알아보기 (Learn more)
- Scala 3 Book — Scala Tools — 스칼라 도구 개요
- Scala 3 Book — Where To Go Next — 다음 단계