[개발 도서] Clean Code :: 9장 - 단위 테스트 / 도메인에 특화된 언어 부분 이해 x

Jihyoung·2021년 7월 26일
0

Clean Code

목록 보기
4/11
post-thumbnail
post-custom-banner

테스트 코드를 항상 깨끗하게 유지하자. 이를 위해서는 단위테스트 필수

📕 TDD 법칙 세 가지

?왜 실제 코드를 짜기 전에 짜냐,, 왜? 실행이 실패하게끔 단위테스트를 짜지? 왜 실패?? 그냥 단순히 오류 수정을 위해서? 왜 나 모르겠지
  1. 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
    : 실제 코드를 짜기 전 단위 테스트부터 짠다.
  2. 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
  3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

이렇게 테스트 코드를 작성하게 되면 양이 실제 코드만큼 방대해지기 때문에 관리가 중요하다.

* TDD(Test Driven Development) : '테스트 주도 개발'이라고 한다. 반복 테스트를 이용한 소프트웨어 방법론으로, 작은 단위의 테스트 케이스를 작성하고 이를 통과하는 코드를 추가하는 단계를 반복하여 구현한다.

📗 깨끗한 테스트 코드 유지하기

실제의 코드가 변하면 테스트 코드도 함께 변화해야 하기 때문에 올바른 테스트 코드를 짜는 것은 중요하다.

📍 테스트는 유연성, 유지보수성, 재사용성 을 제공한다.

유연성, 유지보수성, 재사용성을 제공하는 부분이 바로 단위 테스트다. 테스트 케이스가 존재한다면 변경을 하기 쉬워진다. 따라서 실제 코드를 점검할 수 있는 자동화된 단위 테스트 슈트는 기존 설계를 더 안전하게 보존 가능하다.

* 단위 테스트 : 작성했던 코드의 가장 작은 단위인 함수를 테스트하는 메소드
* 테스트 커버리지 : 테스트 대상의 전체 범위에서 테스트를 수행한 범위 (테스트 커버리지가 100%가 아닐 경우 추가 테스트 케이스를 설계)
* 테스트 슈트(Test Suites) : 테스트 대상 컴포넌트나 모듈, 시스템에 사용되는 테스트 케이스의 집합

##### 테스트 케이스 (Test Case) : 해당 테스트 유닛에 들어가는 Input. 또는 해당 테스트의 수행 n번. (함수 호출)
##### 테스트 유닛 (Test Unit) : 테스트를 하는 단위 기능. (함수)
##### 테스트 슈트 (Test Suites) : 테스트 유닛의 집합. (함수 묶음)


📙 깨끗한 테스트 코드

깨끗한 테스트 코드를 만들기 위해서는 가독성이 중요하다. 이를 위해 명료성, 단순성, 풍부한 표현력이 필요하다. 테스트 코드는 최소의 표현으로 많은 것을 나타내야 한다.

'BUILD-OPERATE-CHECK 패턴' 은 가독성을 늘리기에 좋다.

  • BUILD : 테스트 자료를 만든다.
  • OPERATE : 테스트 자료를 조작한다.
  • CHECK : 조작한 결과를 확인한다.

💻 예시


📍 도메인에 특화된 언어 -> 이해 못함..

흔히 쓰는 시스템 조작 API를 사용하는 대신 API 위에 함수와 유틸리티를 구현한 후 그 함수와 유틸리티를 사용하므로 테스트 코드를 짜기도 읽기도 쉬워진다. 이렇게 구현한 유틸리티는 테스트 코드에서 사용하는 특수 API가 된다. 즉, 테스트를 구현하는 당사자와 나중에 테스트를 읽어볼 독자를 도와주는 테스트 언어이다.

* 도메인에 특화된 언어 (DSL) : 관련 특정 분야에 최적화된 프로그래밍 언어, 해당 분야 또는 도메인의 개념과 규칙을 사용

ex) SQL! DB의 데이터를 참조하기 위해 날리는 query는 말 그대로 "DB에 데이터를 참조하기 위한 목적"으로만 사용되며 SQL로 웹 애플리케이션 서버를 만드는 것은 절대 불가능 하다. 반면 JAVA는 SQL을 만들어 낼 수도 있고 (사실상 SQL은 특정한 문법을 가진 문자열이기 때문이다) 웹 애플리케이션 서버를 만들 수도 있고, 그 외 원하는 모든 것을 만들어 낼 수 있다. 단지 다른 분야에선 다른 언어가 더 좋을 뿐이지 가능은 할 것이다.

??DSL에는 java가 포함되나,,? 도메인 특화 언어는 일반적으로 Java, C, Ruby 등의 범용 언어보다 덜 복잡합니다 이런말도 있고

이런말도 있는데,,어떤게 맞는건지 잘 모르겠다..!

📍 이중 표준

테스트 API코드에 적용하는 표준은 실제 코드에 적용하는 표준과 확실히 다르다.

단순하고, 간결하고, 표현력이 풍부해야 하지만, 실제 코드만큼 효율적일 필요는 없다. 실제 환경이 아니라 테스트 환경에서 돌아가는 코드이기 때문인데, 실제 환경과 테스트 환경은 요구사항이 다르다.

💻 Bad Code

우리가 알고싶은 사실은 '급강하 여부' 이다.
그러나 아래의 코드에는 세세한 내용이 너무 많다.

@Test
public void turnOnLoTempAlarmAtThreashold() throws Exception {
  hw.setTemp(WAY_TOO_COLD); 
  controller.tic(); 
  assertTrue(hw.heaterState());   
  assertTrue(hw.blowerState()); 
  assertFalse(hw.coolerState()); 
  assertFalse(hw.hiTempAlarm());       
  assertTrue(hw.loTempAlarm());
}
💻 Refactoring Code

위의 코드를 리팩터링을 거쳐 이해하기 쉬운 형태로 변환했다.

@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
  wayTooCold();
  assertEquals("HBchL", hw.getState()); 
}
💻 More Selection

아래와 같이 구현하게되면 코드 이해도가 올라간다.

@Test
public void turnOnCoolerAndBlowerIfTooHot() throws Exception {
  tooHot();
  assertEquals("hBChl", hw.getState()); 
}
@Test
public void turnOnHeaterAndBlowerIfTooCold() throws Exception {
  tooCold();
  assertEquals("HBchl", hw.getState()); 
}
@Test
public void turnOnHiTempAlarmAtThreshold() throws Exception {
  wayTooHot();
  assertEquals("hBCHl", hw.getState()); 
}
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
  wayTooCold();
  assertEquals("HBchL", hw.getState()); 
}

실제 환경에서는 절대로 안 되지만 테스트 환경에서는 전혀 문제없는 방식이 있다. 대개 메모리나 CPU 효율과 관련 있는 경우다. 코드의 깨끗함과는 철저히 무관하다.


📍 String, StringBuffer, StringBuilder

  • String
    • 불변성을 가지고 있기 때문에 메모리 사용에 있어서 효율적이다.
    • 문자열을 수정하는 시점에 새로운 String 인스턴스가 생성되고, 처음 선언값은 Garbage로 남아있다가 Garbage Collection에 의해 사라지게 된다.
    • 변하지 않는 문자열을 자주 읽어들이는 경우 유용하다.
    • 문자열 추가,수정,삭제 등의 연산이 빈번하게 발생하는 알고리즘에 String 클래스를 사용하면 힙 메모리(Heap)에 많은 임시 가비지(Garbage)가 생성되어 힙메모리가 부족할 수 있다
  • StringBuffer VS StringBuilder
    • 둘의 가장 큰 차이점은 동기화의 유무이다.
    • StringBuffer는 동기화 키워드를 지원하여 멀티쓰레드 환경에서 안전하다.
    • StringBuilder는 동기화를 지원하지 않기때문에 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만 동기화를 고려하지 않는 만큼 단일쓰레드에서의 성능은 StringBuffer 보다 뛰어나다.

📘 테스트 당 assert 하나

  • assert문이 하나인 함수는 결론이 하나이기 때문에 코드를 빠르고 쉽게 이해 가능하다.

  • Give-When-Then 패턴 이용하여 테스트를 2개로 나눠 각각 assert문을 수행할 경우

    • public void testGetPageHierarchyAsXml() throws Exception { 
        givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
        
        whenRequestIsIssued("root", "type:pages");
      
        thenResponseShouldBeXML(); 
      }
      public void testGetPageHierarchyHasRightTags() throws Exception { 
        givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
      
        whenRequestIsIssued("root", "type:pages");
      
        thenResponseShouldContain(
          "<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
        ); 
      }
    • 장점 : 코드 가독성 증가 / 단점 : 중복 코드 증가

  • Template method 패턴 사용할 경우 중복 제거가 가능하다

    • given/when 부분을 부모 클래스에 두고, then 부분을 자식 클래스에 둔다.
    • 독자적인 클래스를 만든다.
  • 단, assert 문의 개수는 최소화 할수록 좋다.

* assert : 개발/테스트 단계에서 파라미터가 제대로 넘어왔는지 계산이 제대로 됬는지 혹은 특정 메소드가 작동하는 한계상황을 정하는 용도로 사용

📍 Test 당 개념 하나

개념이 많이 섞이게되면 어떤 테스트를 진행하는지 파악하는데 있어서 이해하기 힘들뿐만 아니라 유지보수도 하기 힘들다.

  • 개념 당 assert 문 수를 최소로 줄여라
  • 테스트 함수 하나는 개념 하나만 테스트하라

📒 F.I.R.S.T

F.I.R.S.T설명
빠르게(Fast)테스트는 빨라야 한다.
테스트는 자주 돌려 초반에 문제를 찾아낸다.
독립적으로(Independent)각 테스트는 의존하면 안 된다.
각 테스트는 독립적이며, 어떤 순서로 실행해도 괜찮아야 한다.
반복가능하게(Repeatabel)어떤 환경에서도 반복 가능해야 한다.
자가검증하는(Self-Validating)테스트는 bool 값으로 결과를 내야 한다.
적시에(Timely)테스트는 적시에 작성해야 한다.
단위 테스트는 테스트하려는 실제 코드를 구현하기 직전에 구현한다.

?여기서 적시에 부ㅡ분이 내가 의문을 가졌던 부분과 이어지는것인가,,?

🧩 더 공부할 부분


📚 Reference

profile
로그를 생활화
post-custom-banner

0개의 댓글