Test Code 작성을 생활화 합시다

2024. 4. 25. 17:10TIL

✔오늘 배운 중요한 🔑 point

  • 소프트웨어의 기능과 동작을 테스트하는 테스트 코드는 유지보수 안정성 측면에서 반드시 필요한 코드작성이며 개발자 간의 협업을 원할하게 하기 때문에 매우 중요하다
  • 테스트 코드를 작성할때는 어떠한 상황에서도 실행이 가능해야한다 , 데이터베이스 연동이라던가 네트워크 이슈가 생기더라도 테스트 코드는 실행이 되도록 작성을 해야하는것이 중요하다.
  • Given-When-Then" 구조를 사용하면 각 테스트 케이스의 시나리오를 명확하게 정의할 수 있고 각 부분이 명확하게 구분되어 있어서 테스트의 목적과 예상 결과를 이해하기 쉽다.
  • Ctrl+Alt+L:코드 정렬
  • Ctrl+Alt+O: 사용하지 않는 import문 제거

🎯 오늘 배운 내용

Test Code

프로그램의 문제를 빨리 발견하고 해결하기 위한 코드

First 규칙


Fast: 테스트는 빠르게 동작해야 한다.
Independent : 각 테스트는 서로 의존해선 안되며,아무 순서로 실행이 가능해야한다.
Repeatable : 테스트는 어떤 환경에서도 반복 가능해야 한다.
Self-Validating : 테스트는 성공 또는 실패로 bool 값으로 결과를 내어 검증해야 한다.
Timely : 테스트 코드는 테스트하려는 실제 코드를 구현하기 직전에 구현해야 한다.

 

 

테스트 코드 작성 전 준비과정

경로)  본인이 작성하고있는 파일 -> src ->  build.gradle.kts

 

 

dependencies {
    testImplementation("org.jetbrains.kotlin:kotlin-test")

    testImplementation("io.kotest:kotest-runner-junit5:5.4.2")
    testImplementation("io.kotest:kotest-assertions-core:5.4.2")
}

해당코드를 붙여넣게 되면  해당 라이브러리를 사용할 수 있게된다

 

setting -> Plugins -> Kotest검색 -> Install

 

테스트 할 목적이있는 클래스명에 우클릭 -> Generate -> Test

 

 

Testing library -> Kotest 설정  Superclass : BehaviorSpec 설정 -> OK

 

 

과녁 표시를 누르면 현재 열려있는 test파일 위치를 알수 있다

 

 

테스트 코드를 작성해보자!

import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe

class BagTest : BehaviorSpec({
    Given("이러한 조건이 주어졌을때~ "){
        val validMaxWeight = 10
        When("이러한 기능을 실행하면~"){
            val result=Bag(validMaxWeight)
            Then("이러한 결과가 나와야한다~"){
                result.maxWeight shouldBe validMaxWeight

            }
        }
    }
    
})

 

테스트 실행 결과

Tests passed: 테스트 결과에서 통과한 테스트 수. 이 경우에는 1개의 테스트가 통과
1 of 1 test: 총 테스트 중 통과한 테스트 수
37 ms: 테스트가 실행된 시간 (0.037초)

 

 

정상적인 상황이 아닌 비정상적인 상황에서 테스트 코드를 작성해보자 !!

import io.kotest.assertions.exceptionToMessage
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe

class BagTest : BehaviorSpec({
    Given("이러한 조건이 주어졌을때~ "){
        val validMaxWeight = 10
        When("이러한 기능을 실행하면~"){
            val result=Bag(validMaxWeight)
            Then("이러한 결과가 나와야한다~"){
                result.maxWeight shouldBe validMaxWeight

            }
        }
    }

    Given("maxWeight가 0으로 주어졌을때 "){
        val MaxWeight = 0
        When("Bag의 매개변수에 집어넣게 되면 예외처리가 throw되어야 하고"){
            val exception=shouldThrow<Exception> { Bag(MaxWeight) }  //Bag(MaxWeight)가 생성자를 호출하는 코드를 진행하면 반드시 Exception이 발생해야해!
            Then("그 exception의 메시지는 가방의 최대 무게가 잘못 설정되었습니다 라는 메시지가 나와야한다"){
                exception.message shouldBe "가방의 최대 무게가 잘못 설정되었습니다."

            }
        }
    }

    Given("maxWeight가 음수로 주어졌을때 "){
        val MaxWeight = -425
        When("Bag의 매개변수에 집어넣게 되면 예외처리가 throw되어야 하고"){
            val exception=shouldThrow<Exception> { Bag(MaxWeight) }
            Then("그 exception의 메시지는 가방의 최대 무게가 잘못 설정되었습니다 라는 메시지가 나와야한다"){
                exception.message shouldBe "가방의 최대 무게가 잘못 설정되었습니다."

            }
        }
    }

})

 

 

 

 

🤔 어떻게 활용할까?

 

이전에 작성한 계산기 프로그램의 파일에 대한 Test code를 작성해보자

abstract class Calculator {
    abstract  fun operation(num1:Double,num2:Double):Double
}

class AddOperation():Calculator(){
    override fun operation(num1:Double,num2:Double):Double {
        return num1+num2
    }
}
class SubstractOperation():Calculator(){
    override fun operation(num1:Double,num2:Double):Double {
        return num1-num2
    }
}
class MultiplyOperation():Calculator(){
    override fun operation(num1:Double,num2:Double):Double {
        return num1*num2
    }
}
class DivideOperation():Calculator(){
    override fun operation(num1:Double,num2:Double):Double {
        return num1/num2
    }
}
class RemainderOperation():Calculator(){
    override fun operation(num1:Double,num2:Double):Double {
        return num1%num2
    }
}

fun main(){
    var calList:MutableList<Double>
    while(true){
        calList=startmenu()
        when(calList[0].toInt()){
            1->{
                println("더하기 수행! ${calList[1]} + ${calList[2]} = ${calList[1]+calList[2]} ")
                println(AddOperation().operation(calList[1],calList[2]))
            }
            2->{
                println("빼기 수행! ${calList[1]} - ${calList[2]} = ${calList[1]-calList[2]} ")
                println(SubstractOperation().operation(calList[1],calList[2]))
            }
            3->{
                println("곱하기 수행! ${calList[1]} x ${calList[2]} = ${calList[1]*calList[2]} ")
                println(MultiplyOperation().operation(calList[1],calList[2]))
            }
            4->{
                println("나누기 수행! 몫 반환 ${calList[1]} / ${calList[2]} = ${calList[1]/calList[2]} ")
                println(DivideOperation().operation(calList[1],calList[2]))
            }
            5->{
                println("나누기 수행! 나머지 반환 ${calList[1]} % ${calList[2]} = ${calList[1]%calList[2]} ")
                println(RemainderOperation().operation(calList[1],calList[2]))
            }
        }
        println("연산이 끝났습니다 프로그램을 종료하시겠습니까? y/n")
        val endornot= readLine()!!.toString()
        if(endornot=="y"){
            println("계산기 프로그램을 종료합니다")
            break
        }
        else{

        }
    }

}

fun startmenu() : MutableList<Double>{
    var choicemenu=0.0
    var firstnum=0.0
    var secondnum=0.0
    while(true){
        println("원하는 메뉴를 숫자로 입력해보세요")
        println("1->더하기        2->빼기       3-> 곱하기     4-> 나누기(몫)         5->나누기(나머지)")

        try{
            choicemenu= readLine()!!.toDouble()
            println("연산을 적용시킬 첫번째 숫자 입력:")
            firstnum= readLine()!!.toDouble()
            println("연산을 적용시킬 두번째 숫자 입력:")
            secondnum= readLine()!!.toDouble()
            break
        }
        catch (e:NumberFormatException){
            println("문자열이 아닌 숫자(정수)만 입력해주세요")
        }
    }
    var calList= mutableListOf<Double>(choicemenu,firstnum,secondnum)
    return calList // LIST형태로 사용자 입력값 3개 반환
}

기존 코드

계산기 프로그램의 test code를 작성

import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe

class AddOperationTest : BehaviorSpec({
    Given("더하기 class의 인스턴스를 생성한 상황에서") {
        val addOperation = AddOperation()

        When("인스턴스에 접근해서 operation함수 매개변수에 3이랑 5를 집어넣었을때") {
            val result = addOperation.operation(3.0, 5.0)

            Then("결과값으로 8이 되어야만 한다") {
                result  shouldBe 8.0
            }
        }
    }
})


class SubtractOperationTest : BehaviorSpec({
    Given("빼기 class의 인스턴스를 생성한 상황에서") {
        val subtractOperation = SubstractOperation()

        When("빼기 class안에 있는 operation함수 안에 매개변수 8,3을 넣었을때") {
            val result = subtractOperation.operation(8.0, 3.0)

            Then("5.0이 나와야만 한다") {
                result shouldBe 5.0
            }
        }
    }
})

class MultiplyOperationTest :BehaviorSpec({
    Given("곱하기 class의 인스턴스를 생성한 상황에서"){
        val multiply = MultiplyOperation()
        When("곱하기 함수에 3,10을 넣었을때"){
            val result=multiply.operation(3.0,10.0)
            Then("30.0이 나와야한다"){
                result shouldBe 30.0
            }
        }
    }
})
class StartMenuTest : BehaviorSpec({
    Given("스타트메뉴가 호출된 상황에서") {
        startmenu()
        When("사용자가 입력한 값이 1,20,30이라면") {
            val choicemenu = 1.0
            val firstnum = 20.0
            val secondnum = 30.0
            val result = mutableListOf<Double>()

            Then("1.0,20.0,30.0이 담겨져있는 리스트를 반환해야만 한다") {
                result shouldBe mutableListOf<Double>(choicemenu, firstnum, secondnum)
            }
        }
    }
})
startMenu()함수의 testcode작성을 위한 class StartMenuTest는 유일하게 오류가 발생하여 test가 실패하였는데 startMenu()함수의 choicemenu,firstnum,secondnum등의 값들은 readLine()을 통해서 입력받기 때문에 테스트 코드 환경에는 부합하지 않는다. 물론 그럼에도 불구하고 따로 함수를 정의해서 테스트코드 상에서만 작동하는 함수를 테스트코드 내에 선언을 하는 식으로 해결할 수 있지만  이 경우에는 취지에 맞지 않는다고 생각하여 하지 않았다. 코딩연습과정에서는 readLine()등을 사용하면서 코딩을 하고 있지만 실제 환경에서는 HTTP요청을 통해 처리한다고 한다.

 

 

📓 오늘의 한 문장

The essence of the beautiful is unity in variety.   

- William Somerset Maugham -

'TIL' 카테고리의 다른 글

(알고리즘) 나누어 떨어지는 숫자 배열  (0) 2024.04.27
여러작업을 한번에 수행하자  (0) 2024.04.26
Class에 관하여  (0) 2024.04.24
Kotlin 기초문법  (0) 2024.04.23
Kotlin과 사용규칙  (0) 2024.04.22