Unit Testing (0) - 단위 테스트란 무엇인가

강지혁·2022년 8월 14일
0

Unit Testing

목록 보기
1/3
post-thumbnail

Unit Testing의 1~3장을 읽고 정리한 글입니다.

테스트를 작성하는 일은 아주 아주 중요하다.
사실 근거에 대해 명확히 이해하지 않더라도, 느낌 만으로도 테스트는 중요하다.
(핫픽스를 몇 번 내보면 느낄 수 있다)

다만 "좋은 테스트"를 작성하기 위해서는, 왜 테스트를 해야 하는지 고민해보는 과정이 필요하다고 생각한다. 필자의 생각을 한 번 정리해보았다.

테스트는 왜 중요할까?

#1. 실수를 줄일 수 있다.

  • 직관적이지만 실수를 줄이기 위해 중요하다.
    테스트 코드를 작성하는 습관은 실수를 줄이는 데 명백한 도움이 된다.

#2 . 유지 보수에 도움이 된다.

  • 테스트는 코드의 문서화를 돕는다.

    • 테스트가 존재하면, 상황에 대한 기대 행동을 만족하지 않는 코드는 병합되지 않는다. 이렇게 잘 짜여진 테스트 코드들이 쌓이면, 나중에는 테스트코드들이 그 자체로 기획 명세서의 역할을 대신한다. 이러한 개발 환경에서는 후임자가 코드를 f/up 하는 데 드는 비용도 많이 줄어든다.
  • 테스트 코드를 통해 업무를 더 효율적으로 처리할 수도 있다.

    • 테스트를 작성해두면, 기대 행동을 잘 구현하는 것에만 집중하게 된다.
      명세에 비해 너무 많은 코드를 작성하지도, 불충분한 코드를 작성하지도 않게 된다.
      이는 사소한 듯 보이지만, 개발 주기가 년 단위를 넘어가고, 항상 유지 보수가 필요한 엔터프라이즈 애플리케이션 개발에서는 크리티컬한 포인트가 된다.
  • Testable 한 코드를 만드는 것은 코드의 품질을 향상시킨다.

    • 앞으로의 게시글에서 정리되겠지만, 테스트를 작성하기 용이한 코드이려면 : 객체의 구현 세부 사항이 적절히 감추어져 있고, 객체 간 협력 방식이 알아보기 쉬워야 한다. (고 생각한다.)
    • 이는 OOP와도 밀접한 관련이 있다. (대체로 객체 지향적인 코드일수록 테스트 하기도 용이하다 할 수 있을 것 같다.)

💯 Successful Test Suite?

테스트를 왜 짜야 할 지 고민해보았다.
그렇다면 좋은 테스트란 뭘까?
책 Unit Testing의 저자께서는 아래와 같은 조건들을 제시한다.

  • 개발 주기에 통합되어 있는 테스트
  • 코드베이스에서 가장 중요한 부분만을 대상으로 하는 테스트
    • 일반적으로는 비즈니스 로직
    • 도메인 모델은 항상 다른 애플리케이션 문제와 분리하는 것이 좋다.
      • e.g ) 데이터베이스 커넥션 관리 등등..
  • 최소한의 유지비로 최대의 가치를 끌어내는 테스트
    • 가치가 유지비를 상회하는 테스트만 유지하는 것이 중요하다.
    • 이를 위해서는
      • 가치있는 테스트 식별하기 (좋은 테스트와 그렇지 않은 테스트를 구별하기)
      • 가치있는 테스트 작성하기 (코드 설계)
    • 두 가지 역량이 요구된다.

💡 SW 개발자는, (심지어 훌륭한 개발자라도) 그 결정이 내려진 이유를 정확히 설명할 수 없다면, 설계 결정에 대해 완전히 인정받지 못한다.

(갑자기 정곡을 찌르는 멋진 문장이 책에 적혀 있길래 인용)


앞선 좋은 테스트의 기준을 다시 정리해보면, 결국 테스트가 필요한 코드와 그렇지 않은 코드를 분리하고, 작성할 수 있어야 한다.

또 그럴 수 있으려면, 코드를 적당한 단위로 격리해서 테스트를 작성해야 한다.
따라서 좋은 테스트에 대한 논의 뒤에 단위 테스트에 대한 논의가 뒤따른다.

단위 테스트란 무엇인가?

책에서의 단위 테스트 정의는 아래와 같다.

  • 작은 코드 조각을 검증하고,
  • 빠르게 수행하고,
  • 격리된 방식으로 처리한다.
    • 격리를 어떻게 할 수 있는가? 에 대해서는 의견이 갈린다.
    • 그 유명한 고전파 vs 런던파

고전파 VS 런던파

코드를 어디까지 분리해야 하는가? 에 대한 생각이 다르다.
런던파는 고전파보다 테스트 단위가 좁다.
무조건 한 클래스 (객체)에 대해 테스트를 하고, 모든 의존성은 테스트 대역을 써서 처리하는 것을 지향한다.

격리 주체단위의 크기테스트 더블 사용 대상
런던파단위단일 클래스불변 의존성 외 모든 의존성
고전파단위 테스트단일 클래스 또는 클래스 세트공유 의존성
  • Classical school (고전파)

    // classical school test
        import org.junit.jupiter.api.Assertions.*
        import org.junit.jupiter.api.Test
        
        internal class MarketTest {
            @Test
            fun `Purchase Succeeds when enough Inventory`() {
                // given
                val store = Store()
                val customer = Customer()
                store.addInventory(Product.Shampoo, 10)
        
                // when
                val result = customer.purchase(store, Product.Shampoo, 5)
        
                // then
                assertTrue(result)
                assertEquals(5, store.getCount(Product.Shampoo))
            }
        
            @Test
            fun `Purchase Fails when not enough Inventory`() {
                // given
                val store = Store()
                val customer = Customer()
                store.addInventory(Product.Shampoo, 10)
        
                // when
                val result = customer.purchase(store, Product.Shampoo, 15)
        
                // then
                assertFalse(result)
                assertEquals(10, store.getCount(Product.Shampoo))
            }
        }
       }
       
  • London school (런던파)

       @Test
       fun `London School Mocks Collaborators`() {
    		// given
    		val customer = Customer()
    		val mockStore = mock<IStore>()
    
    		// when
       		`when`(mockStore.hasEnoughInventory(any(), any())).thenReturn(false)
       		val result = customer.purchase(mockStore, Product.Shampoo, 5)
    
    		 // then
    		assertFalse(result)
    		verify(mockStore, never()).minus(any(), any())
       }
           
  • 작가는 고전파의 관점에서 단위 테스트를 바라본다.

    • 단위 테스트: 단일 동작 단위를 검증하고, 빠르게 수행하고, 다른 테스트와 별도로 처리하는 것

작가의 견해에는 나름의 이유가 있다. 다음 4, 5장에서 이를 다룬다.

0개의 댓글