초보 개발자

소프트웨어 테스팅은 정말 어려운가? 왜 어려운가? 본문

컴퓨터공학 전공/소프트웨어공학

소프트웨어 테스팅은 정말 어려운가? 왜 어려운가?

mandudu 2022. 12. 8. 18:07

** 본 글은 차성덕 교수님의 소프트웨어공학이야기 책을 정리한 글입니다. **

테스팅 작업을 통해서 소프트웨어에 오류가 있음을 보일 수는 있지만, 오류가 없다는 점은 보일 수 없다. - Dijkstra

테스팅 작업: 창조적인 파괴, 의도하지 않은 대로 작동하는 것을 확인하는 행위이다. 

소프트웨어 테스팅의 현실적인 어려움

  • 실제 사용되는 환경에서 테스팅하기 어려움
    •  미사일 요격 시스템: 테스팅을 위해 전쟁을 할 수는 없음
  • 모든 입력값의 범위 및 값의 조합에 대해 테스팅이 불가능함
    • 자율주행 차량의 소프트웨어: 모든 상황을 테스팅하는 것은 불가능
    • 글꼴 선택, 효과 선택 등의 워드프로세서 기능: 매우 많은 조합 + 배타적 관계
      → 일부가 성공적이라고 다른 조합도 성공적일 거라 장담 불가능 
  • 소프트웨어의 오작동은 환경의 영향을 받기 때문에 테스팅에서 오류가 없어도 완벽하다고 장담할 수 없음.
    • 제대로 된 테스팅을 위해선 작동환경을 정확하게 명세해야 하고, 테스트 케이스에서 정확히 표현해야 함.
  • 소프트웨어가 여러 스레드로 구성된 경우, 프로그램을 수행할 때마다 행위 및 출력이 달라질 수 있음
    • 병렬 처리의 패턴을 예측할 수 없음.
    • 스레드 간의 상호작용을 모니터링 하기 위한 툴: capture-and-replay
      capture 기능을 수행해야 replay가 가능한데 이 과정이 스레드 상호작용에 영향을 미칠 수 있음

      Probing effect
      : capture-and-replay 없을 때는 오류없었는데, 사용 후 오류가 생길 수도 있음.
    • 툴을 사용해도 테스팅이 훨씬 어려워지기 때문에 병렬프로그램은 추천하지 않음.
  • 다른 기능과의 의존성이 높으면 테스팅이 어려움
    • 홈택스 시스템: 매년 조세 규정 바뀔 때마다 소프트웨어 수정, 새로운 규정 구현
  • 프로그래밍 언어에 따라 테스팅 기법이 다르게 적용되어야 함.
  • 테스팅 할 때 일부 코드만 반복적으로 수행이 됨
    1. 아직까지 발견된 오류가 없지만 소프트웨어의 결함이 없다고는 할 수 없다.
    2. 수행되지 않은 코드의 결함 여부는 알 수 없다.

 

소프트웨어 테스트 케이스

  • 입력값, 예상되는 출력값, 작동 환경에 대한 명세 등이 필요함
  • 보안과 관련된 결점을 찾는 경우 예상 출력값은 필요 없다.
    → 출력 값이 생성되기 전에 비정상적으로 멈추면 결점을 발견한 것이기 때문 (crash, runtime error)
  • SRS와 CODE의 교집합: Correct operation (명세 및 구현이 잘됨) 그러나 100%는 될 수 없을 것
  • Fault of omission: SRS의 누락으로 인한 오류
    - CASE 도구를 사용하여 기술하고, 테스트케이스를 연동하여 관리하기 때문에 발견하기 쉬움 (black-box)
  • Fault of commission: 요구사항에 없는 추가 기능이 구현되어 생기는 오류
    - 보안공격을 목적으로 한 추가  코드가 숨어있는 경우 (white-box)

* black-box testing? 내부는 알 필요 없음, SRS 기반의 테스팅 (입력값-예상 결과값)

* white-box testing? 실제 코드를 꼼꼼히 읽어가며 찾음, 제품의 내부 구조/동작을 세밀하게 검사

 

테스팅 예제 1

int foo (int a, int b, int c, int d, float e){
	float e;
	if (a == 0) return 0;
	int x = 0;
	if ((a==b) OR (c==d) AND bug(a)) x=1;
	e = 1/x;
	return e;
}
  • if (a==0) 조건문: a의 값이 0인 경우 / a의 값이 0이 아닌 경우
  • if ((a==b) OR (c==d) AND bug(a)) 조건문: (a==b)가 참인 경우 / 나머지 조건이 참인 경우 / 둘 다 참인 경우
    → 두 조건문 중 무엇이 먼저 수행되는지 정해진 규칙이 없다면, 모든 순서를 테스팅해야 함.
  • Call-by-value 방법으로 값이 변하지 않으므로 간단함
    but bug(a)가 수행되는 과정에서 side-effect가 발생하도록 call-by-reference를 이용하면 복잡해짐.
  • 조건문에 함수의 호출, shared memory 혹은 external 변수를 이용한다면 더욱 복잡해짐.

테스팅 예제 2

1. if (a==0 | b/a==0 | c==d)
2. if (a==0 || b/a==0 || c==d)
  • Java에서의 short-circuit evaluation
    | 연산자는 끝까지 검사, || 연산자는 앞에서부터 검사 후 true 나오면 끝
  • a=0일 때 1번 문장은 오류 (division by zero), 2번 문장은 그대로 실행됨 
  • 특정 프로그래밍 언어의 정의 및 semantics에 대해 잘 알아야 함.

테스팅 예제 +a

  • C언어 정수형 변수 선언: 변수의 초기값이 어떻게 정의되는지 기술 X
    - 컴파일러가 메모리영역을 할당해주는 것으로 끝, 초기화 안 해줘도 됨

    - 심지어 4바이트를 반드시 할당해주지 않아도 됨. 운영체제별로 확인 필요
  • Java 정수형 변수 선언: Bytecode 개념 지원, 운영체제 상관 X
    - 4바이트 할당, 초기화 필요, 저장 값 범위까지 명세되어 있음
  • Java binding: Static binding vs Dynamic binding

언어와 구현 방법에 따라 테스팅 작업의 난이도는 매우 달라진다.
불필요하게 복잡한 소프트웨어는 좋지 않고, 디자인 과정에서 가독성 및 모듈화를 강조하는 이유이다.
structured design/programming에서도 하나의 모듈에 single entry, single exit를 강조하는 이유.