기타/공부노트

[파이썬] 디버깅을 위한 마음자세와 두가지 프로그램 테스트 방법

코드아키택트 2021. 1. 30. 20:51
반응형

 프로그래밍을 하며 가장 어려운 것은 내가 만든 코드가 수많은 시나리오에서 작동하도록 하는 것이다. 오늘은  수업을 들으며 이런 디버깅을 조금더 쉽게 할 수 있게 하는 자세와 두가지 프로그램 테스트 방법에 대해 정리하려고 한다.

 

 

 

 버그의 기원

첫 버그는 진짜 벌레였다.

 누가 용어를 먼저 사용했는지는 어디서나 큰 논쟁거리다. 컴퓨터 업계에서도 버그라는 말은 초창기부터 쓰여왔다고는 한다. 다만 그것을 공식적으로 기록한 것은 위의 사례라고 한다. 컴퓨터 과학자인 "Grace hopper"(이름이 꼭 메뚜기같다) 동료와 컴퓨터 에러를 확인하려다가, 컴퓨터를 열어보니 실제 벌레가 있었다고 한다. 그래서 이를 노트에 기록하였고, 최초로 버그가 발견된 사례라고 일컫어진다.

 

 

 

디버깅을 위한 프로그래밍 : Defensive Programming

 오늘도 Wikipedia의 정의를 빌린다

 

Defensive programming is a form of defensive design intended to ensure the continuing function of a piece of 
software under unforeseen circumstances.

방어적인 프로그래밍(Defensive programming)은 방어적인 디자인의 한 형태로, 하나의 소프트웨어가 뜻밖의 상황에서도 작동하는것을 보장하도록 의도한다.

 

 쉽게 말해 하나의 소프트웨어가 어느 상황에서도 돌아가도록 하도록 하는 것이다. 총으로 치자면 어떤 상황에서도 총알이 발사되도록 하는 것이다. 단순히 그런면만 본다면 Defensive programming이 가장 잘된 사례는 AK-47인건가.

 어느상황에서 돌아가도록 하기 위해선 코드를 '잘 짜면'된다. 하지만 '잘 짠다'라는 말은 말이야 쉽지 냅다 한다고 되는 것은 아닌거 같다. 수업에서 제시하는 세가지 방법을 제시한다.

  1. 기술명세서(Specification)을 쓴다.
  2. 프로그램을 모듈화 한다
  3. 인풋과 아웃풋의 상태를 확인한다(assertion)

 

기술명세서를 쓴다?

 해석을 하다보면 말이 너무 전문적이거나 어렵게 들릴때가 많다. 이건 쉽게 말하면 def(기능)의 첫줄에 기대되는 input, output을 잘 쓰라는 뜻이다.

 

""" ~~~ """ 부분이 Specification에 해당한다.

 위의 문장을 보면 이 기능이 어떤 입력값을 받아 어떤 결과값을 만들어내는지 한문장에 알 수 있다. 물론 안에 돌아가는 내용은 한눈에 볼 수는 없겠지만... Specification을 잘 작성하는 것은 곧 이어질 Black box테스트에서도 매우 중요한 부분이다. 프로그래밍을 하다보면 논리를 짜는거 보다 어려운 것이 기능의 이름을 만드는 것과, 주석을 쓰는것이기도 한데 힘든만큼 매우 중요했다. 왜냐면 필연적으로 에러가 났으니까.

 

 

프로그램을 모듈화 한다.

 이 모듈이라는 말과 OOP(Object Oriented Programming)에서 매우 중요하다고 들었다. 간략히 얘기하자면 도시학자들이 도시를 돌아다니며 여러가지 공통점을 찾아 <패턴 랭귀지>라는 책을 냈다고한다. 도시의 각 요소들을 모듈로 정리해 놓은 책인것 같다. 아이러니하게 건축과 5년동안 한번도 들어본적없는데, 이게 프로그래밍에게는 지대한 영향을 끼쳤다니 아이러니하다. 모듈화 한다는 다음과 같이 비유할 수 있다. 나는 건축과 출신이다 보니 집이 만들어지는 과정으로 해야겠다.

 

모듈화 하지 않은 공정 모듈화 한 공정
건물을 만든다 기초를 만든다
기둥을  세운다
바닥을 만든다
외피를 만든다

 

 예를들어 집을 만든다고할때 그 과정은 매우 복잡하다. 기초도 세워야 하고 기둥도 세워야하고, 전기도 깔고 통신도 깔고 다양한 작업들이 수반된다. 이를 잘 수행하기 위해서는 각 공정을 분리하고 그 안에서 또는 그 사이에서 생기는 문제들을 잘 처리해야한다. 만약 분리하지 않고 제대로 관리하지 못한다면 '건물에 문제가 생겼다'라는 건 알지만 '어느 공정에 문제가 생겼다'를 찾느라 하루 종일 헤매야 할지도 모른다. 위를 코드로 표현한다면 아래와 같다.

 

모듈화 하지 않은 코드 모듈화 한 코드
Line of code 
Line of code
Line of code
.
.
.
Line of code
def build_foundation(input)
def erect_column(input)
def build_floor(input)
def build_envelop(input)

 

 위와같이 나뉘어져 있으면 코드 한줄한줄을 찾아가서 문제를 해결하는 것이 아니라, 문제가 있는 기능만 해결함으로서 비교적 손쉽게 해결할 수 있다.

 

 

인풋과 아웃풋의 상태를 확인한다

 컴퓨터 프로그램들은 모두 전제조건을 가지고 있다. 영상 편집을 하려면 영상을 인풋으로 넣어야하고, 사진을 편집하려면 사진을 넣어야 하고, 회원가입을 하려면 개인정보를 넣어야한다. 이렇게 각 기능엔 필요로 하는 인풋값이 들어가야하는데, 실수로 잘못된 값을 넣는 경우가 많다. 또한 계산상의 오류로 잘못된 아웃풋 값이 나올때도 있다. 이런 문제들을 방지하는 방법은 여러가지가 있지만, assert에 대해 간략히 짚고 다음 글에서 Exception에 대해서 좀더 써보도록 하겠다.

 

 Assertion은 본인이 가정한 input 또는 output을 강제하는 방법이라고 한다. 만약 조건에 맞지 않는 값이 들어왔을 경우 AssertionError를 띄우게 된다. 예시코드는 다음과 같다.

 

def avg(grades):
    assert len(grades) != 0, 'no grades data'
    return sum(grades)/len(grades)

L=[1,2,3,4,5,6,7,8,9,10]

print(avg(L))

M = []
print(avg(M))

 

  위의 코드는  List를 받으면 그에 따른 평균값을 내보내는 코드이다. 여기서 문제가 되는 경우는 리스트에 아무것도 없을 때이다. 여러 방법이 있겠지만, 예제에선 그런 경우자체를 막아버리는 것으로 문제를 해결했다. 위의 코드를 실행하면 다음과 같은 결과가 나온다.

 

5.5
Traceback (most recent call last):
  File "/home/heejinchae/Programming/PracticeSpace/defensiveProgramming.py", line 10, in <module>
    print(avg(M))
  File "/home/heejinchae/Programming/PracticeSpace/defensiveProgramming.py", line 2, in avg
    assert len(grades) != 0, 'no grades data'
AssertionError: no grades data

 

 첫줄엔 1~10까지의 평균인 5.5가 제대로 나왔다. 하지만 두번째에 대해선, AssertionError에 해당하는 경우기 때문에 에러와 에러메세지를 띄운것을 볼 수 있다.

 

 

 

테스트는 언제 시작해야하는가?

 프로그래밍을 하다보면 항상 에러의 연속이다. 그런면에서 보면 계속 프로그램을 테스트한다고 봐도 되지만, 수업에서 정확히 테스트만 집중해서 할때가 언제가 되어야 하는지를 말하고 싶은 것 같다. 수업에서 제시하는 두가지 조건은 다음과 같다

  1. 코드가 정말 돌아갈때
    1. Syntax error가 없을때
    2. static semantic error가 없을때
  2. 기대하는 결과값이 있을때
    1. 특정 인풋값과 그에 기대되는 아웃풋값이 있을 때

 항상 모르는 단어는 짚고 넘어가야하니 오늘도 조잘조잘 해봐야겠다. Syntax error는 내가 너무많이 봐서 잘 알고 있다. 문법을 잘못쓰면 나온다. 예를들면 print("Hello")를 써야하는데 pirnt("Hello")라고 쓰면 자주 볼 수 있다.

 Static semantic error가 무엇인지 잠시 찾아봤다. Syntax error가 오타라고 한다면, Static semantic error는 Syntax error는 아니지만 코드를 이상하게 사용할때 발생한다고 한다. 파이썬에선 데이터의 타입을 선언하는 일이 드물기 때문에 흔치 않지만, 자바의 예로 float에 int를 할당하거나 할때 생기는 에러와 같은 것이라고 한다.

 

 

 

테스트의 종류

테스트의 종류는 다음과 같은 세가지로 분류하고 있다. 

  • 개별 테스트(Unit test)
    • 각 기능이 문제없이 돌아가는지
  • 반복 테스트(Regression test)
    • 문제가 발생하면 없앨때 까지 계속 테스트
  • 통합테스트(Integration test)
    • 전체적인 프로그램이 돌아가는지 테스트

 

 나의 코딩생활을 돌아보면 마지막 통합테스트를 재빠르게 돌리고 싶어 안달났던 기억이 난다. 그건 프로그램들을 모듈화 하지 못했기 때문에, 개별 테스트를 거의 못해봤기 때문이 아닌가 싶다. 위의 세가지는 굳이 프로그래밍이 아니더라도 모든 분야에서 통용될 수 있는 부분인 것 같다.

 

 

두가지 테스트 방법 : 블랙박스와 글래스박스 테스트

 오늘의 주제인 블랙박스와 글래스박스 테스트에 대한 이야기로 왔다. 

 

 

블랙박스 테스트

Black-box testing is a method of software testing that examines the functionality of an application without peering into its internal structures or workings.

블랙박스 테스팅이란 소프트웨어 테스팅의 한 방법으로 하나의 기능을 내부 구조를 보지 않고 테스트하는 방법이다.

 

 위의 정의를 봤을때 가장 떠오르는 것은 다음과 같은 그림이었다.

 

알고리즘에대한 설명이지만 개념상 맞는 부분이 많다.

 

 좀더 구체적으로 설명하면 블랙박스 테스트는 기능(def name())에 써있는 Specification만 보고 테스트를 진행하는 것이다.

 

제곱근 계산

 

 가령 위와같은 코드가 있을 수 있다. 보통 맨 윗줄에 Specification이 써있다. 설명을 보면 x,와 eps가 float(실수)일때 x-eps <= res*res <= x+eps를 만족하는 res(제곱근의 줄임말인듯)을 return하라 라고 되어있다. 이때 블랙박스 테스트를 하는 방법은 여러가지 테스트 케이스를 정의한 후, 기능이 제대로 돌아가는지만 확인을 하는 것이다.

 

여러가지 경우의 수를 볼 수 있다.

 

 위에서 케이스는 총 5가지인 것을 볼 수 있다. 여기서 볼 수 있는 것은 Specification에서 요구하는 조건 외에는 넣지 않는다는 것이다. 일종의 외줄타기와 같은 느낌인데, 필요로 하는 요구조건을 넣었을때 올바르게 값을 내보내는지만 테스트 한다고 보면 되겠다. 어떻게 보면 효율적이긴 하지만, 현실적으로 실수 등으로 잘못된 값이 들어갈때가 있는것을 생각하면 단점도 분명한 방법이다.

 

 

글래스박스 테스트

White-box testing (also known as clear box testing, glass box testing, transparent box testing, and structural testing) is a method of software testing that tests internal structures or workings of an application, as opposed to its functionality (i.e. black-box testing)

화이트 박스 테스팅(글래스 박스 테스팅 등으로 알려진)은 기능성과는 반대로 내부 구조 또는 애플리케이션이 작동하는지를 테스트하는 소프트웨어 테스팅이다.

 

 정의에 많은 단서가 담겨있다. 주목할 만한 표현은 as opposed to its functionality이다. 이건 내용을 복습하다보니 알게 되었다. 앞에서 블랙 박스 테스트 에서는 인풋에 대한 정확한 결과 값이 나와야했다. 가령 우리가 25의 제곱근을 알고 싶어서 코드를 돌렸다면 5가 나와야 한다. 기능성과 반대라는 뜻은 우리가 25를 넣었는데 3이 나왔어도 글래스박스 테스트는 통과했다는 뜻이 된다. 

 또한 글래스박스 테스트의 목적은 모든 경로(paths)들을 테스트 해보는데 있다. 즉 코드내에 있는 모든 if,else,for,while 등의 경우를 모두 탐색하는데 있다. 모든 분기점을 최소한 한번 테스트한 경우 path-complete라고 한다.

 

 코드에서 이상함을 감지해야한다.

 

 위의 기능은 어떤 값에 대해서 절대값을 내보내주는 기능을 한다. 여기서는 글래스박스의 관점에서만 위의 기능을 평가한다. path-complete를 하기 위해서 우리에게 필요한 값은 2와 -2가 있다. 그리고 두 값을 넣는다면 둘다 2를 return하게 되므로 코드는 글래스박스 관점에서 잘 작동한다고 할 수 있다. 하지만 -1을 넣으면 위는 잘못되었다는 것을 알 수 있다. 왜냐하면 -1이 나올 것이기 때문이다. 이와 같이 화이트박스 테스트는 모든 분기점에서 error가 안나는지 여부는 확인 할 수 있겠지만 원하는 기능을 제대로 하는지 파악하기 어려운 단점이 있다.

 

 

 

끝맺으며

 코딩을 깔짝거려왔고, 작년 7월부터 제대로 배우기 시작했다. Java를 배우면서 구조화된 프로그램을 배운 덕에 위에서 언급한 모듈화 등의 개념을 조금 더 쉽게 이해할 수 있었다. 그리고 위의 내용들도 OOP를 잘 활용했을 때 장점의 연장선으로 이해할 수 있을 것 같다. 블랙박스 테스트와 글래스박스라는 개념을 통해 좀더 체계적으로 디버깅할 수 있을것 같고, 앞으로 유지보수를 위한 프로그래밍에 도움이 많이 될 것 같다. 다음엔 수업의 나머지 부분인 Exception부분을 정리해야겠다.

 

 

 

참고자료


ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-0001-introduction-to-computer-science-and-programming-in-python-fall-2016/lecture-videos/lecture-7-testing-debugging-exceptions-and-assertions/

www.cs.cornell.edu/courses/cs3110/2019sp/textbook/testing/glassbox.html

en.wikipedia.org/wiki/White-box_testing

Semantic Errors in Java

스택오버플로우:Static Semantics meaning?

www.globalapptesting.com/blog/the-worlds-first-computer-bug-global-app-testing

en.wikipedia.org/wiki/Defensive_programming

반응형