본문 바로가기
독서에서 한걸음

Clean Code .Part9

by oncerun 2022. 7. 30.
반응형

다시 마음을 잡았다...

 

이 번장은 단위 테스트에 대해 설명해준다. 테스트를 제대로 해본 경험이 없는 나는 오랜만에 흥분했다.

 

TDD에 진심인 프로그래머들은 테스트에 대해 지식을 공유해줄 때 단위 테스트에 대한 설명부터 시작한다. 

그리고 TDD 법칙 세 가지를 알려준다.

 

첫 번째는 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.

두 번째는 컴파일은 실패하지 않으면서 ( 런타임 오류를 인지하는 듯하다.) 실행이 실패하는 정도로만 단위 테스트를 작성한다. 

세 번째는 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.

 

이 방법은 내가 생각하기에 개발자의 능력에 따라 실패할 수 있는 경우만을 따질 수 있을 것 같고, 테스트 코드의 양이 매우 방대해질 수 있을 것이다. 

 

그럼 여기서 저자는 테스트 코드의 품질을 어떻게 해야하는 가에 대해 질의를 던진다.

보통 프로그래머는 여유를 즐길 수 없을 것이다. 그렇다면 데드라인에 맞춰 개발을 해야 하는데 테스트 코드의 품질까지 생각하며 테스트 코드를 작성하다 보면 분명 시간이 모자랄 것이라고 생각할 것이다. 그래서 지저분하고 냄새가 많이 나지만 실제 코드를 테스트를 하면 그만이라는 생각으로 테스트 코드를 작성할 수 있다.

 

저자는 여기서 지적한다. 냄새나는 테스트 코드는 없으나 마나라는 사실을 인지하라고 말이다.

그 이유 중 하나는 실제 코드의 변경에 따라 테스트 코드도 변경되어야 하는데 만약 테스트 코드의 품질이 낮다면 테스트 코드를 변경하기가 어려워 지기 때문이라고 한다.

 

테스트가 우리에게 제공하는 것은 유연성, 유지보수성, 재사용성

 

테스트 코드는 실제 코드에 대한 버팀목이다. 테스트 코드의 존재만으로 실제 코드의 변경이 두려워지지 않기 때문이다.

 

단위 테스트의 중요성을 알려주었따면 어떻게 테스트 코드를 작성하는 것이 좋은가?라는 궁금증이 생긴다. 

저자는 가독성을 중요시 한다. 가독성을 위해선 명료하고, 단순하고, 풍부한 표현력이 필요하다고 한다.

 

하나의 예시를 보고 해당 예시의 잘못된 점을 파악해보자.

void testGetDataAsXml() throws Exception
{
    crawler.addPage(root, PathParser.parse("TestPageOne"), "test page");
    
    request.setResouce("TestPageOne");
    request.addInput("type", "data");
    Responder responder = new SerializedPageResponder();
    SimpleResponse response = 
      (SimpleResponse) responder.makeResponse(
        new FitNesseContext(root), request);
    String xml = response.getContext();
    
    
    assertEquals("text/xml", response.getContentType()_;
    assertSubString("test page", xml);
    assertSubString("<Test", xml);
}

 

 

 

해당 테스트의 목적은 요청한 데이터가 xml인지, 콘텐츠 안에 "test page", "<Test"라는 문자가 존재하는지 확인하는 테스트 코드이다. 

 

이 코드에서는 PathParser를 호출하는 부분이 존재하고 이는 crawler가 사용하는 인수의 한 부분으로 활용된다.  이것은 테스트와 무관하며 테스트 코드의 의도를 흐리게 한다.

또한 Responder 객체를 생성하는 코드와  response를 수집해 변환하는 코드도 필요가 없다. 

 

이 코드는 읽는 사람을 고려하지 않는다.

첫 번째 줄부터 코드를 이해하기 위해 노력해야한다.

crawler가 어떤 인수를 받아야 하는지?

응답을 어떻게 변환해야 하고 어떤 응답 객체를 사용해야 하는지?  테스트 코드를 이해하기 위해 더 많은 힘을 쏟아야 한다.

 

그럼 어떻게 개선해야 하는가? 

이 코드는 명확히 세 부분으로 나눠진다.

이를 통해 우리는  첫 부분은 테스트 자료 생성, 두 번째는 테스트 자료를 조작하며, 세 번째 부분은 결과를 확인한다.

이때 사용하는 패턴을 BUILD-OPERATE-CHECK 패턴이라 한다.

 

BUILD : 테스트 자료를 만든다.

OPERATE : 테스트 자료를 조작한다.

CHECK : 조작한 결과를 확인한다.

 

보통 우리는 given-when-then을 주석으로 사용한다.

 

이를 개선하기 위해서 우리는 확실하게 필요한 데이터와 함수만으로 구성할 수 있다. 이 코드를 읽는 독자를 위해서 말이다.

앞장에서도 함수의 네이밍과 각 맥락의 중요성을 설명했던 것을 생각해보면 개선된 코드를 보고 좀 더 깨끗하고 이해하기 쉽다고 느낄 것이다.

 

void testGetDataAsXml() throws Exception {
  //given
  makePageWithContent("TestPageOne", "test page");
  
  //when
  submitRequest("TestPageOne", "type:data");
  
  //then
  assertResponseIsXML();
  assertREsponseContains("test page", "<Test");
}

 

개선된 코드를 보는 순간 세세한 코드에 집중하지 않고 해당 테스트가 무엇을 테스트하려는지 직관적으로 이해할 수 있다. 이는 매우 좋은 가독성을 가지고 있는 코드라 할 수 있다.

 

 저자는 테스트 코드를 작성할 때 실제 코드가 돌아가는 환경에 대해 배제한다. 아주 쉽게 예를 드는데 StringBuffer와 string 자료형으로 문자열을 더하는 경우이다. 효율을 위해 StringBuffer가 실제 환경에서 유용하지만 테스트 환경에서는 효율을 그리 강조하지 않고 가독성에 더욱 치중하는 모습을 보여준다.

 

테스트 코드를 작성할 때 하나의 비즈니스 로직을 어떻게 구분하여 부분 테스트를 진행할 것인지 분할하는 작업에도 고민을 해야 한다.  저자는 "테스트 함수마다 한 개념만 테스트"하라는 규칙을 제안한다.

 

역시 한 장으로 테스트를 설명하는 것은 불가능했던 것 같다. 저자도 수박 겉핥기 정도로 훑었다고 했다. 앞으로 코드를 작성할 때 테스트를 먼저 작성해보아야겠다. 

 

반응형

'독서에서 한걸음' 카테고리의 다른 글

Clean Code .Part11  (0) 2022.08.02
Clean Code .Part10  (0) 2022.07.30
Clean Code .Part7  (0) 2022.04.21
Clean Code .Part6  (0) 2022.04.11
Clean Code .Part5  (0) 2022.04.10

댓글