소프트웨어 테스팅

어떤 프로그램을 만들면 항상 그 프로그램이 정상적으로 동작하는지 확인하기 위해 테스트를 진행합니다. 매번 중구난방으로 테스트를 해왔는데, 테스트도 체계화된 이론이 있었습니다.

소프트웨어 테스팅

소프트웨어 테스팅은 소프트웨어의 결함이 존재함을 보이는 과정입니다. 문제가 없는 것을 보이는 것이 아니라 문제가 있는 것을 밝히는 것입니다. 소프트웨어 생명주기의 프로세스 요구사항 분석-설계-구현-테스트-유지보수의 4번째 단계이기도합니다.
요구사항 설계와 다르게 동작하는 모든 것은 결함입니다. 이미 제작된 소프트웨어에 대해서 테스트 케이스를 만들고 테스팅하는 것은 매우 귀찮은 작업입니다. 그렇기 때문에 최근에는 개발 초기부터 테스트 케이스를 만들고 이것으로 개발하는 방식인 TDD(Test Driven Development) 방식이 만들어졌습니다.

테스트 주도 개발

TDD는 단위 테스트(Unit Test)를 기반으로 하는 방식입니다. 여기서 단위 테스트란 클래스 범주 내에서 작은 단위, 즉 함수 단위의 기능에 대한 유효성을 검증하는 테스트입니다. (참고) 단위 테스트를 작성하는 이유는 다음과 같습니다. - 코드를 수정하거나 기능을 추가할 때 수시로 빠르게 검증할 수 있다. - 리팩토링 시 안정성을 확보할 수 있다. - 개발 및 테스팅에 대한 비용을 절감할 수 있다. 보통 개발이 끝난 뒤에 문제가 없는지 확인하기 위해 애플리케이션을 실행하고, 직접 수동(통합) 테스트를 진행해왔습니다. 단위 테스트를 작성하지 않은 코드들은 테스트를 작성하지 않은 코드들보다 버그가 있을 확률이 높고, 직접 테스트하는 비용이 너무 큽니다. 따라서 이런 경우를 방지하기 위해 기능 구현과 동시에 테스트를 하는 방식을 도입했습니다.

하지만 무지성으로 테스트를 진행하는 것은 좋지 않습니다. 클린코드에 따르면, 좋은 테스트는 FIRST라는 5가지 규칙을 따라야 합니다. 1. Fast: 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 한다. 2. Independent: 각각의 테스트는 독립적이며 서로 의존해서는 안된다. 3. Repeatable: 어떤 환경에서도 반복 가능해야 한다. 4. Self-Validating: 테스트는 성공 or 실패로, bool 값으로 결과를 내어 자체적으로 검증되어야 한다. 5. Timely: 테스트는 적시에, 즉 테스트하려는 실제 코드를 구현하기 직전에 구현해야 한다.

좋은 테스트빠르게, 독립적으로 어느 환경에서든 실행이 가능하고 검증할 수 있어야 합니다. 만약 테스트 실행 비용이 크다면 통합 테스트를 진행하는 것이 나을 수 있습니다.

테스트 코드를 먼저 작성해야 하는 이유

좋은 테스트의 FIRST 조건 중 Timely는 ‘테스트 코드는 실제 코드를 구현하기 직전에 구현해야 한다’입니다. 이렇게 테스트 코드를 먼저 작성하여 단위 테스트를 진행하는 개발 방법론이 테스트 주도 개발(TDD)입니다. 테스트 코드를 먼저 작성하는 이유는 다음과 같습니다. - 코드를 깔끔하게 작성할 수 있다. - 장기적으로 개발 비용을 절감할 수 있다. - 개발이 끝나면 테스트 코드를 작성하기 힘들다. TDD의 개발 단계에는 리팩토링이 있습니다. 이 과정을 통해 중복된 코드가 제거되고, 복잡한 코드는 깔끔하게 정리됩니다. 개발 초기 속도가 더딘 것처럼 느껴지더라도 나중에 돌아보면 전혀 더디지 않았음을 알 수 있을 것입니다.
아, 테스트 코드를 작성할 때 ‘실패 테스트’부터 작성해야 합니다. 순차적으로 실패하는 테스트를 먼저 작성하고, 오직 테스트가 실패할 경우에만 새로운 코드를 작성해야 합니다.

TDD 방법 및 순서

  1. 실패하는 작은 단위 테스트를 작성합니다. 이때, 컴파일이 되지 않을 수 있습니다.
  2. 빨리 테스트를 통과하기 위해 프로덕션 코드를 작성합니다. 이를 위해 가짜 구현 등을 작성할 수도 있습니다.
  3. 그 다음의 테스트 코드를 작성합니다. 실패 테스트가 없을 경우에만 성공 테스트를 작성합니다.
  4. 새로운 테스트를 통과하기 위해 프로덕션 코드를 추가 또는 수정합니다.
  5. 1~4단계를 반복하여 실패/성공의 모든 테스트 케이스를 작성합니다.
  6. 개발된 코드들에 대해 모든 중복을 제거하며 리팩토링합니다.
  • 가짜로 구현하기: 실패하는 테스트를 가장 빠르게 구현하는 방법은 아무 값이나 반환하도록 하는 것이다. 테스트가 통과하면 단계적으로 상수를 변수로 바꾸어 준다.
  • 삼각 측량: 테스트 주도로 추상화된 과정을 일반화하는 과정이다. 테스트 예시가 2개 이상일 때만 추상화를 진행하자.
  • 명백하게 구현하기: 쉬운 코드인 경우, 위 두 방법을 사용하지 않고 바로 정답 코드를 구현하는 방법이다.

화이트박스 테스트와 블랙박스 테스트

  • 화이트박스 테스트(코드 기반 테스트)
    • 프로그램의 내부 구조와 동작을 테스트하는 방법으로, 소프트웨어 내부 소스 코드를 검사하는 방법이다.
    • 개발자 관점의 단위 테스트 방법이고 구현 기반 테스트이다.
    • 개발자가 내부 소스 코드의 동작을 추적할 수 있어 동작의 유효성과 코드를 모두 테스트 할 수 있다.
      1. 기초 경로 검사(Base Path Testing): 테스트 케이스의 설계자가 코드의 복잡성을 측정할 수 있게 해주는 테스트 기법이다.
      2. 제어 구조 검사(Control Structure Testing): 논리적 조건, 반복 구조, 데이터의 흐름을 테스트하는 기법이다.
      • 조건 검사(Condition Testing): 프로그램 모듈 내의 논리적 조건을 테스트하는 테스트 케이스 설계 기법
      • 반복 검사(Loop Tesing): 반복 구조에 초점을 맞춰 실시하는 테스트 설계 기법
      • 데이터 흐름 검사(Data Flow Testing): 실제 사용자들이 입력하는 값들을 변수에 넣을 때 변수 정의와 변수 사용 위치를 어떻게 했는지에 초점을 맞춰서 테스트하는 설계 기법
    • 검증 기준
      1. 문장 검증 기준(Statement Coverage): 소스 코드의 모든 구문을 한 번 이상 수행되도록 항목을 설계한다.
      2. 분기 검증 기준(Branch Converage): 소스 코드의 모든 조건문을 한 번 이상 수행되도록 항목을 설계한다.
      3. 조건 검증 기준(Condition Coverage): 소스 코드의 모든 조건문의 참, 거짓을 각각 한 번 이상 수행되도록 항목을 설계한다.
      4. 분기/조건 기준(Branch/Condition Coverage): 소스 코드의 모든 조건문과 각 조건문에 포함된 개별 조건식의 결과가 참/거짓인 경우 각각을 한 번 이상 수행되도록 항목을 설계한다.
  • 블랙박스 테스트(I/O 기반 테스트)
    • 소프트웨어의 내부 구조나 작동 원리를 모르는 상태에서 소프트웨어의 동작을 테스트하는 방법이다.
    • 요구 사항 검사를 위해 대외적으로 공개된 설계도 등을 기반으로 테스트를 진행하며, 소프트웨어의 특징이나 요구 사항 등에 초점을 맞춰 진행한다.
    • 사용자 관점의 테스트 방법이고 기능 테스트라고도 한다.
      1. 동치 분할 테스트(Equivalence Partitioning Testing): 정상적인 입력 자료와 비정상적인 입력 자료의 개수를 균등하게 해서 테스트 케이스를 정하고, 해당 입력 자료에 맞는 결과가 출력되는지 확인하는 기법이다.
      2. 경계값 분석(Boundry Value Analysis): 입력 조건의 중간값보다 경계값에서 오류 발생 확률이 높기 때문에 경계값을 테스트 케이스로 선정하여 검사하는 기법이다.
      3. 원인-효과 그래프 검사(Cause-Effect Graphing Testing): 여러 입출력 데이터를 분석해서 영향을 미치는 상황을 체계적으로 분석한 다음 효율성이 높은 테스트 케이스를 선정하는 기법이다.
      4. 오류 예측 검사(Error Guessing): 과거의 경험이나 확인자의 감각으로 테스트하는 방법이다.
      5. 비교 검사(Comparison Testing): 동일한 테스트 케이스를 여러 버전의 프로그램에 적용하여 동일한 결과가 출력되는지 비교하는 테스팅 방법이다.

참고