[우테코/5기/안드로이드] 2주차 프리코스 톺아보기

Falco·2022년 11월 20일
0

우테코 프리코스

목록 보기
2/5
post-thumbnail
post-custom-banner

2주 차 공통 피드백

README.md를 상세히 작성한다

프로젝트를 진행할 때 해당 프로젝트가 어떠한 프로젝트이며, 어떤 기능을 담고 있는지 기술한다.

기능 목록을 재검토한다

너무 세세한 부분까지 정리하기보다 구현해야 할 기능 목록을 정리하는 데 집중한다.
정상적인 경우도 중요하지만, 예외적인 상황도 기능 목록에 정리한다.

기능 목록을 업데이트한다

기능을 구현하면서 문서를 계속 업데이트한다. 죽은 문서가 아니라 살아있는 문서를 만들기 위해 노력한다.

값을 하드 코딩하지 않는다

문자열, 숫자 등의 값을 하드 코딩하지 마라. 상수를 만들고 이름을 부여해 이 변수의 역할이 무엇인지 의도를 드러내라.

    companion object{
        const val CONST_VAL = 100
    }

코틀린에서의 상수는 대문자 스네이크 케이스로 작성하며 일반적으로 companion object 안에 상수로 선언하게 된다.

여기서 const는 컴파일을 진행하며 결정되는 상수이며, 런타임에 할당되는 val 와 달리 컴파일 시간 동안 할당이 되어야 한다.

즉, const 는 함수나 어떤 클래스의 생성자에게도 결코 할당 될 수 없고 오직 문자열이나 기본 자료형으로 할당되어야 한다.

구현 순서도 코딩 컨벤션이다

클래스는 프로퍼티, init 블록, 부 생성자, 메서드, 동반 객체 순으로 작성한다.

class StudentClass(private val name: String) {
    val students: Int // 프로퍼티

    init {
    
    } // init 블록

	StudentClass(name: String, subname: String) {
    
    } // 부 생성자

    private fun addStudents(): Unit { } // 메서드

	companion object {
      // ..
    } // 동반 객체
}

변수 이름에 자료형은 사용하지 않는다

변수 이름에 자료형, 자료 구조 등을 사용하지 마라.

val carNameList: String = Console.readLine()
val listString: List<String> = carNameList.split(",")

한 함수가 한 가지 기능만 담당하게 한다

함수 길이가 길어진다면 한 함수에서 여러 일을 하려고 하는 경우일 가능성이 높다. 아래와 같이 한 함수에서 안내 문구 출력, 사용자 입력, 유효값 검증 등 여러 일을 하고 있다면 이를 적절하게 분리한다.

fun userInput(): List<Int> {
   println("숫자를 입력해 주세요: ")
   val userInput = Console.readLine().trim()
   val user = mutableListOf<Int>()
   for (c in userInput) {
       user.add(c.digitToInt())
   }
   require(user.size == 3) { "[ERROR] 숫자가 잘못된 형식입니다." }
   return user
}

함수가 한 가지 기능을 하는지 확인하는 기준을 세운다

  • 여러 함수에서 중복되어 사용되는 코드가 있다면 함수 분리를 고민해 본다.
  • 함수의 길이를 15라인을 넘어가지 않도록 구현하며 함수를 분리하는 의식적인 연습을 할 수 있다.

테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다

단지 기능을 점검하기 위한 목적으로 테스트를 작성하는 것은 아니다.
테스트를 작성하는 과정을 통해서 나의 코드에 대해 빠르게 피드백을 받을 수 있을 뿐만 아니라 학습 도구로도 활용할 수 있다.
이런 경험을 통해 테스트에 대해 어떤 유용함을 느꼈는지 알아본다.

처음부터 큰 단위의 테스트를 만들지 않는다

테스트의 중요한 목적 중 하나는 내가 작성하는 코드에 대해 빠르게 피드백을 받는 것이다. 시작부터 큰 단위의 테스트를 만들게 된다면 작성한 코드에 대한 피드백을 받기까지 많은 시간이 걸린다.
그래서 문제를 작게 나누고, 그 중 핵심 기능에 가까운 부분부터 작게 테스트를 만들어 나간다.

큰 단위의 테스트

숫자 야구 게임을 시작해서 사용자가 숫자를 입력하면, 컴퓨터 숫자와 비교하여 그 결과를 알려준다.

작은 단위의 테스트

사용자의 숫자가 컴퓨터의 숫자와 하나도 일치하지 않으면 낫싱을 출력한다.
사용자의 숫자가 컴퓨터의 숫자와 1개는 일치하고, 위치가 다르면 1볼을 출력한다.

What I Learn

테스트 코드 작성 연습

테스트를 작성하는 이유가 무엇인가?

  1. 내가 생각한 기능에서 예상치 못한 문제를 미리 발견할 수 있다.
  2. 테스트 코드를 작성하며 코드의 모듈화를 한번 더 고민하게 된다.
    • 해당 기능이 어떤 도메인 객체에서 이루어져야하는지
  3. 코드가 업데이트 시 기능이 동일하게 작동함을 알 수 있다.
  4. 코드 변경에 대한 사이드 이펙트를 줄여준다.

2주차 테스트코드 작성 스터디

테스트를 작성하기 위해서는 @Test어노테이션을 붙여 테스트 함수로 만들어야 한다.

@Test
fun `"1,2"을 , 로 split 했을 때 1과 2로 잘 분리되는지 확인하는 학습 테스트를 구현한다`() {
    val input = "1,2"
    assertThat(input.split(',')).containsAll(listOf("1", "2"))
}

@Test
fun `"1"을 , 로 split 했을 때 1만을 포함하는 배열이 반환되는지에 대한 학습 테스트를 구현한다`() {
    val input = "1"
    assertThat(input.split(',')).containsExactly("1")
}

@Test
fun `(1,2) 값이 주어졌을 때 String의 substring() 메소드를 활용해 () 을 제거하고 "1,2"를 반환하도록 구현한다`() {
    val input = "(1,2)"
    assertThat(input.substring(1, 4)).isEqualTo("1,2")
}

@DisplayName 어노테이션을 활용하여 보여지는 이름을 수정할 수 있다.

@BeforeEach,@AfterEach 어노테이션을 통해 해당 테스트가 진행되기 전, 후에 작업을 정의할 수 있다.

@ParameterizedTest를 통해 해당 파라미터를 통해 해당 값들을 테스트할 수 있다.

class SetTest {
    private lateinit var numbers: MutableSet<Int>

    @BeforeEach
    fun setUp() {
        numbers = HashSet()
        numbers.add(1)
        numbers.add(1)
        numbers.add(2)
        numbers.add(3)
    }

    @Test
    @DisplayName("Set의 size() 메소드를 활용해 Set의 크기를 확인하는 학습테스트를 구현한다.")
    fun setSizeTest() {
        assertThat(numbers.size).isEqualTo(4)
    }

    @ParameterizedTest
    @ValueSource(ints = [1, 2, 3])
    @DisplayName("1, 2, 3의 값이 존재하는지를 확인한다.")
    fun containsTest(value: Int) {
        assertThat(numbers).contains(value)
        // or
        assertThat(numbers.contains(value)).isEqualTo(true)
        // or
        assertTrue(numbers.contains(value))
    }

}

여러개의 인자를 통해 테스트하고 싶다면 @CsvSource를 활용할 수 있다.

@ParameterizedTest
@CsvSource({"test,TEST", "tEst,TEST", "Java,JAVA"})
void toUpperCase_ShouldGenerateTheExpectedUppercaseValue(String input, String expected) {
    String actualValue = input.toUpperCase();
    assertEquals(expected, actualValue);
}

자주 발생하는 오류에 대해서는 다음과 같은 메서드를 활용할 수 있다.

  • assertThatIllegalArgumentException()
// 다음과 동일
assertThrows<IllegalArgumentException> { }
  • assertThatIllegalStateException()
  • assertThatIOException()
  • assertThatNullPointerException()

assertThat()의 확장함수로 정말로 많은 테스트를 제공하고 있다. 이를 활용하여 가독성 높은 테스트를 만들어보자.

더 많은 assertJ활용법

느낀점

야구 게임이라는 간단한 동작을 1주일이나 미션으로 진행하라는 것이 어떤 의미일까 생각다. 그 의미를 추가된 요구 사항에서 찾아 볼 수 있었다.

요구사항

  1. indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라
  2. JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
  • 첫 번째
    함수를 작게 만들어서 각각의 함수 즉 기능에 대한 책임을 줄이는 것
    이러한 소스는 가독성을 증가시키고, 나중에 함수를 리팩토링할 때나 기능을 변경할 때 유용하기 때문이다.

  • 두 번째
    단위테스트의 작성에 있어 메소드의 기능이 한 가지 일만 수행하는 것이 유리하다.
    테스트 코드를 만들면서 테스트케이스를 작성할 때 함수가 여러 가지 일을 동시에 한다면 테스트가 특정 상황에서만 작동하며, 후에 코드가 디벨롭됬을 때 작성된 이런 테스트는 의미가 없어 질 것이다.

간단한 야구 게임이었지만 익숙하지 않은 테스트코드의 작성에 대해 잘 알 수 있었다.

profile
강단있는 개발자가 되기위하여
post-custom-banner

0개의 댓글