13 minute read

최근에 발표 스터디에서 이번엔 무슨 주제로 발표를 할까… 하다가, 평소에 내가 테스트 코드에 대해 생각하고 있는 내용들을 잘 다듬어서 공유하면 좋겠다는 생각이 들었다.

이를 바탕으로 아직 테스트 코드를 작성하지 않고 있는 엔지니어분들이(특히 서버 사이드) 왜 테스트 코드를 작성해야 하는지 간단하게 발표했다.

이 내용을 그냥 흘려 보내기엔 좀 아까운 것 같아서, 형태를 다듬어서 글의 형태로도 작성하게 되었다.

목차

  1. 개요
  2. 테스트 코드는 이렇다던데
  3. 예를 들어 보자
  4. 테스트 코드는 프로덕트의 일부다

개요

테스트 코드는 이미 많은 사람들이 들어 본 키워드다. 인프런 김영한님의 강의, 동욱님이 작성하신 웹 서비스 책 등 많은 매체에서 테스트 코드를 프로젝트 개발의 한 과정으로서 다룬다. 이는 소위 ‘네카라쿠배’ 라고 하는 IT 기업들에서는 당연히 관습화된 것이며, 무릇 ‘좋은 개발자’라면 당연히 작성해야 하는 것으로 여겨지기에 이르렀다.

테스트 코드를 왜 작성해야 할까? 이는 여전히 많은 사람들의 관심사이다. 동시에, 이미 많은 사람들이 테스트 코드를 작성하면 뭐가 좋은지, 어떤 점에서 도움이 되는지 언급하고 있다.

google_02 google_01
테스트 코드 관련 구글 검색어 흔히 알려져 있는 테스트 코드 작성 이유

그런데 이런 이유들을 보면 많은 사람들은 이런 생각이 든다.

  • 그런데… 이유는 알겠는데 그래서?
  • 당장 내가 쓸 수 있나?

이런 의문 해소에 도움이 되고자, 실제로 테스트 코드를 작성했을 시의 효과를 예시와 함께 소개해 보고자 한다. 동시에, 엔지니어로서 우리가 테스트 코드를 바라보는 시선에 대해서도 내 의견을 가볍게 얹어 보려고 한다. 이미 테스트 코드를 잘 작성하고 계신 분들보다는, 아직 테스트 코드에 대해 물음표가 남아 있는 분들에게 도움이 될 글이다. 예시 코드는 코틀린으로 작성하였지만, 크게 어려운 내용은 없다.

테스트 코드는 이렇다던데

흔히 알고 있는 테스트 코드를 작성해야 하는 이유는 무엇일까?

  • 코드에 대한 신뢰성 향상
  • 리팩토링 시 안정성 향상
  • 테스트 코드의 문서로서의 역할
  • 작성한 코드 각각의 역할의 구분

이 외에도 많다. ChatGPT에게 물어보면 아래와 같이 답변해 준다.

  1. 코드의 신뢰성 확보
  2. 유지 보수 용이성
  3. 회귀 테스트 (Regression Test)
  4. 문서화
  5. 디버깅과 오류 추적
  6. 개발 속도 향상
  7. 품질 보증

chatgpt_response

이러한 이유들 때문에 테스트 코드는 서버 개발자에게 필수적인 작업이며, 코드의 품질과 유지 보수성을 높이는 데 큰 기여를 합니다. Spring Boot를 사용할 때는 특히 JUnit과 같은 도구를 사용해 손쉽게 테스트를 작성하고 실행할 수 있으며, 이를 통해 안정적이고 신뢰할 수 있는 웹 서비스를 개발할 수 있습니다.

반면, 그럼에도 테스트 코드를 작성하기 어려워하는, 혹은 작성하지 않는 이유는 무엇이 있을까? 혼자서 나름 생각해 보았다.

  • 테스트 코드 자체에 대해 잘 모르겠다. 생소하다.
  • 작성한 코드에 대한 테스트 코드를 어떻게 작성해야 할지 모르겠다.
  • 시간이 없다.
  • 요구사항이 자주 변경된다.
  • 해 봤는데 오히려 시간만 더 들고 뭐가 좋은지 모르겠다. 의미가 없다.

예를 들어 보자

위의 테스트 코드를 작성해야 하는 이유 중 몇 가지를 예시와 함께 다루어 보고자 한다.

  • 테스트하기 좋은 코드는 역할이 분명한 코드다
  • 테스트 코드는 살아 있는 문서다
  • 테스트 코드는 리팩토링을 위한 발판(scaffold)이다

테스트하기 좋은 코드는 역할이 분명한 코드다

아래와 같은 코드가 있다.

@Service
class AssetsService(
    val assetsRepository: AssetsRepository
) {
    fun overall(userId: UserId): List<Asset> {
        val bankAccounts = assetsRepository.bankAccounts(userId = userId)
        val stocks = assetsRepository.stocks(userId = userId)
        val insurances = assetsRepository.insurances(userId = userId)
        val cars = assetsRepository.cars(userId = userId)

        return (bankAccounts.filter { it.isExpired.not() && it.isDisplay && it.isShared.not() } +
                stocks.filter { it.isExpired.not() && it.isDisplay } +
                insurances.filter { it.isExpired.not() && it.isDisplay && it.isPrize } +
                cars.filter { it.isExpired.not() && it.isDisplay })
            .sortedBy { it.id }
    }
}

‘자산’이라는 도메인이 있고, 자산 업권으로 은행 계좌, 주식, 보험, 자동차가 있다. 자산 공통으로 가지는 필드들이 있고, 각 업권별로 추가로 가지는 필드들이 독립적으로 있다.

sealed interface Asset {
    val id: Long
    val isDisplay: Boolean
    val isExpired: Boolean
}

data class BankAccount(
    override val id: Long,
    override val isDisplay: Boolean,
    override val isExpired: Boolean,
    val isShared: Boolean
) : Asset

data class Stocks(
    override val id: Long,
    override val isDisplay: Boolean,
    override val isExpired: Boolean,
    val isKorean: Boolean
) : Asset

data class Insurance(
    override val id: Long,
    override val isDisplay: Boolean,
    override val isExpired: Boolean,
    val isInsured: Boolean,
    val isPrize: Boolean
) : Asset

data class Car(
    override val id: Long,
    override val isDisplay: Boolean,
    override val isExpired: Boolean,
) : Asset

관련 요구사항은 다음과 같다.

  • 각 자산 업권 별 활성 상태인 자산만을 대상으로 한다.
    • 계좌: 만료되지 않았고, 표시 상태이며, 공유받은 계좌가 아님
    • 주식: 만료되지 않았고, 표시 상태
    • 보험: 만료되지 않았고, 표시 상태이며, 일반 보험임
    • 자동차: 만료되지 않았고, 표시 상태
  • id를 기준으로 정렬한다.

위 코드를 테스트해 본다고 가정하자. 어떻게 해야 할까? 우선 아래 질문에 대해 생각해 보자.

  • 각 업권별로 활성 상태 필터링이 되었는지 테스트할 수 있는가?
  • 활성 상태 필터링과 정렬 여부 필터링을 독립적으로 테스트할 수 있는가?

아무래도 위 코드대로라면 어려울 것이다. overall 메서드가

  • userId에 해당하는 자산을 업권별로 가져 옴
  • 각 업권 별로 활성 상태인 값만 남도록 필터링함
  • id를 기준으로 정렬함

세 가지 역할을 한 번에 가지고 있다. 즉, 한 가지 역할에 대한 테스트를 하려고 해도, 나머지 두 역할이 결합되어 있어서 테스트를 작성하기 쉽지 않다. 이러한 문제가 테스트를 작성하려고 할 때 드러났다.

객체 지향 5원칙 (SOLID) 중 S는 단일 책임 원칙 (Single Responsibility Principle)을 가리킨다. 하나의 객체는 하나의 동작에 대한 책임만을 가져야 한다는 원칙이다. 이를 바탕으로 위 내용을 다시 고려해 보자.

  • overallrepository로부터 자산 데이터를 가져 와서 합친 후 리턴하는 역할만을 가진다.
  • 각 업권의 활성 여부 판단에 대한 책임은 각 업권이 가진다.
  • 자산의 정렬 기준에 대한 책임은 자산이 가진다.

이를 바탕으로 코드를 리팩토링해 보자. 생략한 내용은 이전의 코드와 동일하다.

@Service
class AssetsService(
    val assetsRepository: AssetsRepository = AccountsInMemoryClient()
) {
    /**
     * 사용자의 자산을 가져 오고, 각 카테고리별 active한 상태를 필터링한 후, id를 기준으로 정렬한다.
     */
    fun overall(userId: UserId): List<Asset> {
        val bankAccounts = assetsRepository.bankAccounts(userId = userId)
        val stocks = assetsRepository.stocks(userId = userId)
        val insurances = assetsRepository.insurances(userId = userId)
        val cars = assetsRepository.cars(userId = userId)

        // 데이터를 가져와서 잘 합치는 역할을 한다.
        return (bankAccounts + stocks + insurances + cars)
            .filter { it.isActive() }
            .sortById()
    }
}

// ...

sealed interface Asset {
    // ...

  	// 모든 자산 업권은 활성화 여부를 나타내는 메서드를 가진다.
    fun isActive(): Boolean

    companion object {
        // Asset을 import 하면, 자산 목록을 정렬하는 확장 함수를 사용할 수 있다.
        fun List<Asset>.sortById() = sortedBy { it.id }
    }
}

data class BankAccount(
    // ...
) : Asset {
    override fun isActive() = this.isExpired.not() && this.isDisplay && this.isShared
}

data class Stocks(
    // ...
) : Asset {
    override fun isActive() = this.isExpired.not() && this.isDisplay
}

data class Insurance(
    // ...
) : Asset {
    override fun isActive() = this.isExpired.not() && this.isDisplay && this.isPrize
}

data class Car(
    // ...
) : Asset {
    override fun isActive(): Boolean = this.isExpired.not() && this.isDisplay
}

이렇게 작성한 코드는 테스트하기 쉬울까? 은행 계좌에 대해 활성화 여부를 테스트하는 코드를 아래와 같이 작성할 수 있다.

class BankAccountTest {
    @Test
    fun `노출 상태가 아닌 계좌는 활성 상태가 아니다`() {
        // Given: a bank account that is not displayed
        val account = BankAccount(id = 1, isDisplay = false, isExpired = false, isShared = false)

        // When: we check if the account is active
        val isActive = account.isActive()

        // Then: the account should not be active
        assertThat(isActive).isFalse
    }

    @Test
    fun `만료된 계좌는 활성 상태가 아니다`() {
        // Given: a bank account that is expired
        val account = BankAccount(id = 1, isDisplay = true, isExpired = true, isShared = false)

        // When: we check if the account is active
        val isActive = account.isActive()

        // Then: the account should not be active
        assertThat(isActive).isFalse
    }

    @Test
    fun `공유 계좌는 활성 상태가 아니다`() {
        // Given: a bank account that is shared
        val account = BankAccount(id = 1, isDisplay = true, isExpired = false, isShared = true)

        // When: we check if the account is active
        val isActive = account.isActive()

        // Then: the account should not be active
        assertThat(isActive).isFalse
    }

    @Test
    fun `만료되지 않았으며, 공유 계좌가 아니며, 노출 상태인 계좌는 활성 상태이다`() {
        // Given: a bank account that is not expired, shared, and displayed
        val account = BankAccount(id = 1, isDisplay = true, isExpired = false, isShared = false)

        // When: we check if the account is active
        val isActive = account.isActive()

        // Then: the account should be active
        assertThat(isActive).isTrue
    }
}

BDD (behaviour driven development) 패턴에 따라 given-when-then으로 상황-액션-의도한 결과를 명시하여 작성하였다. 어떤 상황에 은행 계좌 업권이 활성 상태인지 단위 테스트를 진행할 수 있다. 자산 목록 (List<Asset>)의 정렬, Service에서의 Asset 합치기와는 전혀 무관하다.

테스트를 작성하며 동시에 기존 코드를 조금 더 각 역할과 책임을 분리하도록 리팩토링할 수 있었다.

테스트 코드는 살아 있는 문서다

프로젝트는 많은 경우 한 사람이 혼자서 담당하지 않는다. 담당한다고 하더라도, 해당 프로젝트의 시작부터 끝까지 혼자서 담당하지 않는다. 또한, 혼자서 담당하더라도, 시간이 지남에 따라 과거의 비지니스 정책 등을 잊게 된다.

이를 위해 작성한 코드에 대한 문서화가 필요하다. 이는 Swagger나 Spring Rest Docs를 통해 API 사용자에게 제공하는 문서와는 다르다. 여기서 의미하는 문서화는 엔지니어 스스로를 위한 기능 및 정책 명세서다. 프로젝트를 새로 맡게 되거나, 과거의 코드에 대해 확인이 필요할 때, 테스트 코드는 매우 중요한 역할을 한다.

앞에서 작성한 테스트 코드를 다시 살펴보자. 일부 코드는 생략하였다.

class BankAccountTest {
    @Test
    fun `노출 상태가 아닌 계좌는 활성 상태가 아니다`() {
        // ...
    }

    @Test
    fun `만료된 계좌는 활성 상태가 아니다`() {
        // ...
    }

    @Test
    fun `공유 계좌는 활성 상태가 아니다`() {
        // Given: a bank account that is shared
        val account = BankAccount(id = 1, isDisplay = true, isExpired = false, isShared = true)

        // When: we check if the account is active
        val isActive = account.isActive()

        // Then: the account should not be active
        assertThat(isActive).isFalse
    }

    @Test
    fun `만료되지 않았으며, 공유 계좌가 아니며, 노출 상태인 계좌는 활성 상태이다`() {
        // Given: a bank account that is not expired, shared, and displayed
        val account = BankAccount(id = 1, isDisplay = true, isExpired = false, isShared = false)

        // When: we check if the account is active
        val isActive = account.isActive()

        // Then: the account should be active
        assertThat(isActive).isTrue
    }
}

은행 계좌라는 도메인에 대해, 우리가 위 테스트 코드를 보고 알 수 있는 사실은 4가지다.

  • 노출 상태가 아닌 계좌는 활성 상태가 아니다.
  • 만료된 계좌는 활성 상태가 아니다.
  • 공유 계좌는 활성 상태가 아니다.
  • 만료되지 않았으며, 공유 계좌가 아니며, 노출 상태인 계좌는 활성 상태이다.

이는 믿을 수 있는 분명한 문서이다. 정말 그럴까? 테스트 코드는 코드이기 때문에, 실행시켜 확인해 보면 된다.

bank_account_unit_test

우리가 문서화를 어려워하는 이유 중 하나는, 시시각각 변하는 코드의 변경점을 일일히 트래킹하여, 작성해 둔 적재적소에 반영하기가 사실상 불가능하다는 점이다. 하려면 할 수 있지만, 매우 어렵고 시간이 많이 드는 일이다.

테스트 코드는 살이 있는 문서로서, 프로덕션 코드와 운명을 같이 한다. 정책이 변경되어 코드가 수정되면 관련 테스트가 실패하게 되고, 테스트 실행을 통해 이를 즉시 확인할 수 있다 (TDD는 아예 반대 순서로 접근하지만 우선은 패스). 프로덕션 코드의 수정에 따라 테스트 코드의 성공/실패 여부가 명확히 드러나기 때문에, 꾸준히 잘 관리된 테스트 코드들은 매우 강력한 문서이다.

테스트 코드는 리팩토링을 위한 발판(scaffold)이다

건물을 짓거나 보수할 때, 건물 주변에 작업 및 안전을 위해 발판을 설치하곤 한다. 발판(scaffold)은 작업자들이 건설, 보수, 청소 등의 작업을 위해 이동하거나 물자를 옮기는데 사용된다 (출처). 확실한 발판은 꼼꼼하고 안정적으로 작업할 수 있도록 돕는다.

코드를 리팩토링할 때도 마찬가지다. 기존에 작성해 둔 테스트 코드는, 기능의 추가나 수정이 기존 프로덕트에 미치는 영향을 파악하도록 돕는다. 물론 완벽하게 파악할 수는 없지만, 제품의 품질을 유지하는 선에서 이는 매우 중요하다.

예시를 통해 확인해 보자. 앞에서 작성한 Service를 호출하여 응답값으로 만드는 Factory 클래스가 다음과 같이 추가되었다.

object AssetResponseFactory {
    fun toResponseDto(
        asset: Asset
    ): AssetResponse {
        if (asset is BankAccount) require(asset.isShared.not())	// 은행 계좌일 경우 공유 상태가 아니어야 함
        return AssetResponse(
            id = UserId(id = asset.id)
        )
    }
}

data class AssetResponse(
    val id: UserId
)

class AssetResponseFactoryTest {
    @Test
    fun `자산이 은행 계좌일 경우, 공유 상태이면 IllegalArgumentException 발생한다`() {
        // Given: a shared BankAccount
        val sharedBankAccount = BankAccount(id = 1, isDisplay = true, isExpired = false, isShared = true)

        // When / Then: attempting to create a response should throw an IllegalArgumentException
        assertThatThrownBy { AssetResponseFactory.toResponseDto(sharedBankAccount) }
            .isInstanceOf(IllegalArgumentException::class.java)
    }

    @Test
    fun `자산이 은행 계좌일 경우, 공유 상태가 아니면 예외를 던지지 않는다`() {
        // Given: a non-shared BankAccount
        val nonSharedBankAccount = BankAccount(id = 1, isDisplay = true, isExpired = false, isShared = false)

        // When: creating a response DTO
        val result = AssetResponseFactory.toResponseDto(nonSharedBankAccount)

        // Then: no exception is thrown and the response is correctly created
        assertThat(result).isNotNull
        assertThat(result.id.id).isEqualTo(nonSharedBankAccount.id)
    }
}

당연히 테스트를 작성했고, 문제 없이 기능이 통과한다. 그렇게 기능은 배포되었다.

3년 후, 정책에 변경점이 생겼다. 기존에는 은행 계좌는 공유 상태가 아니어야 활성 상태였지만, 이제는 공유 상태여야 활성 상태다. 코드를 아래와 같이 수정했다.

 data class BankAccount(
     // ...
) : Asset {
    override fun isActive() = this.isExpired.not() && this.isDisplay && this.isShared
}

@Test
fun `공유 계좌는 활성 상태이다`() {
    // Given: a bank account that is shared
    val account = BankAccount(id = 1, isDisplay = true, isExpired = false, isShared = true)

    // When: we check if the account is active
    val isActive = account.isActive()

    // Then: the account should not be active
    assertThat(isActive).isTrue
}

@Test
fun `만료되지 않았으며, 공유 계좌이며, 노출 상태인 계좌는 활성 상태이다`() {
    // Given: a bank account that is not expired, shared, and displayed
    val account = BankAccount(id = 1, isDisplay = true, isExpired = false, isShared = true)

    // When: we check if the account is active
    val isActive = account.isActive()

    // Then: the account should be active
    assertThat(isActive).isTrue
}

다행히 수정한 테스트는 잘 통과한다. 앞서 작성한 Factory의 테스트도 잘 통과한다. 그렇다면 이대로 배포하면 될까?

문제가 있었다. 위와 같이 코드를 수정하면, 각 단위 테스트는 문제 없이 통과한다. 그렇지만,

  • AssetsService.overall()BankAccount가 공유 상태인 것들만 리턴한다.
  • AssetResponseFactory.toResponseDto()에는 BankAccount가 공유 상태가 아닌지 확인한다.
  • 따라서 실제 서비스 배포 시 IllegalArgumentException이 발생한다.

다행히 단위 테스트 외에도, 통합 테스트도 잘 작성해 두었다.

class AssetsServiceTest {
    @Test
    fun `assets overall response test`() {
        // given
        val assets = AssetsService().overall(UserId(1L))

        // when
        // then
        val responses = assets.map {
            AssetResponseFactory.toResponseDto(it)
        }
    }
}

위 테스트가 실패하였고, 덕분에 코드 수정으로 인한 영향 범위를 조기에 파악할 수 있었다.

프로그래머 밈 중에는, “코드가 잘 작동하면 손대지 마라 (If the code works, don’t touch it)”는 밈이 있다.

dont_touch_meme

이 밈이 왜 나왔을까? 아주 간단해 보이는 코드 하나 수정했다가, 의도치 않은 수많은 버그가 발생하는 것을 경험한 전 세계의 수많은 엔지니어들의 애환이 담긴 밈이다.

하지만 수정이 없는 코드는 죽은 코드다. 서비스가 잘 돌아간다는 것은, 변경 사항이 계속 생긴다는 것을 의미한다. 따라서 좋으나 싫으나 엔지니어들은 계속 코드를 수정해야 한다. 피할 수 없으면, 잘 해야 하니까. 수정을 잘 하는 방법 중 하나로 테스트 코드를 평소에 꾸준히 작성하는 것이 도움이 된다.

테스트 코드는 프로덕트의 일부다

테스트 코드를 작성하면 뭐가 좋은지 예시를 들어 소개했다. 이번에는 테스트를 작성하지 않는 이유들을 다시 돌아 보자.

  1. 테스트 코드 자체에 대해 잘 모르겠다. 생소하다.
  2. 작성한 코드에 대한 테스트 코드를 어떻게 작성해야 할지 모르겠다.
  3. 시간이 없다.
  4. 요구사항이 자주 변경된다.
  5. 해 봤는데 오히려 시간만 더 들고 뭐가 좋은지 모르겠다. 의미가 없다.

각 항목에 대해 고민해 보자.

1, 2번은 결국 테스트를 더 자주 작성해 보고, 공부하는 수밖에 없다. 테스트 코드가 유용한 도구라는 확신이 들었다면, 열심히 공부해서 실전에서 써먹어 보자.

3번의 경우에는, 테스트 코드에 대해 다시 생각해 볼 필요가 있다. 회사에서 어느 분에게 들었던 말을 빌려 소개해 보면, 우리가 일정 기간 동안 어느 기능을 개발할 때, API를 하나 빼먹거나 하는 식으로 프로덕션 코드가 미완성인 채로 “완성입니다~” 라고 이야기하지는 않는다. 그런데 어째서인지 테스트에 대해서는 “개발하기에도 시간이 부족해서 어쩔 수 없었다”와 같은 말이 굉장히 흔하다. 이것은 잘못되었다. 테스트 코드 역시 프로덕트의 일부로서, 반드시 작성해야 하고 꾸준히 관리되어야 한다. 코드가 아닌 건설, 제조업 등에서 테스트 없이 제품을 출시한다면, 순식간에 뉴스에 나올 것이다. 코드도 마찬가지다. 테스트 코드 없이 작성된 코드는 잘 작동하는 하는 가짜에 지나지 않는다.

4번과 같이 요구조건이 자주 변경된다면, 오히려 테스트 코드가 더욱 필요하다고 말하고 싶다. 서비스를 운영하다 보면 요구사항이 자주 변경되는 것은 당연한 일이다. 서비스가 망해 가거나, 아무도 안 쓰면 코드를 수정할 필요도 없다. 개발 중이든, 개발 완료 후에 수정을 하든, 기존 로직에서 변경 혹은 추가가 발생한다면, 영향도를 파악해야 한다. 정책의 변경 역시 피할 수 없는 일이고, 피할 수 없다면 잘 해야 하니까, 테스트 코드를 통해 잘 해보자.

5번은 더 할 말이 없다. 기존의 경험을 통해 테스트 코드에 대해 부정적으로 생각하게 되었다면, 충분히 존중한다. “포스트맨으로 호출 해보면 되는데?”, “QA에서 확인해 주겠지” 등 다른 방법이 낫지 않냐는 의견도 있다. 다만 이 글에서 소개한 여러 테스트 코드의 역할들이 공감이 된다면, 테스트 코드에 대해 다시 고민해 보시라고 권하고 싶다. 더 많은 자료와 함께 고민해 보면, 생각이 바뀌실지도 모른다.

그럼에도 쓰기 어려운 경우라면

그럼에도 테스트 코드를 쓰기 어려운 환경이 분명 있다. 테스트 코드가 힘을 발휘하는 원리는 결국 ‘지금 시간을 조금 더 투자해서, 나중에 매우 큰 시간을 아낀다’라고 할 수 있다. 이 전제가 깨진다면, 테스트 코드는 효과적으로 사용되기 어렵다. 가령 단기간 기능 개발을 한 후 프로젝트에서 하차하는 SI 프로젝트라면? 기존에 테스트 코드 하나 없이 몇 년간 운영되던 프로젝트를 넘겨 받은 SM 프로젝트라면? SI 프로젝트의 경우, 물론 테스트 코드를 함께 작성해 갈 수 있다. 다만 항상 일정에 쫓기고, 프로젝트가 완료되고 나면 더 이상 스스로 작성한 테스트 코드를 사용할 일이 없으니, 그 효용의 체감이 어렵다. SM 프로젝트의 경우, 기존에 테스트가 없었다면, 테스트를 채워 나가며 프로젝트에 대한 이해도를 높일 수는 있다. 하지만 마찬가지로 다른 운영 업무에 치여 쉽지 않기도 하고, SM 프로젝트 역시 언제 하차하게 될 지 모르니 크게 효과를 보기 어렵다.

결론

테스트 코드 작성이 대한민국 SW 엔지니어들 사이에서 당연한 것이 되기를 바란다. 무엇을 테스트하고, 어떻게 검증할 것인지는 엔지니어 개개인이 각자의 고민과 철학을 드러내기에 가장 좋은 영역이라고 생각한다. 더 많은 분들이 테스트 코드에 대해 고민하고, 이를 바탕으로 서로 교류하길 고대한다.

Leave a comment