ZIO로 콘솔 입출력 테스트하기

CHEESE·2023년 8월 22일
0

OSSCA 2023

목록 보기
6/7
post-thumbnail

TestConsole

콘솔 프로그램에서 입출력 함수를 테스트하려면 어떻게 해야 할까?
보통의 경우에는 함수를 분리해서 연산 결과만 테스트한다.
함수를 분리해서 단위테스트를 하는 것은 각 함수를 실행한 후의 결과를 검증하기에는 좋지만, e2e 테스트와 같은 전체 프로세스가 정상적으로 동작하는 지 여부를 검증하기는 어렵다.
하나 하나의 단위가 동작하는 것도 중요하지만 전체 프로세스가 정상 동작해야 사용할 수 있는 프로그램이니까!
e2e 테스트를 진행하기 위해 콘솔 입출력 테스트가 불가피할 경우 ZIO의 TestConsole을 이용하면 콘솔과 상호작용하는 테스트 코드를 작성할 수 있다.

메소드 종류

ZIO의 TestConsole이 제공하는 메소드를 살펴보자

feedLines

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")
		}
	}
)

output

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) 비교
		}
	}
)

clearInput / clearOutput

버퍼를 지운다.

테스트에 의존성 추가하기

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를 사용하는 것을 알 수 있다.

0개의 댓글