목차

    도서관리 어플리케이션 이해하기

    • 우선 자바로 되어있는 코드를 코틀린으로 바꾼다고 생각하면 된다. 하단은 대략적인 프로젝트의 (흔한)플로우이다.

    • localhost:8080/h2-console 접속 및 로그인 정보

    테스트 코드란 무엇인가? 그리고 왜 필요한가?

    • 개발 과정에서 문제를 미리 발견할 수 있다.
    • 기능 추가와 리팩토링을 안심하고 할 수 있다.
    • 빠른 시간 내 코드의 동작 방식과 결과를 확인할 수 있다.
    • 좋은 테스트 코드를 작성하려 하다보면, 자연스럽게 좋은 코드가 만들어 진다.
    • 잘 작성한 테스트는 문서 역할을 한다.(코드리뷰를 돕는다)
    A라는 API는 25개의 다른 로직에 영향을 미친다. 어느날 A라는 API를 수정할 일이 생겼다. 그렇다면...?? 눈물이난다...

    코틀린 코드 작성 준비하기

    • 코틀린 build gradle 생성
    더보기
    plugins {
        id 'org.springframework.boot' version '2.6.8'
        id 'io.spring.dependency-management' version '1.0.11.RELEASE'
        id 'java'
        id 'org.jetbrains.kotlin.jvm' version '1.6.21'
        id 'org.jetbrains.kotlin.plugin.jpa' version '1.6.21'
        id 'org.jetbrains.kotlin.plugin.spring' version '1.6.21'
        id 'org.jetbrains.kotlin.kapt' version '1.6.21'
    }
    
    group = 'com.group'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = '11'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
        implementation 'org.springframework.boot:spring-boot-starter-web'
    
        implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' // 코틀린을 사용하기 위한 의존성 추가
        implementation 'org.jetbrains.kotlin:kotlin-reflect:1.6.21' // 코틀린을 사용하기 위한 의존성 추가
        implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.13.3' // 코틀린을 사용하기 위한 의존성 추가
        implementation 'org.junit.jupiter:junit-jupiter:5.8.1' // 코틀린을 사용하기 위한 의존성 추가
        implementation 'com.querydsl:querydsl-jpa:5.0.0' //querydsl 의존성
        kapt("com.querydsl:querydsl-apt:5.0.0:jpa")
        kapt("org.springframework.boot:spring-boot-configuration-processor")
    
        runtimeOnly 'com.h2database:h2'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
    
    tasks.named('test') {
        useJUnitPlatform()
    }
    
    /**
     * 코틀린에 필요한 compile 옵션 추가
     */
    compileKotlin {
        kotlinOptions {
            jvmTarget = "11"
        }
    }
    
    compileTestKotlin {
        kotlinOptions {
            jvmTarget = "11"
        }
    }

    사칙연산 계산기에 대해 테스트 코드 작성하기

    1. 계산기는 정수만 취급한다.

    2. 계산기가 생성될 때 숫자를 1개 받는다.

    3. 최초 숫자가 기록된 이후에는 연산자 함수를 통해 숫자를 받아 지속적으로 계산한다.

    데이터 클래스로 테스트 코드 검증하기

    더보기

    검증하고자 하는 클래스에 data를 붙여준다.

    package com.group.libraryapp.calculator
    
    import java.lang.IllegalArgumentException
    
    data class Calculator (
        private var number: Int
    ){
    
        fun add(operand: Int){
            this.number += operand
        }
    
        fun minus(operand: Int){
            this.number -= operand
        }
    
        fun multiply(operand: Int){
            this.number *= operand
        }
    
        fun divide(operand: Int){
            if(operand == 0){
                throw IllegalArgumentException("0으로 못나눔")
            }
            this.number /= operand
        }
    }

    메인을 만들어 테스트를 진행한다.

    package com.group.libraryapp.calculator
    
    import java.util.Calendar
    
    /**
     * main 생성
     */
    
    fun main(){
        val calculatorTest = CalculatorTest()
        calculatorTest.addTest();
    }
    class CalculatorTest {
        fun addTest(){
            val calculator = Calculator(5)
            calculator.add(3)
    
            val expectedCalculator = Calculator(8)
            if(calculator != expectedCalculator){
                throw IllegalStateException()
            }
        }
    }

    data를 지우고 number를 public으로 하거나 get을 열어주어 test에서도 접근가능하도록한다.

    코드 컨벤션 _number

    package com.group.libraryapp.calculator
    
    import java.lang.IllegalArgumentException
    
    class Calculator (
        //private var number: Int
        //var number: Int // setter를 연 상태
        private var _number: Int
    
    ){
    
        /**
         * getter를 연상태
         */
        val number: Int
            get() = this._number
    
        fun add(operand: Int){
            this._number += operand
        }
    
        fun minus(operand: Int){
            this._number -= operand
        }
    
        fun multiply(operand: Int){
            this._number *= operand
        }
    
        fun divide(operand: Int){
            if(operand == 0){
                throw IllegalArgumentException("0으로 못나눔")
            }
            this._number /= operand
        }
    }

    test쪽에서 바로 get 가능하다.

    package com.group.libraryapp.calculator
    
    import java.util.Calendar
    
    /**
     * main 생성
     */
    
    fun main(){
        val calculatorTest = CalculatorTest()
        calculatorTest.addTest();
    }
    class CalculatorTest {
        fun addTest(){
            // given
            val calculator = Calculator(5)
    
            //when
            calculator.add(3)
            /*
            val expectedCalculator = Calculator(8)
            if(calculator != expectedCalculator){
                throw IllegalStateException()
            }  */
            //then
            if(calculator.number != 8 ){
                throw  IllegalStateException()
            }
        }
    }

    Junit5 사용법과 테스트 코드 리팩토링

    Junit5에서 사용되는 5가지 어노테이션

    6

    • @Test : 테스트 
    • @BeforeEach : 각 테스트 메소드가 수행되기 전에 실행되는 메소드를 지정한다.
    • @AfterEach : 각 테스트가 수행된 후에 실행되는 메소드를 지정한다.
    • @BeforeAll : 모든 테스트를 수행하기 전에 최초 1회 수행되는 메소드를 지정한다.
    • @AfterAll : 모든 테스트를 수행한 후 최후 1회 수행되는 메소드를 지정한다.

    Test Code 작성

    더보기
    package com.group.libraryapp.calculator
    
    import org.junit.jupiter.api.*
    
    class JunitTest {
        companion object {
            @BeforeAll
            @JvmStatic
            fun beforeAll() {
                println("모든 테스트 시작 전")
            }
    
            @AfterAll
            @JvmStatic
            fun afterAll() {
                println("모든 테스트 종료 후")
            }
        }
    
        @BeforeEach
        fun beforeEach() {
            println("각 테스트 시작 전")
        }
    
        @AfterEach
        fun afterEach() {
            println("각 테스트 종료 후")
        }
    
        @Test
        fun test1() {
            println("테스트 1")
        }
    
        @Test
        fun test2() {
            println("테스트 2")
        }
    }

    결과

    계산기에 적용하기

    assertThat Imort 하기

    • AssertProvider 선택하기

    테스트 코드 작성하기

    @Test
    fun addTest(){
        // given
        val calculator = Calculator(5)
    
        // when
        calculator.add(3)
    
        // then
        assertThat(calculator.number).isEqualTo(7);
    }

    테스트 코드 결과

    추가로 사용하는 단언문

    • isTrue/isFalse : true/false 검증
    // then
    val isNew = true
    assertThat(isNew).isTrue();
    assertThat(isNew).isFalse();
    • hasSize(n) : size 검증 (주로 list의 갯수를 확인)

    • extracting/containsExactlyInAnyOrder : 주어진 컬렉션 안의 Item 들에서 name 이라는 프로퍼티를 추출한 후, 그 값이 A와 B인지를 검증한다.(AnyOrder : 이 때 순서는 중요하지 않다)

    • assertThrows : funtion1 함수를 실행했을 때 liigalArgumentException이 나오는지 검증

    만약 나온다면 message로 던져주는 메서드

    Junit5으로 Spring Boot 테스트 하기

    • Controller - bean 관리 대상이므로 @SpringBootTest로 진행
    • Service - bean 관리 대상이므로 @SpringBootTest로 진행
    • Repository - bean 관리 대상이므로 @SpringBootTest로 진행
    • Domain - bean 관리 대상이 아니므로 @Test 진행

    어떤 계층을 테스트 해야 할까?

    보통은 Service 계층을 테스트한다. 보통 A를 보냈을 때 B가 잘 나오는지, 원하는 로직을 잘 수행하는지 검증할 수 있기 때문이다.

     

    @Autowired 해주기

    class UserServiceTest @Autowired constructor(
        private val userRepository: UserRepository
        ,private val userService: UserService
    ) {

    위처럼 contrructor를 사용해서 한 번에 Autowired 해줄 수 있다.

     

    테스트 코드 작성하기

    1. 사칙연산 테스트 코드 작성

    2. UserTest 작성

    2.1 UserTest 작성(get)

    2.2 UserTest 작성(Delete)

    3. Book관련 Loan 관련 테스트 작성

     

    모든 테스트 Terminal로 실행하는 방법

    ./gradlew test

    결과

    다음과 같이 커버리지도 알 수 있다.

    2번 째 방법

    다음의 test를 눌러 본다. 더 권장 되는 방법으로 어디서 틀렸는지 쉽게 확인 가능하다.

    + Recent posts