[우테코] TDD로 자동차 경주 구현

Yerin·2023년 2월 15일

우아한테크코스

목록 보기
1/10
post-thumbnail

발렌타인데이 ~ 💖 에 들은 강의를 정리한 기록입니다...
나도 발렌타인걸한테 초콜릿 받았다 이말이야 ~ ✌

다음 로또 미션은 TDD 기반으로 미션을 진행해야하기 때문에 TDD와 관련하여 앞의 미션이었던 자동차 경주 구현을 다시 살펴보는 강의를 들었다. 역시나 강의자는 '제이슨' ~ 아 맞다. 그리구 제이슨이 레벨1 팀에서의 담당코치가 되었고, 발렌타인데이라고 초콜릿도 챙겨줬다. (달달한게 초콜릿인지 제이슨인지 🤷‍♀️ 암튼 서윗서윗)

TDD (Test Driven Development), 테스트 주도 개발

TDD란?

매우 짧은 개발 사이클을 반복하는 소프트웨어 개발 프로세스 중 하나이다. TDD는 단순한 설계를 장려하고 자신감을 불어 넣어 준다. (이말이 처음에는 이해안갔지만..!)

프로그래밍 순서

  1. 빨강 실패하는 작은 테스트를 작성한다. 처음에는 컴파일조차 되지 않을 수 있다.
  2. 초록 빨리 테스트가 통과하게끔 만든다. 이를 위해 어떤 죄악을 저질러도 좋다.
  3. 리팩터링 일단 테스트를 통과하게만 하는 와중에 생겨난 모든 중복을 제거한다.

원칙

  • 실패하는 단위 테스트를 작성할 때까지 구현 코드(production code)를 작성하지 않는다.
  • 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
  • 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

TDD를 통한 용기 🤭

강의에서 들은 이야기인데 너무 조와서 그대로 따왔다.

요구 사항 분석 및 설계

  • 요구 사항 분석을 통해 대략적인 설계 - 객체 추출
  • UI, DB 등과 의존 관계를 있지 않는 핵심 도메인 영역을 집중 설계
  • 일차적으로는 도메인 로직을 테스트하는 것에 집중

-> 나는 원래 InputView부터 차례대로 구현했었는데 TDD를 진행하면서는 도메인부터 설계하기 시작했다!

TDD를 하는데 익숙하지도 않고, 막막하다면?

대표적으로 테스트하기 어려운 코드는 뭐가 있을까?

내부 API : Random, shuffle, 날짜
외부 세계 : 외부 REST API, 데이터베이스 API

자료형이 아닌 여러 값들을 테스트 하는 방법

아래는 지금 페어와 함께하는 로또 미션인데 @MethodSource, ParameterizedTest, @JvmStatic을 통해 테스트 코드를 만들 수 있다. !

 @MethodSource("matchingCountNumbers")
    @ParameterizedTest
    fun `당첨 번호와 몇개 일치 하는지 판단 한다`(numbers: List<Int>, matchCount: Int) {
        val lotto = Lotto(numbers)
        val winningNumbers = listOf(1, 2, 3, 4, 5, 6)

        assertThat(lotto.countMatchingNumbers(winningNumbers)).isEqualTo(matchCount)
    }

    companion object {
        @JvmStatic
        fun matchingCountNumbers(): List<Arguments> {
            return listOf(
                Arguments.of(listOf(1, 2, 3, 7, 8, 9), 3),
                Arguments.of(listOf(1, 2, 3, 4, 8, 9), 4),
                Arguments.of(listOf(1, 2, 3, 4, 5, 9), 5),
                Arguments.of(listOf(1, 2, 3, 4, 5, 6), 6),
                )
        }
    }

역컴파일

내가 작성한 코틀린 코드가 Java로 어떻게 표현되는지 궁금할 때, 훌륭한 개발자가 언어 기능을 보다 현명하게 사용하기 위해 언어 기능이 어떻게 작동하는지 알아야 한다.
이때 역컴파일을 통해 숨겨진 비용을 탐색할 수 있다.

사실 무슨 말인지 잘 못알아들었는데 강의 자료는 가져와봤다.

class Person(val name: String, val age: Int) {
    var nickname: String? = null
}

Menu > Tools > Kotlin > Show Kotlin Bytecode (단축키 : 컨+쉬+A)
Decompile

public final class Person {
   @Nullable
   private String nickname;
   @NotNull
   private final String name;
   private final int age;

   @Nullable
   public final String getNickname() {
      return this.nickname;
   }

   public final void setNickname(@Nullable String var1) {
      this.nickname = var1;
   }

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final int getAge() {
      return this.age;
   }

   public Person(@NotNull String name, int age) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super();
      this.name = name;
      this.age = age;
   }
}

원시값 포장, Wrapping

❔ 왜 원시값을 포장하나요?
들어오는 통로가 한가지로 묶이지 않을 가능성.. 높아짐
하나로 묶어주면 한번에 관리가 가능해진다

이걸 응용해본다면?

// 부생성자
constructor(name: String, position: Int): this(CarName(name), position))

-> override toString() 하지말고 부생성자로 name을 String으로 연결하자


수업 마지막 질문 !

❔ 둘의 차이는 무엇인가?

val name1: String = carName.name
val name2: String 
	get() = carName.name

name1 값은 객체를 생성할 때의 name으로 결정되며, name이 그 이후에 바뀐다고 해서 name1이 함께 바뀌지 않는다.
그러나 name2 값은 getter 호출 시점의 name으로 반환된다.
-> 생성당시에 name1은 변수로 할당되고 name2는 함수로 할당됩니다!



👻 maxOf, maxBy
👻 코틀린 역컴파일

profile
𝙸 𝚐𝚘𝚝𝚝𝚊 𝚕𝚒𝚟𝚎 𝚖𝚢 𝚕𝚒𝚏𝚎 𝙽𝙾𝚆, 𝙽𝙾𝚃 𝚕𝚊𝚝𝚎𝚛 !

0개의 댓글