유닛 테스트, 통합 테스트 - yunis teseuteu, tonghab teseuteu

모듈이라 하면, 어렵게 생각하실 거 없습니다. 일종의 프로그램 내의 하나의 기능으로 생각하시면 쉬울 것 같습니다. MS Word라는 프로그램이 있다는 가정하에 Word 내에 있는 기능 중, 폰트 사이즈 선택 기능이 일종에 모듈이라 볼 수 있겠습니다. 다시 말해 모듈을 테스트한다는 의미는 하나의 기능만이 잘 동작하는지를 확인하는 과정입니다. 다른 말로는 모듈 테스트(Module Test)라고도 합니다. 모듈이 개발 완료되는 시점에서 개발자가 명세서 기반으로 정확히 개발을 하였는지를 테스트하게 됩니다. 단위 테스트에서는 화이트박스 테스트, 블랙박스 테스트 기법을 모두 이용하여 진행할 수 있지만, 내부 구조를 들여다볼 수 있는 화이트박스 테스트 기법을 주로 이용하여 테스트를 진행하게 됩니다.

본론으로 들어와서 테스트하려는 모듈을 실행하려면 해당 모듈을 실행할 수 있는 환경 구성이 필요합니다. 다시 말해 테스트할 모듈을 호출하여 실행하는 모듈이 있을 수 있으며, 반대로 테스트하려는 모듈이 다른 모듈을 호출하여 실행하는 경우도 있을 수 있습니다. 즉 테스트하려는 모듈을 실행하려면 앞서 말한 2가지 경우의 수의 모듈이 모두 존재해야 정확한 테스트가 가능합니다. 하지만 상위 모듈이나 하위 모듈이 100% 존재한다는 보장이 없기 때문에 아래와 같은 가상의 모듈을 만들어 사용해야 합니다.

테스트 드라이버 (Test Driver): 테스트 대상이 되는 모듈을 호출하여 대상 모듈의 실행 결과는 받는 가상 모듈

테스트 스텁 (Test Stub): 호출하는 상황의 대상 모듈을 테스트하기 위한 가상 모듈

다시 정리하면 테스트 드라이버는 테스트 대상이 되는 모듈의, 상위 가상 모듈로 생각하시면 쉽게 이해가 가실 거 같고, 반대로 테스트 스텁은 테스트 대상이 되는 모듈의, 하위 가상 모듈로 생각하시면 되겠습니다.

통합 테스트 (Integration Test)

모듈을 통합하는 과정에서 모듈 간 호환성의 문제를 찾아내기 위해 수행되는 테스트입니다.

다시 말에 모듈 간의 인터페이스가 올바르게 작동하는지를 테스트하게 됩니다. 예를 들어 다시 MS Word라는 프로그램 내에 ‘폰트 사이즈’ 모듈과 ‘폰트 스타일’ 모듈을 통합한다는 가정을 해봅니다. 이때 만약 ‘폰트 스타일’ 모듈 구현 당시 폰트 사이즈가 변화할 수 있다는 것을 인지하지 않고 개발을 하게 된다면 폰트 사이즈 모듈과 통합되는 과정에서 에러가 발생할 수 있습니다. 이를테면 기본 폰트 사이즈 9pt에서만 폰트 스타일이 먹히는 문제? 일수 있겠습니다.

다시 정리하면 각각의 통합한 모듈이 올바르게 연계되어서 동작하는지를 테스트하게 됩니다.

모듈 테스트 시 통합 기법으로는 크게 빅뱅 통합과 점진적 통합 기법이 있습니다.

빅뱅 통합: 전체 모듈을 모두 통합한 이후 통합 테스트를 수행하는 방식 이때 오류가 발생했을 시 오류가 발생한 모듈을 찾기가 쉽지 않다는 단점이 있습니다.

점진적 통합: 한 번에 모듈을 통합하지 않고 점진적으로 통합하는 방식으로 하향식 통합, 상향식 통합 기법이 대표적입니다. 점진적 통합 기법을 이용하게 되면 설계상의 오류를 빨리 발견할 수 있다는 장점이 있습니다.

단위 테스트는 소스코드의 특정 모듈(프로그램 내 하나의 기능을 부르는 말)이 의도된 대로 정확히 작동하는지 검증하는 절차이며, 함수, 메서드, 개별 코드 같은 작은 단위에 대해 테스트 케이스(Test Case)로 분리하고 테스트 코드를 작성하여 테스트하는 것을 말한다.

외부 API와의 연동이 필수라든가 DB 데이터 접근 등 외부 리소스를 직접 사용해야 하는 테스트라면 단위 테스트가 아니다. 단위 테스트에서 외부와의 연동이 필요하다면 테스트 대역(Test Double)을 사용하면 된다.

테스트 대역(Test Double)??
외부 의존 구성요소를 사용할 수 없거나 직접 사용하고 싶지 않을 때, 테스트 대상 코드와 대신해서 상호작용하는 객체를 말한다.

  • 언어별로 단위 테스트를 위한 프레임워크가 존재하며 보통 이름을 xUnit이라 칭한다.
    • ex) JUnit(JAVA), CppUnit(C++), NUnit(.NET 프레임워크), unittest(Python)

단위 테스트 수행 이유

  1. 기능 테스트 수행 시간 단축
    • 기능 테스트는 비용이 많이 든다. 일반적으로 애플리케이션을 열고 사용자(또는 다른 사용자)가 예상되는 동작의 유효성을 검사하기 위해 따라야 하는 일련의 단계 수행이 포함된다. 이러한 단계는 항상 테스터에게 알려진 것은 아니며, 이는 테스트를 수행하기 위해 해당 영역에 더 많은 지식을 갖추어야 함을 의미한다. 테스트 자체는 사소한 변경인 경우 몇 초가 걸리거나 큰 변경의 경우에는 몇 분 정도 걸릴 수 있다. 마지막으로, 이 프로세스는 시스템에서 수행하는 모든 변경 사항에 대해 반복되어야 한다.
    • 반면에 단위 테스트는 밀리초 단위의 작은 시간이 소요되고 단추를 눌러 실행할 수 있으며 시스템 전체에 대한 정보가 반드시 필요하지는 않는다. 또한 테스트 통과 또는 실패 여부는 개인이 아닌 test runner의 몫이다.
  2. 회귀에 대한 보호
    • 테스터는 새 기능을 테스트할 뿐만 아니라 이전에 구현된 기능이 여전히 예상대로 작동하는지 확인하기 위해 이전에 존재했던 기능도 테스트하는 것이 일반적이다.
    • 단위 테스트를 사용하면 모든 빌드 후에 또는 코드 줄을 변경한 후에도 전체 테스트 도구 모음을 다시 실행할 수 있다. 즉, 새 코드가 기존 기능을 중단시키지 않는다는 신뢰를 준다.
  3. 실행 가능한 설명서
    • 특정 메서드가 무엇을 하는지 또는 특정 입력이 지정된 동작이 어떻게 수행되는지 항상 명확하지는 않을 수 있다. 빈 문자열을 전달하면 이 메서드는 어떻게 작동하는가를 자문해 볼 수 있다.
    • 이름이 잘 지정된 단위 테스트의 도구 모음이 있는 경우 각 테스트는 지정된 입력에 대해 예상되는 출력을 명확하게 설명할 수 있어야 한다. 또한 실제로 작동하는지 확인할 수 있어야 한다.
  4. 낮은 결합도 코드
    • 코드가 밀접하게 결합되면 단위 테스트하기가 어려울 수 있다. 따라서 작성 중인 코드에 대한 단위 테스트를 만들지 않으면 결합이 덜 분명해질 수 있다.
    • 코드 테스트를 작성하면 결합은 자연스럽게 분리된다. 그렇지 않으면 테스트하기가 더 어려워지기 때문.

장점

  1. 테스트 시간 단축으로 문제점 발견 가능성이 높아지고 안정성이 향상
    • 단위 테스트의 목적은 프로그램의 각 부분을 고립시켜서 각각의 부분이 정확하게 동작하는지 확인하는 것이다. 이를 통해 문제 발생 시 정확하게 어느 부분이 잘못되었는지를 재빨리 확인할 수 있게 해 준다. 따라서 프로그램의 안정성이 높아진다. 단위 테스트는 개발 시간을 증가시키는 것처럼 보이지만 개발 기간 중 대부분을 차지하는 디버깅 시간을 단축시킴으로써 여유로운 프로그래밍을 가능케 한다.
  2. 이전 기능도 함께 테스트하기 때문에 더 쉬워진 코드 변경
    • 프로그래머는 단위 테스트를 믿고 리팩토링을 할 수 있다. 리팩토링 후에도 해당 모듈이 의도대로 작동하고 있음을 단위 테스트를 통해서 확신할 수 있다. 이를 회귀 테스트(Regression Testing)라 한다. 어떻게 코드를 고치더라도 문제점을 금방 파악할 수 있고 수정된 코드가 정확하게 동작하는지 쉽게 알 수 있게 되므로 프로그래머들은 더욱더 의욕적으로 코드를 변경할 수 있게 된다.
  3. 테스트 간 결합도가 낮으므로 간단해진 통합
    • 단위 테스트는 단위 자체의 불확실성을 제거해주므로 상향식(Bottom-up) 테스트 방식에서 유용하다. 먼저 프로그램의 각 부분을 검증하고 그 부분들은 합쳐서 다시 검증하는 통합 테스트에서 더욱더 빛을 발한다.

좋은 단위 테스트

  • Fast
    • 완성도 높은 프로젝트에서 수천 개의 단위 테스트를 수행하는 것은 드문 일이 아니다. 단위 테스트는 실행하는 데 시간이 거의 걸리지 않는다. (밀리초단위..)
  • Isolated
    • 독립형 단위 테스트는 독립적으로 실행될 수 있으며, 파일 시스템 또는 데이터베이스와 같은 외부 요인에 종속되지 않는다.
  • 반복 가능
    • 단위 테스트를 실행하는 것은 해당 결과와 일치해야 한다. 즉, 실행 사이에 아무 것도 변경하지 않으면 항상 동일한 결과를 반환한다.
  • 자체 검사
    • 테스트는 사람의 개입 없이 통과했는지 여부를 자동으로 검색할 수 있어야 한다.
  • Timely
    • 단위 테스트는 테스트 중인 코드에 비해 작성하는 데 불균형적으로 긴 시간이 걸리지 않아야 한다. 코드를 작성하는 데 비해 많은 시간이 걸리는 코드를 테스트하는 경우 더 많은 테스트가 가능한 디자인을 고려할 필요가 있다.

통합 테스트(Integration Test)

통합 테스트는 모듈을 통합하는 과정에서 모듈 간 인터페이스가 올바르게 작동하는지를 테스트하는 것을 말한다.

  • 일반적인 웹 어플리케이션은 프레임워크, 라이브러리, 데이터베이스, 구현한 코드가 주요 통합 테스트 대상이다.
    • 예를 들어 회원 가입 코드에 대한 통합 테스트를 수행하면 스프링 프레임워크나 마이바티스 설정이 올바른지, SQL 쿼리문이 맞는지, DB 트랜잭션이 잘 동작하는지 등을 검증할 수 있다.
  • 여러 모듈간 통합을 거치기 때문에 단위 테스트에 비해 테스트 실행 속도가 느린 편이다.

트랜잭션(Transactcion)?
데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위를 뜻한다.

기능 테스트(Functional Test)

기능 테스트는 사용자와 어플리케이션의 상호작용이 원활하게 이루어지는지 테스트하는 것을 말한다.
그래서 이 테스트를 수행하려면 시스템을 구동하고 사용하는데 필요한 모든 구성요소가 필요하다.

따라서 E2E(End to end) 테스트로도 볼 수 있다. 예를 들면 브라우저에 웹 서버를 구동하거나 모바일에 앱을 구동하고 화면의 흐름에 따라 알맞은 상호 작용을 해야 하는 것이다.