본문 바로가기

프로그래밍(TA, AA)/개발방법론

[개발방법론] 테스팅

테스트는 소프트웨어 엔지니어가 해야하는 매우 중요한 작업입니다. 따라서 면접장에서 관련된 질문을 받을 수 있습니다. 물론, 여러분이 테스트 관련 직종에 지원하고 있다면(다시 말해 SDET(Software Design Engineer in Test) 또는 SET(Software Engineer in Test)로 지원하고 있다면) 당연히 테스트에 주의를 기울어야 할 것입니다.


테스팅과 관련된 질문들은 보통 다음 네 가지 범주 중 하나에 속합니다. (1)실생활에서 만나는 객체(펜과 같은)를 테스트 하라 (2)소프트웨어를 하나 테스트하라 (3)주어진 함수에 대한 테스트 코드를 작성하라 (4)발생한 이슈에 대한 해결책을 찾아내라.


이 네가지 범주 각각에 대한 접근법을 지금부터 살펴보도록 하겠습니다.


잘 정돈된 입력이 주어지거나, 사용자가 지정된 가이드라인을 따라 시스템을 사용할 것이라는 가정을 하면 안된다는 사실을 기억하길 바랍니다. 그릇된 방식으로 시스템이 사용될 수 있으니, 그에 대비해야 합니다.


표면적으로만 보면 테스트에 관계된 문제들은 광범위한 테스트 케이스 목록을 만들어 내는 것에 관계된 것처럼 보입니다. 어느 정도 맞는 말입니다. 적절한 테이스트 케이들을 만들어 내야 합니다. 하지만 면접관들이 보고자 하는 것은 더있습니다.


- 큰 그림을 이해하고 있는가

당신은 소프트웨어가 지향하는 바가 무엇인지 정말로 이해하고 있는 사람인가? 테스트 케이스 간의 우선순위를 적절히 매길 수 있는가? 가령 여러분이 아마존과 같은 전자 상거래 시스템을 테스트하라는 요구를 받았다 해보자. 상품 이미지가 적절한 장소에 정확하게 뜨는지 확인하는 것은 아주 중요하다. 하지만 구매 대금이 정확하게 지불되도록 하는 것, 배송 목록이 정확히 갱신되도록 하는것, 그리고 대금이 이중으로 청구되도록 하지 않는 것이 훨씬 더 중요하다.


- 퍼즐 조각을 제대로 맞추는 방법을 아는가

소프트웨어가 어떻게 동작하는지, 그리고 각 소프트웨어가 보다 더 큰 생태계의 일부로 어떻게 귀속되는지 이해하고 있는가? 여러분이 구글의 스프레드시트 소프트웨어를 테스트하라는 업무를 할당받았다 해보자. 문서를 열고, 저장하고, 편집하는 과정을 테스트하는 것은 중요하다. 하지만 구글 스프레드시트는 더 큰 생태계의 일부분이다. Gmail이나 플러그-인 등의 다른 컴포넌트들과 제대로 통합되는지 테스트 해봐야 한다.


- 조직화

문제에 구조적으로 접근하고 있는가, 아니면 생각나는 대로 아무 방법이나 질러보고 있는가? 어떤 응시자는 카메라에 대한 테스트 케이스를 찾으라는 문제를 던지면 떠오르는 대로 아무 테스트나 만들어 낸다. 훌륭한 응시자는 '사진 촬영', '이미지 관리', '설정' 등의 범주로 시스템을 나눈 다음에 테스트를 만들어 갑니다. 이런 구조적 접근법을 사용하면 테스트 케이스를 보다 풍부하게 찾아 나갈 수 있다.


- 실용성

실제로 적용하기 적절한 테스트 계획을 만들 수 있나? 가령 어떤 사용자가 특정한 이미지를 여는 순간 소프트웨어가 다운된다고 보고 있다고 하자. 여러분이 그 사용자에게 소프트웨어를 재설치하라고 말한다면, 그건 그다지 실용적인 답이 못된다. 여러분이 만든 테스트 계획은 실행 가능해야 하고, 실제로 구현할 수 있는 실용적인 것이 되어야 한다.


이러한 측면들을 고려하고 있다는 사실을 드러내 보이면 테스팅 팀의 중요한 일원이 될 수 있다는 사실을 보일 수 있다.



실제 세계의 객체 테스트하기


어떤 지원자는 '펜을 테스트하라'와 같은 질문을 받으면 당환한다. 결국, 여러분이 테스트해야 하는 것은 소프트웨어를 테스트하는 것 아닌가. 그렇지 않나? 그럴수도 있다. 하지만 여전히 이런 질문을 던지는 면접관들은 많다. 사례를 통해 살펴 보자.


질문: 클립을 테스트하려면 어떻게 하겠나?


단계1: 사용자는 누구인가? 왜인가?

면접관과 어떤 사용자가 어떤 목적으로 제품을 사용하는지 의논해봐야 한다. 생각했던 것과는 다른 답이 나올 수 있다. 가령 "선생님들이, 신문을 함께 철해 두기 위해 사용한다"는 답을 들을 수도 있고, "조각가가 동물 모양으로 구부리기 위해 사용한다"는 답을 들을 수도 있다. 둘 다일 수도 있다. 면접관이 어떤 답을 하느냐에 따라 남은 질문들을 어떻게 처리해야 할지 생각해야 한다.


단계2: 어떤 유스케이스가 있나?

유스케이스의 목록을 만들어 두면 도움될 것이다. 이 문제의 경우, 유스케이스는 단순히 '신문을 망가뜨리지 않고 함께 철한다'가 될 것이다.

질문에 따라서는 유스케이스가 여러개 도출될 수 있다. 뭔가를 보낼 수도 있고, 받을 수도 있고, 쓰거나 지울 수도 있을 것이다.


단계3: 한계 조건은?

하나의 클립으로 철할 수 있는 신문은 몇 장인가? 몇 장 이상을 철하면 클립이 망가지는가? 한번에 30장은 영구적 손상없이 철할 수 있다거나, 30장에서 50장 정도는 약간의 손상으로 철할 수 있다거나 하는 정보를 찾아야 한다.

이 한계 조건은 환경적 요인들로 확대될 수 있다. 가령, 우리가 테스트하는 클립은 아주 더운 곳에서도 정상적으로 문서를 철할 수 있어야 한다거나, 하는 조건들이 그에 해당한다. 아주 추운 곳에서는 어떠한가?


단계4: 스트레스/고장 조건은?

고장 나지 않는 제품은 없다. 따라서 고장이 발생하는 조건을 분석하는 것도 여러분이 해야 하는 일이다. 면접관과 토론해봐야 할 것 가운데 하나는 제품이 고장 나더라도 받아들일 수 있는 때는 (심지어는 필요한 때는) 언제인가 하는 것이고, 어떤 종류의 고장을 심각하게 간주해야 하느냐 하는 것이다.

가령, 여러분이 세탁기를 테스트한다면 여러분은 우선 해당 세탁기가 적어도 30벌의 셔츠나 바지를 세탁할 수 있는지 알아봐야 할 것이다. 30개에서 45개의 의류를 투입하면, 오염이 적절히 제거되지 않는 등의 사소한 고장이 발생할 수 있다. 45벌 이상의 옷을 넣으면 '극심한 고장'이 생기더라도 받아들일 수 있을 것이다. 물론 이 경우 '극심한 고장'은 세탁기에 물이 공급되지 않는 등의 오작동이어야 한다. 물이 넘치거나, 화재가 발생한다거나 하는 것이 아니다.


단계5: 테스트는 어떻게 수행할 것인가?

어떤 경우, 이것은 테스트 수행에 관계된 세부사항을 토로하는 것과 관계되 작업이다. 가령, 정상적으로 사용한다는 가정 하에 어떤 의자를 5년 동안은 문제 없이 사용할 수 있는지 테스트하고자 한다면, 그 의자를 실제로 집에 가져가서 오년동안 기다리고 싶지는 않을 것이다. 대신 '정상적으로 사용'한다는 것이 어떤 의미인지를 정의해야 한다(일년에 몇 번이나 앉으면 정상 사용인가? 팔걸이 부분은 어떤가?) 또한 수작업으로 테스트하는 것 이외에도, 기계가 수행하는 자동화된 테스트를 도입할 것도 고려해 봐야 한다.



소프트웨어 테스팅

하나의 소프트웨어를 테스트하는 것은 실제 세계의 객체를 테스트하는 것과 아주 유사하다. 주된 차이점은 소프트웨어 테스팅의 경우 성능 테스트 세부 사항을 더 많이 강조한다는 것이다.


소프트웨어 테스팅의 두 가지 핵심적 측면은 다음과 같다.


수동 테스트 vs. 자동화된 테스트: 실제 세계에서라면 모든 것을 자동화하고 싶겠지만, 항상 가능하지는 않다. 어떤 것들은 수동 테스트가 더 나은데, 컴퓨터가 효과적으로 검사할 수 있도록 정량화하기 어려운 정성적 특성이 너무 강하기 때문이다(어떤 콘텐츠가 포르노그래피에 관계된 것인지 판단하는 것 등이 그렇다). 또한, 컴퓨터는 일반적으로 살펴보라 지시한 문제들만 인식하지만, 인간의 인지 능력은 특별히 검토된 적이 없는 새로운 문제들을 밝혀낼 수도 있다. 인간과 컴퓨터는 둘 다 테스트 프로세스의 핵심적 부분이다.


블랙박스 테스트 vs. 화이트 박스 테스트: 이런 구분은 소프트웨어 내부를 어디까지 들여다 볼수 있느냐에 근거한 것이다. 블랙 박스 테스트의 경우, 우리는 소프트웨어를 주어진 그대로 테스트 해야 한다. 화이트 박스 테스트의 경우, 우리는 그 내부의 개별 함수들을 프로그램적으로 접근하여 테스트할 수 있다. 블랙 박스 테스트도 자동화할 수 있지만, 분명히 훨씬 더 어렵ㄴ다.



소프트웨어 테스트에 적용할 수 있는 접근법 하나를 처음부터 끝까지 살펴보자.


단계1: 블랙박스 테스트를 하고 있는가 아니면 화이트 박스 테스트를 하고 있는가?

테스트의 마지막 단계에 가서야 이런 질문을 던지는 경우도 많지만, 가능한 한 이런 질문을 던지는 쪽을 선호한다. 면접관에게 블랙 박스 테스트를 해야 하는 것인지 아니면 화이트 박스 테스트를 해야 하는지, 아니면 둘 다 해야 하는지 확인하라.


단계2: 누가 사용할 것인가? 왜 사용하는가?

소프트웨어는 보통 한 부류 이상의 사용자를 대상으로 한다. 소프트웨어의 기능은 그 점을 염두에 두고 설계된다. 가령 여러분이 부모가 웹브라우저를 통제할 수 있도록 하는 소프트웨어를 테스트하라는 주문을 받으면, 여러분이 고려해야 할 사용자는 부모(차단을 실행하는 사용자)와 아이들(차단 대상 사용자)이다. 차단 대상도 아니고 차단 실행자도 아닌 '손님'도 사용자 중 하나가 될 수 있다.


단계3: 어떤 유스케이스들이 있나?

앞서 살펴본 차단 소프트웨어의 경우, 부모의 유스케이스는 소프트웨어를 설치하고, 차단 기능을 활성화하고, 차단을 해제하고, 인터넷을 사용하는 등의 행위들로 구성된다. 아이들의 경우에는 불법적인 콘텐츠에 접근하는 경우와 합법적 콘텐츠에 접근하는 경우로 나누어 볼 수 있을 것이다.

여러분이 해야 할 일은 유스케이스를 '마술적으로' 생각해 내는 것이 아니다. 유스케이스는 면접관과 상의하여 도출해야 한다.


단계4: 사용 한계(bounds of use) 지점은?

유스케이스가 모호하므로, 그 의미를 정확히 알아 내야 한다. 웹사이트를 차단한다는 것은 어떤 의미인가? 불법 콘텐츠를 담고 있는 특정한 페이지만 차단한다는 의미인가, 아니면 그 페이지를 포함하는 전체 웹사이트를 차단하여야 한다는 뜻인가? 무엇이 나쁜 콘텐츠인지 프로그램이 스스로 학습하여야 하나, 아니면 화이트 리스트나 블랙 리스트를 사용하여 차단하여야 하나? 어떤 콘텐츠가 부적절한지 학습하여야 한다면, 잘못된 긍정(false positive)이나 잘못된 부정(false negative) 확률은 어느 정도까지 허용할 수 있는가?


단계5: 스트레스 조건과 장애 조건은?

소프트웨어에 장애가 발생하면(그런 일은 불가피하다) 그 장애는 어떤 모습이어야 하는가? 당연하게도, 소프트웨어에 오류가 발생한다고 해서 컴퓨터가 전부 뻗어버리면 곤란하다. 대신, 차단한 사이트에 접속이 가능해진다거나, 차단하지 않은 사이트가 차단된다거나 하는 일이 벌어질 것이다. 후자의 경우가 발생하면, 부모로부터 패스워드를 받아 선택적으로 차단을 푸는 기능이 있어야 하지는 않는지 면접관과 토론해봐야 할 것이다.


단계6: 테스트 케이스는? 테스트 실행은 어떻게?

수동 테스트와 자동화된 테스트가 구별되는 지점이 바로 여기다. 그리고 블랙박스테스트와 화이트박스테스트를 실제로 해보게 되는 것도, 바로 이 단계부터다.

단계3과 단계4에서는 대략적인 유스케이스를 정의하였다. 단계6에서는 이를 좀 더 상세화하고 테스트를 어떻게 수행할 것인지 토론한다. 정확히 어떤 상황을 테스트하고자 하는 것인가? 어떤 단계를 자동화할 수 있나? 사람이 개입해야 하는 부분은 어디인가?

자동화하면 좀 더 강력한 테스트를 할 수 있게 되지만, 중대한 단점도 있음을 기억하라. 수동 테스트 또한 여러분의 테스트 절차에 포함되어야 한다.


이 리스트를 읽어 나갈 때, 그저 생각할 수 있는 아무 시나리오나 찔러보려하지 말라. 조직적이지 않은 방식으로 테스트를 하게 되면 중요한 범주의 테스트를 빼먹는 일이 생긴다. 대신, 여기서 다룬 방식대로 조직적으로 접근하라. 주요 컴포넌트에 따라 테스트를 나누고, 거기서부터 시작하라. 그렇게 하면 보다 완전한 테스트 케이스 목록을 만들어 낼 수 있을 뿐 아니라, 여러분이 구조적이고 질서 있게 움직이는 사람이라는 인상을 줄 수 있다.



함수 테스트

함수 테스트는 가장 쉬운 종류의 테스트다. 보통 입력과 출력을 확인하는 테스트만 하면 되기 때문에, 면접관과도 길게 이야기 할 일이 없을 것이다. 모호한 점도 적다.


하지만 그렇다고 대화의 중요성을 간과해서는 안 된다. 어떤 가정을 하건, 면접관과 그에 관해 토론해야 한다. 특정한 상황을 어떻게 다루어야 하느냐에 관계된 문제라면 더욱 그렇다.


여러분이 sort(int[] array)를 테스트하라는 문제를 받았다고 하자. 이 함수는 정수 배열을 정렬한다. 다음과 같이 진행하면 좋다. 


단계1: 테스트 케이스 정의

일반적으로, 다음과 같은 테스트 케이스들을 생각해 봐야 한다.


- 정상적인 케이스: 전형적인 입력에 대해 정확한 출력을 생성하는가? 여기서 발생할 수 있는 잠재적 문제들에 대해 꼭 생각해 보길 바란다. 가령, 정렬을 하려면 모종의 분할이 필요할 때가 있다. 애렵으로 주어진 배열의 길이가 홀수라면 정확히 반으로 분할되지 않을 것이므로 알고리즘이 정상동작 하지 않을 수도 있다. 따라서 여러분이 고안하는 테스트 케이스는 두 가지 사례가 반드시 포함되어야 한다.

- 극단적인 케이스: 빈 배열을 인자로 넘기면 어떻게 되는가? 아니면 원소 하나로 구성된 아주 작은 배열을 넘긴다면? 아주 큰 배열을 넘기면 어떻게 되는가?

- 널(null), 그리고 잘못된(illegal) 입력: 입력이 잘못 주어졌을 때 코드가 어떻게 동작하는지 생각해 봐야 한다. 가령 n번째 피보나치 수를 생성하는 함수를 테스트해야하는 경우, 테스트 케이스에는 n이 음수인 경우를 테스트하는 코드가 반드시 포함되어야 할 것이다.

- 이상한 입력: 이런 종류의 입력도 때로 주어진다. 이미 정렬된 배열을 입력으로 부면 어떻게 되나? 아니면 아예 역순으로 정렬된 배열이 주어진다면?


이러한 테스트를 만들어 내려면 여러분이 작성하는 함수에 대한 지식이 필요하다. 요구사항이 불명확하다면 우선 면접관에게 물어 필요한 부분을 알아내기 바란다.


단계2: 예상되는 결과를 정의하라

예상 결과는 대체로 명확하게 정의할 수 있다. 위의 경우를 예로 들면, 올바르게 정렬된 배열이 예상 결과이다. 하지만 확인할 사항이 더 있을 수도 있다. 가령 sort 메서드가 반환하는 배열이 정렬된 상태의 새로운 배열이라면, 원래 배열의 내용은 변경되지 않아야 한다는 조건이 만족되었는지 검사할 수도 있을 것이다.


단계3: 테스트 코드를 작성하라

테스트 케이스를 만들고 결과를 정의했다면 테스트 케이스를 코드 형태로 구현하는 것은 간단하다. 아마 다음과 같은 코드가 만들어질 것이다.


public class Testing {
    public void testAddThreeSorted() {
        MyList<integer> list = new MyList<integer>();
        list.addThreeSorted(3, 1, 2);
        assertEquals(list.get(0), 1);
        assertEquals(list.get(1), 2);
        assertEquals(list.get(2), 3);
    }
    
    public class MyList<t> extends ArrayList<t> {
        public void addThreeSorted(T item1, T item2, T item3) {
            this.add(item1);
            this.add(item2);
            this.add(item3);
        }
    }
    
    private boolean assertEquals(Object e1, Object e2) {
        return e1.equals(e2);
    }
}


문제 해결에 관한 문제

이미 있는 문제를 어떻게 디버깅하고 해결할 것인지를 설명하라는 문제도 출제됩니다. 많은 응시자들이 이런 질문은 받으면 망설입니다. "소프트웨어를 재설치하라"는 식의 비현실적인 답안을 내기도 합니다. 이런 질문도 다른 문제와 마찬가지로 구조적으로 접근하여 해결할 수 있습니다.


사례를 통해 살펴보겠습니다. 여러분이 구글 크롬팀에서 일하고 있는데, 이런 버그 리포트를 받았습니다. "실행하자마자 크롬 브라우저가 죽는다." 여러분이라면 어떻게 하겠는가?


브라우저를 재설치하면 이 사용자의 문제가 해결될지도 모릅니다. 하지만 같은 문제에 처할 다른 사용자들에게는 도움이 되지 않을 것입니다. 여러분이 해야할 일은 진짜 문제가 무엇인지 알아내어 개발자가 해결할 수 있도록 돕는 것입니다.


단계1: 시나리오를 이해하라

여러분이 해야할 첫번재 일은 상황을 가능한 한 정확하게 이해할 수 있도록 많은 질문을 던지는 것입니다.


- 사용자는 얼마나 오랫동안 이 문제를 겪었나?

- 브라우저의 버전은? 운영체제 버전은?

- 이 문제가 항상 똑같이 발생하는가? 아니면 얼마나 자주 발생하는가? 언제 그런일이 발생하는가?

- 오류가 발생하면 오류 보고서가 표시되는가?


단계2: 문제 분할

이제 시나리오를 구체적으로 이해했으니, 문제를 테스트 가능한 단위로 분할할 순서입니다. 지금 살펴보는 사례의 경우, 다음과 같은 흐름으로 상황이 전개됨을 상상할 수 있습니다.


1. Windows의 시작메뉴로 간다.

2. 크롬 아이콘을 클릭한다.

3. 브라우저가 시작된다.

4. 브라우저가 설정을 읽어 들인다.

5. 브라우저가 홈페이지에 HTTP 요청을 날린다.

6. 브라우저가 HTTP 응답을 받는다.

7. 브라우저가 웹페이지를 파싱한다.

8. 브라우저가 웹페이지를 화면에 표시한다.


이 과정 가운데 어떤 지점에서 문제가 발생하여 브라우저가 비장상적으로 종료되는 것입니다. 뛰어난 테스터라면 문제를 진단하기 위해 이 시나리오에 포함된 각 항목을 따라가 볼 것입니다.


단계3: 구체적이고도 관리 가능한 테스트들을 생성하라.

방금 살펴본 구성요소들 각각에 대해 현실적인 지시사항을 만들어 내야 합니다. 사용자에게 하라고 지시할 수 있고, 직접 해 볼 수도 있는 지시사항들 말이다(여러분 기계에서 그 절차들을 반복할 수 있도록) 실제로는 여러분이 고객과 상대하게 되는데, 그들에게는 뭘 해달라는 지시사항을 전달할 수 없습니다. 그들은 할 수도 없고, 하려고 하지도 않을 것입니다.