IMCAT.DEV
로그인 가입
Scala 중급 테스트

Scala 테스트 주도 개발 (TDD)

Scala 테스트 주도 개발 (TDD) — 중급

테스트 주도 개발 (TDD)

1. 도입 (왜 배워야 하는가)

테스트 주도 개발(Test-Driven Development, TDD)은 소프트웨어 개발에서 품질을 보장하고, 코드의 유지 보수를 쉽게 하기 위한 중요한 방법론입니다. TDD를 통해 개발자가 작성하는 코드가 실제로 요구 사항을 충족하는지를 빠르게 확인할 수 있으며, 버그를 사전에 예방할 수 있습니다.

TDD를 모른다면 다음과 같은 문제가 발생할 수 있습니다:

  • 버그 증가: 개발 후에 테스트를 진행하면 이미 구현된 코드에서 문제가 발생할 확률이 높아집니다.
  • 유지 보수 어려움: 기존 코드에 대한 테스트가 없으면 나중에 기능 변경이나 버그 수정 시 불안정해질 수 있습니다.
  • 의사소통 문제: 팀원 간에 코드의 의도나 기능에 대한 이해가 부족해질 수 있습니다.
  • 2. 핵심 개념 설명

    TDD의 핵심 개념은 "테스트를 먼저 작성하고, 그 테스트를 통과할 수 있는 코드를 작성하는 것" 입니다. 이 과정을 반복하면서 점진적으로 기능을 구현해 나갑니다.

  • Red-Green-Refactor: TDD의 기본 사이클입니다.
  • - Red: 테스트를 작성하고 실패하는 상태를 확인합니다. - Green: 테스트를 통과하는 최소한의 코드를 작성합니다. - Refactor: 코드를 개선하고 최적화합니다.

    관련 용어 정리

    용어설명
    테스트 케이스특정 기능에 대한 테스트를 정의하는 코드
    리팩토링코드 구조를 개선하는 과정
    유닛 테스트개별 함수 또는 메서드를 테스트하는 것

    3. 기본 코드 예제

    다음은 Scala에서 TDD를 적용해 간단한 덧셈 기능을 구현하는 예제입니다.

    // 덧셈 기능을 테스트하는 클래스
    import org.scalatest.flatspec.AnyFlatSpec
    import org.scalatest.matchers.should.Matchers
    
    class CalculatorSpec extends AnyFlatSpec with Matchers {
      "Addition" should "correctly add two numbers" in {
        val calculator = new Calculator()
        calculator.add(2, 3) should be(5) // 테스트 케이스
      }
    }
    
    // 덧셈 기능을 구현하는 클래스
    class Calculator {
      def add(a: Int, b: Int): Int = a + b
    }

    실행 결과: 테스트가 통과하면 아무런 오류 메시지가 없습니다.

    코드 한 줄씩 동작 설명

    1. import org.scalatest.flatspec.AnyFlatSpec - ScalaTest 라이브러리에서 FlatSpec을 임포트합니다. 이는 테스트를 작성하기 위한 기본 클래스입니다.
    2. class CalculatorSpec extends AnyFlatSpec with Matchers - CalculatorSpec 테스트 클래스를 정의합니다. Matchers를 사용하여 테스트 결과를 쉽게 검증합니다.
    3. "Addition" should "correctly add two numbers" in { ... } - "Addition"이라는 주제로 테스트를 정의하고, 그 안에 테스트 내용을 작성합니다.
    4. val calculator = new Calculator() - Calculator 클래스의 인스턴스를 생성합니다.
    5. calculator.add(2, 3) should be(5) - add 메소드를 호출하여 결과가 5인지 확인합니다.
    6. 4. 실전 활용 예제

      1. 쇼핑몰에서 장바구니를 구현할 때

      장바구니에 상품을 추가할 때 TDD를 활용하여 다음과 같은 테스트를 작성할 수 있습니다.

      class ShoppingCartSpec extends AnyFlatSpec with Matchers {
        "ShoppingCart" should "add items properly" in {
          val cart = new ShoppingCart()
          cart.addItem("Apple", 1) // 사과 1개 추가
          cart.getItems should contain ("Apple") // 사과가 장바구니에 있어야 함
        }
      }
      
      class ShoppingCart {
        private var items: List[String] = List()
      
        def addItem(item: String, quantity: Int): Unit = {
          items = items :+ item // 장바구니에 아이템 추가
        }
      
        def getItems: List[String] = items // 장바구니의 아이템 반환
      }

      2. 사용자 로그인 기능 구현

      사용자가 올바른 자격 증명으로 로그인하는지 테스트하는 예입니다.

      class UserSpec extends AnyFlatSpec with Matchers {
        "User" should "authenticate with valid credentials" in {
          val user = new User("testUser", "password123")
          user.authenticate("testUser", "password123") should be(true) // 로그인 성공
        }
      }
      
      class User(val username: String, val password: String) {
        def authenticate(inputUsername: String, inputPassword: String): Boolean = {
          inputUsername  username && inputPassword  password // 인증 로직
        }
      }

      5. 흔한 실수와 주의사항

      1. 테스트를 작성하지 않고 코드 구현

    7. 잘못된 코드:
    8.   class Calculator {
          def add(a: Int, b: Int): Int = a + b // 테스트 없이 구현
        }
    9. 올바른 코드: 테스트 케이스를 작성한 후 구현해야 함.
    10. 2. 테스트 실패 원인을 파악하지 않음

    11. 잘못된 코드:
    12.   calculator.add(2, "3") // 타입 불일치로 실패
    13. 올바른 코드: 입력 타입을 확인하고 맞게 테스트해야 함.
    14. 6. 정리

    15. TDD는 품질 높은 코드를 작성하는 데 도움을 줍니다.
    16. Red-Green-Refactor 사이클을 통해 점진적으로 기능을 구현합니다.
    17. 테스트를 먼저 작성하고 그에 맞춰 코드를 작성하는 것이 중요합니다.
    18. 다음에 학습하면 좋은 주제

    19. Mocking과 Stubbing: 테스트에서 의존성을 관리하는 방법
    20. 통합 테스트와 유닛 테스트의 차이
    21. CI/CD 파이프라인에서 TDD 적용하기