3 분 소요

단위 테스트란 하나의 기능 단위를 테스트하는 것을 말합니다. 혹은 단위 기능들을 복합하여 다시 하나의 단위 테스트를 만들 수도 있습니다.

좋은 단위 테스트는 다음의 조건을 갖춰야 합니다.

  1. 독립성

    다른 테스트에 의존성이 없어야 합니다. 특정 테스트를 단독 실행해도 통과할 수 있도록 구성하세요. 테스트 복잡성 뿐만 아니라 기능 코드의 복잡성을 낮추고 안정성을 향상시킵니다.

  2. 반복성

    여러번 반복해서 실행해도 같은 결과가 나와야 합니다.

  3. 중복 제거

    중복된 테스트를 하지 마세요. 하기에 홀수끼리 더하면 짝수가 되는걸 확인할 필요가 있을까요? 꼭 필요한 테스트만 작성하시고, 불필요한 테스트는 없어야 합니다. 코드량이 많아지면, 유지보수 부하만 늘어나니까요.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
     TEST(TestPlus, Test1) {
         // 1 + 2 == 3을 테스트합니다. 좋습니다. 
         EXPECT_TRUE(Plus::Run(1, 2) == 3); 
    
         // 2 + 1 == 3을 테스트합니다. 인수를 바꿔도 정상동작인지 확인하니, 좋습니다. 
         EXPECT_TRUE(Plus::Run(2, 1) == 3); 
    
         // 무조건 3을 리턴할 수 있으니, 다른 결과값을 확인합니다. 좋습니다. 
         EXPECT_TRUE(Plus::Run(2, 4) == 6)    
    
         // 홀수끼리 더하면 짝수가 나오는지 확인합니다. 글쎄요. 필요할까요? 
         EXPECT_TRUE(Plus::Run(1, 3) == 4)
     }    
    

또한

  1. 좋은 이름을 사용해서 테스트명으로 무엇을 하는지 설명할 수 있어야 합니다.

    테스트할 기능명 + 시나리오 + 예상동작으로 이름을 짓는게 좋습니다.

  2. 테스트 코드 자체가 기능 사용 설명 예제가 될 수 있어야 합니다.

    테스트 코드는 기능의 사용법을 알려줄 수 있어야 합니다.

  3. 테스트 코드 자체를 최대한 간결하게 만들어야 합니다.

    테스트를 작성하다가 지치면 안됩니다. 이건 테스트 하려는 기능의 사용법이 복잡하고 악취가 난다는 뜻입니다. 테스트를 복잡하게 만들지 말고, 기능 구현을 리팩토링 하세요. Facade나 팩토리나, 유틸리티 작성을 권장합니다. 테스트 코드는 최대한 짧아야 하고, 작성 시간도 짧아야 합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
         TEST(..., ...) {
             // 실행 동작 테스트를 위해 a 개체를 만든뒤 복잡하게 Method를 호출하고 데이터를 설정합니다.
             MyClassB a; 
             a.Method();
             a.SetData1(...);
             a.SetData2(...);
    
             // 실행 동작 테스트를 위해 a 개체를 만든뒤 복잡하게 Method를 호출하고 데이터를 설정합니다.
             MyClassB b:
             b.Method();
             b.SetData1(...);
             b.SetData2(...);
    
             // 실행 동작 테스트를 위해 c 개체를 만들고 a, b 개체를 전달하고 뭔가 열심히 한뒤 상태 검사를 합니다.
             MyClassC c(a, b);
             c.DoSomthing();
             EXPECT_TRUE(c.Check());
         }
    
    

    보다는 하기가 낫습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     TEST(..., ...) {
         // A, B 클래스로부터 c를 생성합니다.
         MyClassC c = MyClass::Create(
             MyClassA::Create(...),
             MyClassB::Create(...)
         );
         c.DoSomthing();
         EXPECT_TRUE(c.Check());
     }
    
  4. 빠르게 테스트 되어야 합니다.

    자동화된 단위 테스트가 느려지면, 점점 테스트를 안하게 될 수 있습니다. 리팩토링의 신호입니다. 속도 개선을 시작하세요. 혹은 프로젝트를 모듈 단위로 쪼개세요.(관심사의 분리 원칙 참고) 안그러면 금새 백만라인 되버립니다.

  5. 가까스로 테스트를 통과해야 합니다. 테스트와 상관없는 불필요한 코드가 테스트에 작성되면 안됩니다. 또한 불필요한 정보가 노출 되어서도 안됩니다. 기능 예제를 오해할 수 있고 테스트의 의도가 불분명 해집니다.

    1
    2
    
     add(0); // add 가 잘 되는지 확인
     add(76); // add 가 잘 되는지 확인. 근데 왜 하필 76이지?
    
  6. 특정 인프라 시스템 종속성이 없어야 합니다.

    예를 들어 메일 전송이 필요한 시스템에서 매번 실제로 메일을 전송해야 한다면, 테스트 시간이 오래 걸립니다. 더군다나 메일 서버가 고객 시스템에 있는 것이라면, 실제로 방문해서 테스트를 할 수도 없는 노릇입니다. 이럴때는 인프라 접근에 대한 모의 개체(Test Double) 를 만들어 테스트 해야 합니다.

모의 개체(Test Double) 란 용어가 나왔는데요, 실제 기능구현에는 필요하지 않지만, 테스트를 위해 임시로 사용하는 개체를 말합니다.

주로 하기의 경우에 필요합니다.

  1. 테스트 속도 개선이 필요한 경우
  2. 아직 구현되지 않은 것을 활용하는 개체를 테스트 하려는 경우
  3. 데이터가 매번 변경되어 테스트 반복성을 해치는 경우
  4. 인프라 종속성이 있는 경우
  5. 코드 사용 구조가 복잡하여, 일부 코드만 격리하여 테스트하고 싶은 경우(원하는 코드 빼고는 다 모의 개체로 만들어 테스트 수행)

모의 개체의 종류는 다음과 같습니다.

  1. Dummy : 사용할 개체들이 구현되지 않았을때, 임시 인수 전달 용도로 사용

  2. Fake : 테스트용으로 작성된 원래 개체의 단순화된 버전. 데이터베이스, 웹서버등 인프라 종속성이 많은 경우, 실제처럼 동작하는 것처럼 속임.

  3. Stub : Fake와 유사함. 테스트를 위한 미리 정의된 데이터 값을 가지고 있다가 테스트 호출시에 응답함.

  4. Spy : Stub의 역할을 하면서 추가 정보 기록(메서드가 잘 호출됐는지, 몇번이나 호출됐는지)

  5. Mock : 함수 호출이 잘 되는지 확인하는 용도(행위 검증)로 사용

실제 테스트를 하다보니, 프로젝트 초반에는 현재 작성중인 코드를 빨리 테스트하고 싶은데, 연관된 클래스가 좀더 많이 작성되어야 할 때가 많습니다. 더군다나 협력 개발할때는 동료가 개발해 주시기를 기다려야 하죠. 이런 경우 Dummy, Fake, Stub 개체들이 유용하게 사용되니 많이들 활용하시기 바랍니다.

잊지마세요!!!

  1. 독립성, 반복성, 중복 제거
  2. 테스트는 예쁜 이름을 짓고, 설명서가 될 수 있도록, 간결하게, 그리고 빠르게 실행되는 Clean Code 이어야 합니다.
  3. 테스트를 위해 기다리지 마세요. 고객 시스템에 연결하지도 마시고요. Dummy, Fake, Stub이 있습니다.

댓글남기기