콘솔 프로그램에서 입출력 함수를 테스트하려면 어떻게 해야 할까?
보통의 경우에는 함수를 분리해서 연산 결과만 테스트한다.
함수를 분리해서 단위테스트를 하는 것은 각 함수를 실행한 후의 결과를 검증하기에는 좋지만, e2e 테스트와 같은 전체 프로세스가 정상적으로 동작하는 지 여부를 검증하기는 어렵다.
하나 하나의 단위가 동작하는 것도 중요하지만 전체 프로세스가 정상 동작해야 사용할 수 있는 프로그램이니까!
e2e 테스트를 진행하기 위해 콘솔 입출력 테스트가 불가피할 경우 ZIO의 TestConsole
을 이용하면 콘솔과 상호작용하는 테스트 코드를 작성할 수 있다.
ZIO의 TestConsole이 제공하는 메소드를 살펴보자
readLine에 문자열을 공급한다.
import zio._
import zio.test._
import zio.test.Assertion._
val testSuite = suite("ConsoleTest")(
test("test input of console") {
for {
_ <- TestConsole.feedLines("cheese", "5")
_ <- zio.Console.printLine("이름이 무엇입니까?")
name <- zio.Console.readLine // "cheese" 공급
_ <- zio.Console.printLine("몇 살입니까?")
age <- zio.Console.readLine // "5" 공급
} yield {
assertTrue(name == "cheese") &&
assertTrue(age == "5")
}
}
)
printLine
으로 만들어진 내용을 가져온다.
import zio._
import zio.test._
import zio.test.Assertion._
val testSuite = suite("ConsoleTest")(
test("test input of console") {
for {
_ <- TestConsole.feedLines("Yes")
answer <- zio.Console.readLine("Yes or No => ")
_ <- zio.Console.printLine(s"${answer}라고 대답했어요.") // (1)
output <- TestConsole.output // 콘솔에 출력된 모든 값
} yield {
assertTrue(output(0) == "Yes라고 대답했어요.") // 0번째 출력(1) 비교
}
}
)
버퍼를 지운다.
e2e 테스트를 작성할 때 데이터베이스에 저장된 값을 가져오는 등 외부 의존성이 필요한 경우가 있다.
이는 어떻게 테스트할 수 있을까?
사용자의 이름, 취미를 입력받아 데이터베이스에 저장하는 함수를 테스트한다고 가정할 때, 함수 실행 후 데이터베이스를 직접 조회하여 정상적으로 저장되었는지를 검증하는 코드를 작성해보자.
import doobie.implicits.toSqlInterpolator
import io.github.gaelrenoux.tranzactio.ConnectionSource
import io.github.gaelrenoux.tranzactio.doobie.{Database, tzio}
import zio._
import zio.test._
case class User(name: String, hobby: String)
object ReservationTest2 extends ZIOSpecDefault {
override def spec = suite("readRestaurant")(
test("test DI") {
for {
_ <- ZIO.unit
db <- ZIO.service[Database.Service]
name = "cheese"
hobby = "잠자기"
expected = User(name, hobby)
_ <- TestConsole.feedLines(name, hobby)
// 로직 실행
_ <- UserProgram.saveUser
// 데이터베이스 조회
saved <- db
.transactionOrWiden(for {
res <- tzio {
sql"""|select *
|from user
|where name=${expected.name} and hobby=${expected.hobby}
""".stripMargin
.query[User]
.unique
}
} yield res)
} yield {
assertTrue(expected == saved) // 기대한 값이 저장되었는지 비교
}
}
).provideLayer(conn >>> ConnectionSource.fromConnection >>> Database.fromConnectionSource)
private val conn = ZLayer(
ZIO.attempt(
java.sql.DriverManager.getConnection(
s"jdbc:postgresql://DB_URL/postgres?user=DB_USER&password=DB_PW"
)
)
)
}
테스트 코드에 의존성을 추가할 때에도, 일반적인 기능 코드에 의존성을 추가하는 것과 동일하게 provideLayer
를 사용하는 것을 알 수 있다.