반응형

제목: 분산 인터넷 시스템 테스팅 사례 연구(A Case Study of Testing a Distributed Internet-System)

저자: Siegfried GOESCHL 1, 오스트리아

문서유형: 컨퍼런스 페이퍼, 2001

 

관계형 데이터베이스에 대량의 쿼리를 처리하는 인터넷 상의 분산 금융 서비스 시스템 개발 프로젝트에서 사용된 테스팅 전략 및 도구를 기술한 자료



프로젝트 개요

1. 기존 애플리케이션 리엔지니어링 프로젝트

  • 세계 곳곳에 위치한 클라이언트에게 금융 데이터로의 빠른 접근을 허용하는 시스템으로 수백만 개의 튜플(tuples)을 가진 관계형 테이블에 대한 표준적인 쿼리가 단 몇 초 내에 처리되어야 함
  • 기존의 애플리케이션에서는 두 개의 다른 형식의 서버 시스템(하나는 표준 C로 프로그램 되었고 다른 하나는 4세대 언어인 FORTE로 구현됨)Sybase 데이터베이스에 액세스를 제공하고 쿼리의 결과는 SGML 형식으로 전달됨
  • 이 원래의 시스템이 한계에 다다르게 되면서 클라이언트의 로드가 증가하는 상황에서 점점 느려지고 신뢰성 문제가 발생하게 됨
  • 경영진은 빠른 메시지 배포와 데이터를 SGML로 변환하는 별도의 SGML 프론트엔드를 확보하기 위해 기존 시스템을 리엔지니어링 하기로 결정함. , 핵심 트랜잭션을 신규 CORBA 서버를 가지고 C++로 재구현하기로 함
  • 웹 서버를 분산 CORBA 서버 및 관계형 데이터베이스와 결합하는 신규 시스템의 아키텍쳐는 아래와 같다. Web 서버는 HTML 리퀘스트를 수신하고, 이를 SGML 리퀘스트로 전환하며, 리퀘스트 타입에 따라 적절한 서버로 보낸다. SGML 서버는 Web 서버로부터 SGML 리퀘스트를 받아 CORBA 서버상의 하나 또는 여러 개의 IDL 기능을 호출(invoke)한다. CORBA 서버는 이 호출을 비즈니스 로직을 캡슐화(encapsulation)하여 서버를 데이터베이스 레이아웃 변경으로부터 보호하는 데이터베이스 계층으로 위임한다.

[시스템 아키텍쳐]


2. 프로젝트 제약(constraints)

이 리엔지니어링 프로젝트는 아래와 같은 제약을 가지고 있었다.

  • 프로젝트 팀의 SYBASE 데이터베이스 사용 경험이 거의 없었음
  • 리엔지니어링 할 기능이 C 또는 FORTE로 쓰여졌고 대략 10,000 라인의 레가시 코드로 구성되었음
  • 성능 문제 때문에 원래의 데이터베이스도 리엔지니어링 되어야만 했음
  • 재사용이 가능하도록 하기 위해 기존의 획일적인 단일 엔트리(single-entry) API가 모듈식 IDL 인터페이스로 대체되어야만 했음
  • 개발자가 메모리 관리에 대한 책임을 져야 하기 때문에 C++CORBA 서버를 개발하는 것은 에러 발생 가능성이 높음
  • 결과물이 여러 플랫폼(SOLARIS, NT)을 지원해야만 했음

 

이런 제약들 때문에 프로젝트 초기부터 테스팅이 특히 중요시되었고 프로젝트 자원의 적어도 절반을 테스팅에 할당하기로 결정함. 그리고 이런 테스팅에 대한 강조 덕분에 프로젝트가 시간 안에 성공적으로 종료될 수 있었음


테스트 계획(Test Planning)

1. 테스트 요구사항

테스트 계획 시 아래와 같은 요구사항을 고려해야만 했음

  • SGML 서버가 이전 서버와 완전하게 호환되어야 한다(fully backward compatible).
  • 신규 시스템이 기존 시스템 보다 더 좋은 평균 반응 시간을 가져야 한다.
  • 신규 시스템을 신뢰할 수 있어야 한다(reliable).
  • 테스트가 다수의 플랫폼 상에서 실행 될 수 있어야 한다.


2. 테스트 전략

소프트웨어의 3개 계층(SGML 서버, CORBA 서버, Database 서버)을 먼저 개별적으로 테스트하고 이어서 서로 통합하여 테스트 하기로 함. 통합은 상향식 테스트 전략(bottom-up test strategy)을 사용. , 아키텍쳐의 데이터베이스 액세스 계층 테스트부터 시작하여 클라이언트로 점차 올라감. 

  • 데이터베이스 층 테스트(Database Layer Test): 데이터베이스 계층을 테스트 하기 위해 C++로 쓰여진 회귀 테스트 스위트를 생성함. 이 회귀 테스트는 프로젝트 인력의 SYBASE 경험 부족을 보완하고, 데이터베이스 스키마에서 생기는 변화를 테스트 하며, 데이터베이스에 대한 다양한 쿼리를 벤치마크(기존 품질/성능과 비교 측정) 할 방법을 제공한다. 동일 데이터에 액세스하는 여러 다른 방법이 존재하므로 회귀 테스트가 결과를 기록하고 비교하는 중요한 역할을 함
  • CORBA 서버 테스트: CORBA 서버를 테스트하기 위해 서버로의 리퀘스트를 생성하는 프록시 클라이언트 애플리케이션(a proxy client application)을 구현함. 이 프록시 클라이언트는 실제 클라이언트의 동작을 흉내 내어 다양한 순서의 다양한 종류의 리퀘스트를 생성한다. 이 테스트는 메모리 누수(memory leaks)가 없음을 증명하고 ORB(Object Request Broker)의 신뢰성(reliability)을 검증하는데 집중함
  • SGML 서버 테스트: SGML 서버를 테스트 하기 위해 회귀 테스트 스위트를 생성하고 사전 기록된 리퀘스트를 가지고 이를 실행한다(API 명세에 기반한 리퀘스트와 생산 시스템으로부터 나온 수백 개의 실제 리퀘스트를 활용). 생산 시스템(production system)에서 추출된 리퀘스트는 기존의 운영 프로파일(operational profile)을 반영하고, 명세로부터 도출된 리퀘스트는 신규 요구사항을 반영한다.


테스트 도구(Test Toolbox)

테스팅을 용이하게 하기 위해 프로젝트에서 아래와 같은 도구 들이 개발되어 사용되었다.

  • 어써션 패키지(an Assertion Package)
  • 로그파일 뷰어(Logfile Viewer)
  • C++ 단위 테스트 프레임워크
  • Perl 회귀 테스트 프레임워크


어써션 패키지(Assertion Package)

1. 어써션 체킹

  • ‘Design by Contract’은 디버깅에 소요되는 시간을 크게 줄일 수 있는 중요한 개념으로 프로그램의 입력 상태에 대한 사전조건(preconditions)’, 출력 상태에 대한 사후조건(postconditions)’, 변경되면 안 되는 오브젝트 상태를 표현한 불변 어써션(invariant assertions)’을 프로그래머가 구체적으로 명세할 것을 요구한다.
  • C Runtime Library에 포함된 표준 어써션 기능은 어써션 위반 시 에러 메시지를 출력하고 프로그램을 중단시켜 버리므로 운영 환경의 클라이언트 테스트 또는 서버 테스트에서는 그다지 유용하지 못하다(이런 표준 어써션 기능은 로컬 모듈 테스팅에서 주로 사용됨).
  • 이런 이유로 프로젝트에서 아래의 기능을 가진 어써션 패키지를 구현하였음
    -
    Windows NT 상에서의 즉각적인 디버깅(just in time debugging) 지원
    -
    커스토마이징이 가능한 동작(customisable behaviour)
    -
    어써션의 선택적 활성화(selective activation of the assertions)


2. 즉각적인 디버깅(Just In Time Debugging)

  • 실패한 어써션(failed assertion)이 나올 때마다 디버거를 시작시키는 MFC 어쎠선 패키지에서 빌려온 개념으로 디버깅과 테스팅에 매우 유용한 기능
  • 프로그램이 중단된 이유가 어드레싱 에러(addressing error) 때문인지 또는 불변식(invariant)을 위반했기 때문인지를 아는 것이 중요하며, 후자의 경우 왜 invariant가 위반되었는지 그 이유를 정확하게 집어내기 위해 콜 스텍(call stack)을 조사할 수 있다.


3. 커스토마이징이 가능한 동작(Customisable Behaviour)

  • 좋은 어써션 패키지는 아래의 요구사항을 충족시킬 수 있도록 유연해야 한다.
    -
    에러 메시지를 공식 기록(log)
    -
    관리 콘솔에 SNMP(Simple Network Management Protocol) 경고(alert)를 전송
    -
    시스템 관리자(system administrator)에게 메일을 전송
    -
    프로그램 중단시키는 대신 예외를 제기(throw an exception) 
  • 이런 유연성은 테스트 대상 시스템이 다양한 어써션 핸들러를 등록(register) 할 수 있는 ‘Chain of Command 스텍을 구현함으로써 달성될 수 있었음. 각각의 어써션 핸들러는 어써션을 다음 핸들러에게 위임하기 전에 특정 수준의 기능 제공을 책임진다.


로그파일 뷰어(Logfile Viewer)

  • Windows NT에서 로그파일을 보는데 사용되는 에디터가 아래와 같은 여러 제약을 가짐
    -
    일부 프로그램이 로그파일을 독점적으로 잠그려 한다.
    -
    로그파일 뷰를 자동으로 업데이트하는 프로그램이 거의 없다.
    -
    큰 로그파일을 보기 위해서 필터가 필요하다.
    -
    다수의 로그파일을 동시에 볼 필요가 있을 수 있다.
  • 따라서 이런 제약 없이 클라이언트/서버 타입의 애플리케이션 테스팅에 적합하도록 커스토마이징된 로그파일 뷰어를 개발함

[다수의 로그파일 모니터링]


C++ 단위 테스트 프레임워크: APITEST

1. 단위 테스트의 잘 알려진 딜레마

  • 경험에 비추어 보면 소프트웨어 개발 주기의 한 부분으로 공식적인 단위 테스트(formal unit tests)를 생성하고 유지보수 하는 것은 그 만한 가치가 있다. 대개 소프트웨어 개발자는 자신의 작업 단위를 위한 테스트 드라이버(“main” 루틴과 결과를 출력하는 몇 줄의 문장으로 구성된 코드)를 작성한다.
  • 이 테스트 드라이버는 소프트웨어가 처음 개발 및 실행되는 동안에 실패가 발생하지 않음을 보여주지만 이후 돌보지 않게 되면서 테스트 대상 단위에 더 이상 맞지 않게 되고 심지어 컴파일조차 안 되기도 함
  • 모두에게서 단위 테스팅에 대한 관심이 사라질 즈음에는 이런 테스트 소프트웨어를 유지보수 하는 비용이 너무 비싸 그대로 버려지게 됨(개별 단위 테스트를 재생성할 비용 효과적인 방법이 부재)
  • 이런 딜레마는 항상 제기되는 질문인 왜 소프트웨어 개발자가 적절한 단위 테스트를 생성하지 않는가와도 관련됨. 개발자는 대개 아래와 같은 답을 한다.
    이 단위를 따로 떼어서 테스트 하는 것이 너무 어렵다…”
    단위 테스트가 타당하기에는 아직 코드가 충분하지 않다…”
    그럴만한 충분한 시간이 없다…”
    단위 테스트를 어떻게 작성하는지 모른다...”


2. 일회용(throw-away) 테스트 드라이버 vs. 재사용가능한 단위 테스트 프레임워크

  • 위에 열거된 개발자의 반박 대부분이 어느 정도는 타당하지만 가용한 단위 테스트 프레임워크가 존재하는 경우는 상황이 달라진다. 재사용 가능한 적절한 단위 테스트를 생성하는데 걸리는 시간과 한번 쓰고 버릴 일회용 테스트 드라이버를 생성하는데 걸리는 시간이 비슷하다면 소프트웨어 개발자도 개발자로서의 자신의 가치를 높일 수 있는 단위 테스트를 소홀히 하기 어렵게 된다.
  • 이런 이유로 분산 테스트의 특수한 요구사항을 충족시키는 C++ 단위 테스트 프레임워크를 프로젝트에서 구현하기로 결정하였고, 그 결과가 APITEST 도구이다.
  • 개발된 APITESTSOLARISWindows NT 환경의 다른 여러 프로젝트에도 사용되었다.


3. 단위 테스트 프레임워크 기본 개념

  • 각각의 단위 테스트는 고유한 이름을 가진 하나 또는 그 이상의 테스트 스위트를 가지고, 각각의 테스트 스위트는 하나 또는 여러 테스트 케이스를 포함할 수 있다. 모든 테스트 케이스는 테스트 스위트 내의 번호에 의해 유일하게 식별된다

[단위 테스트, 테스트 스위트, 테스트 케이스 간의 관계]


  • 특정 테스트 케이스 호출의 결과(, 테스트 판정 결과)통과(passed)’, ‘실패(failed)’, ‘충돌(crashed)’, ‘미정의(undefined)’ 4개 중 하나가 주어진다. 테스트의 모든 요구된 사후조건을 충족시키지 못하면 실패, 예외(exception)가 테스트 케이스 구현 밖으로 전파되면 충돌, 더 이상 사용되지 않는 테스트 케이스는 미정의가 할당된다.
  • 커맨트 라인 인터페이스(command line interfaces)는 단위 테스트를 다양한 방식으로 실행하는 것을 허용한다. , 모든 테스트 스위트를 실행하거나, 하나의 특정 테스트 스위트만 실행하거나, 특정 테스트 스위트 내의 하나의 테스트 케이스만을 실행 하는 것이 가능하다.

[단위 테스트 실행 후 출력되는 실행 요약 정보(execution summary) 예]



4. 단위 테스트 프레임워크 기능

APITEST 단위 테스트 프레임워크는 아래 기능을 지원한다.

a) 메모리 누출 발견(Detection of Memory Leaks)

  • 대규모 소프트웨어 개발에서 C/C++ 프로그래밍 언어의 가장 어려운 측면 중 하나가 수동 메모리 관리(manual memory management)이며, 따라서 메모리 누출 발견을 간편하게 하는 것이 필수적이다.
  • APITEST는 개발 중 메모리 누출 및 기타 관련 문제를 발견하는데 자주 사용되는 도구인 Pure AtriaINSURE++와 밀접한 통합을 제공한다.
  • INSURE++는 모든 메모리를 추적하고 실행 동안의 메모리 할당을 체크하지만 초기화 루틴(initialisation routines)에 의해 자원이 할당되는 방식 때문에 그 결과를 해석하기가 어렵다. 이해할 수 있는 결과를 얻기 위해 테스트 케이스를 반복적으로 실행하는 것이 필수적으로 되어버림
  • APITES는 테스트 케이스를 실행하기 전에 INSURE++ 콘솔에 상태 메시지를(status messages) 출력하고, 테스트 케이스 실행 후에는 할당된 자원을 보고한다.
  • INSURE++가 없는 경우 자원 소모(resource consumption)를 수동으로 추적하는 것이 가능하지만 작은 메모리 누출을 발견하는데 더 많은 시간이 요구된다. 따라서 APITEST INSURE++를 조합하여 사용하는 것이 최적임 


b) 성능 측정(Performance Measurements)

  • 실행 요약 정보(execution summary)가 테스트 케이스를 실행하는데 걸린 시간에 대한 정보를 제공함
  • 의미 있는 결과를 얻기 위해서는 테스트를 한번 이상 실행하는 것이 필수적임. 그렇지 않으면 초기화 시간(initialisation time)에 의해 결과가 왜곡될 수 있음


c) 스트레스 테스팅(Stress Testing)

  • 서버 개발을 위해서는 완전 부하(full load) 하에서 시스템을 테스트 하여 부하 증가 시의 성능 데이터를 수집하는 것이 필수적
  • 클라이언트 역할을 하는 단위 테스트가 있다고 가정했을 때 이를 재사용하여 장시간에 거쳐 다수의 클라이언트가 존재하는 상황에서의 서버를 테스트 할 수 있음
  • APITEST는 하나 또는 여러 개의 쓰레드를 가지고 테스트를 여러 번 실행시키는 커맨드라인 옵션을 제공하므로 이런 테스트 수행을 용이하게 한다. 또한 실제 클라이언트의 동작을 흉내 낼 수 있도록 두 테스트 케이스 실행 사이의 간격(사용자 정의 대기 시간) 지정을 지원한다.


d) 멀티쓰레드 안전성(Multithread Safety) 테스트

  • 소프트웨어가 실행 된 후에 데드락에 걸리지 않음을 확인하려면 단위 테스트를 멀티 쓰레드로 실행하는 것이 중요하다(하지만 멀티쓰레드 테스트 프로그램을 작성하는데 훨씬 더 많은 노력이 요구되므로 대개 이런 테스트가 생략됨).
  • APITEST는 커맨드라인에 실행 쓰레드 수를 정의할 수 있게 하여 이런 테스트를 용이하게 한다. , 단위 테스트가 다수의 쓰레드에 의해 동시에 실행될 수 있다. 또한 실제 클라이언트의 동작을 흉내내기 위해 각각의 쓰레드에서의 테스트 케이스 실행 순서를 무작위화(randomise) 하는 것이 가능하다.
  • 이 테스트를 통과하였다고 해서 코드가 멀티쓰레드 안전하다고(multithread-safe) 보장할 수는 없지만 일정 시간 후에 데드락에 걸리지는 않음을 확인시켜 준다.


e) 테스트 데이터의 간단한 변경(Simple Modification of Test Data)

  • 일회성 테스트 드라이버(throw-away test driver)는 필요한 테스트 데이터를 하드코드 하는 경향이 있는데, 이는 새로운 테스트 환경으로의 마이그레이션을 복잡하게 만든다(, 통합 테스팅을 위한 신규 데이터베이스).
  • 이런 복잡을 피하기 위해 테스트 데이터에 액세스 하는 간편한 방법을 제공하는 것이 필수적이며, 프로젝트에서는 구성 파일(configuration file) 지원을 통해 이를 달성하였다.
  • 아래 예처럼 구성 파일에서 각각의 엔트리는 하나의 키(a key)와 하나의 값(a value)으로 구성되고, 키는 테스트 스위트 명, 테스트 케이스 번호, 변수 명으로 구성된다


f) 회귀 테스팅과 통합(Integration of Regression Testing)

  • 데이터베이스에 액세스하는 모듈을 테스팅 할 때 단위 테스트는 일부 불변사항(invariants)을 체킹하는 것 외에는 대개 결과의 내용(content)을 체크하지 않는다. Invariant 체킹 예) “반드시 결과가 나와야 한다”, “이 필드는 null 일 수가 없다
  • APITEST‘verbose 모드라는 개념을 제공하며 이는 콘솔 또는 파일로 ‘verbose 출력을 뿌린다. 이런 verbose 출력은 캡쳐될 수 있고 차이점 발견을 위해 참조 정보(reference)에 직접 비교될 수 있다.
  • APITEST는 모든 쓰레드가 각각 자신의 출력 파일을 사용하는 것을 가능하게 하므로 쓰레드들의 verbose 출력 간의 차이점을 발견하는 것이 가능하다. 모든 쓰레드가 동일한 테스트를 실행하므로 이런 차이점은 경쟁 상황(race condition)’을 의미할 수도 있다.


g) 단순 스크립팅(Simple Scripting)

  • 현실에서는 여러 이유로 매번 실행 될 필요가 없는 파손 테스트(broken tests)가 항상 나오게 된다.
  • 이를 위한 해결책은 실행되어야 하는 모든 테스트 케이스와 테스트 스위트를 스크립트 파일(a script file)에 명세 하는 것이다.
  • 이 스크립트 파일은 커맨드라인 옵션에 의해 작동된다


h) 커맨드 라인 옵션(Command Line Options)

단위 테스트 프레임워크는 아래와 같은 커맨드라인 옵션을 지원한다.

Command Line Option

Description

-C file

테스트 구성 파일

-F file

실행할 테스트를 나타낸 스크립트 파일

-I

실패한 테스트 무시(Ignore)

-M threads

실행 쓰레드

-R

쓰레드에 대한 랜덤 셔플 테스트 순서

-S testsuite [-T testcase]]

실행 테스트

-O filename

출력 파일

-V

verbose 출력 활성화

-W ms

테스트 케이스 간의 대기 시간

-X repeats

반복 횟수


Perl 회귀 테스트 프레임워크: DROPTEST

1. DROPTEST 기본 개념

  • DROPTESTFred Wild에 의해 개발된 Perl 회귀 테스트 프레임워크이다. 약간의 변경을 통해 다양한 플랫폼(Windows 95, Windows NT, SINIX, AIX, SOLARIS)의 여러 프로젝트에서 사용되어짐
  • SGML 서버의 동작이 교체된 기존 컴포넌트와 동일함을 증명하기 위해 SGML 서버를 위한 회귀 테스트 스위트를 구현함(이 회귀 테스트 스위트의 일부는 명세로부터 도출되었고 나머지는 생산 시스템으로부터 나온 수백 개의 실제 리퀘스트로 구성됨)
  • 전체 프로그램 로직이 아래의 디렉토리 구조를 기반으로 하며, 기본 아이디어는 회귀 테스트를 test 디렉토리에 떨구는 것이다(그래서 이름이 DROPTEST)
  • Perl 스크립트는 test 디렉토리에 있는 모든 파일을 체크하고 만약 prepost 디렉토리에 상응하는 파일이 있으면 이것들을 PRE step(선택) à TEST step(필수) à POST step(선택)의 순서로 실행한다.
  • 선택적인 PRE stepPOST step은 구성 정보 복사, 임시 파일 삭제, 남아 있는 프로세스 말소(kills) 등의 작업을 수행한다.
  • TEST step은 현 출력이 reference 디렉토리에 저장된 참조 출력(a reference output)과 동일하면 성공적인 것으로 다르면 성공적이지 못한 것으로 간주된다.


2. SGML 서버 회귀 테스팅

  • 회귀 테스트는 실제 생산 데이터베이스(real production database)의 복사본을 가지고 실행되었고 매일 업데이트 되었음
  • 이런 환경에서의 회귀 테스트는 신규 참조 데이터(reference data)를 생성하는 것이 얼마나 용이한지에 크게 의존함. 프로젝트에서는 C 또는 FORTE로 쓰여진 오리지널 서버에 회귀 테스트를 실행 시킴으로써 참조 데이터를 간편하게 생성할 수 있었음
  • SGML 서버는 SGML 리퀘스트를 SGML 서버로 전송하는 PERL 스크립트를 가지고 테스트 되었으며, PERL 스크립트 호출은 DROPTESTTEST step에서 이루어짐
  • POST step에서는 캐싱 관련 테그가 결과로부터 제거되었고 참조 파일(reference files)과 비교되었다. 이런 자동화된 비교를 통하여 새로운 결과에 있는 차이점(deviations)이 손쉽게 발견될 수 있었다.

[금융 문서를 검색하기 위한 SGML 리퀘스트 데이터]


테스트 결과

  • 발견된 버그는 아래 그림처럼 제품 사용이 가능하지만 고객 불만족이 있을 수 있는 사소한 버그(minor)’, ‘제품을 사용할 수 없는 심각한 버그(severe)’, ‘서버 충돌을 야기하는 치명적 버그(fatal)’로 분류됨
  • 또한 ‘DROPTEST 실행에 의해 발견된 결함(droptest)’, ‘APITEST 실행에 의해 발견된 결함(apitest)’, ‘빌드 프로세스 동안의 문제(build)’, ‘수동 테스팅 및 테스트 케이스 준비 동안에 발견된 결함(manual)‘, ‘코드 검토를 통해 발견된 결함(review)‘으로 분류됨


  • 아래는 발견된 버그를 메모리 관련 버그(memory)’, ‘구현 관련 버그’, ‘SGML 서버에서의 출력 관련 버그(sgml)’로 분류함
  • 메모리 관련 문제로는 메모리 누출, 메모리 엑세스 위반, 이미 자유롭게 된 메모리 사용 등이 있다.
  • SGML 관련 문제로는 SGML 응답(replies) 포맷팅, 유실된 SGML 테그(tag) 등이 있다.

반응형

+ Recent posts