WeniVooks

검색

견고한 파이썬

테스팅과 디버깅

1. 테스팅과 디버깅

프로그래밍에서 테스팅과 디버깅은 코드의 품질을 보장하고, 오류를 찾아내어 수정하는 핵심 과정입니다. 테스팅은 코드가 예상대로 작동하는지 검증하는 반면, 디버깅은 코드에서 버그를 찾고 수정하는 과정입니다.

1.1 테스팅 (Testing)

테스팅은 소프트웨어 개발의 중요한 부분으로, 코드가 정확하게 작동하는지 확인하는 과정입니다.

  1. 단위 테스팅 (Unit Testing): 이는 코드의 가장 작은 단위인 함수나 메소드를 개별적으로 검증하는 테스트입니다. 파이썬에서는 unittest, pytest와 같은 테스팅 프레임워크를 사용해 이러한 테스트를 손쉽게 구현할 수 있습니다.
  2. 통합 테스팅 (Integration Testing): 여러 컴포넌트 또는 시스템의 일부분이 서로 올바르게 작동하는지 확인하는 테스팅입니다. 이는 단위 테스팅에서 발견되지 않은 오류를 잡아내는 데 중요합니다.
  3. 기능 테스팅 (Functional Testing): 이는 실제 사용자 시나리오를 기반으로 전체 애플리케이션이 예상대로 작동하는지 평가하는 테스트입니다.

테스팅은 버그를 조기에 발견하고 수정함으로써 소프트웨어의 신뢰성을 보장하는 데 핵심적인 역할을 합니다. 특히 소프트웨어를 수정하거나 확장할 때 기존의 기능들이 여전히 올바르게 작동하는지 확인하는 데에도 유용합니다.

1.2 디버깅 (Debugging)

디버깅은 프로그램에서 버그나 오류를 찾아내고 수정하는 과정입니다. 이는 코드의 오류를 효과적으로 진단하고 해결하는 데 필요한 기술입니다.

  1. 브레이크포인트 (Breakpoints): 코드 실행을 특정 지점에서 일시 중지하고 해당 시점에서의 변수 값과 프로그램 상태를 검사합니다. 이를 통해 문제의 원인을 좁혀 나갈 수 있습니다.
  2. 로그 출력 (Logging): 코드의 다양한 지점에 로그를 출력하여 프로그램의 실행 흐름을 모니터링하고, 오류가 발생하는 시점을 파악합니다.
  3. 스택 트레이스 분석 (Stack Trace Analysis): 스택 트레이스는 프로그램이 오류를 발생시킬 때의 호출 스택을 나타냅니다. 이는 오류의 위치와 원인을 추적하는 데 유용한 정보를 제공합니다. 스택 트레이스는 프로그램이 예외를 던지거나 오류가 발생할 때 기록되는 프로그램의 호출 스택입니다. 이는 함수 호출의 순서와 계층 구조를 나타내기에 발생한 위치와 그 원인을 파악하는데 도움이 됩니다.

2. 단위 테스트와 테스트 주도 개발

unittest — Unit testing framework

단위 테스트(unit test)는 개별 함수나 메서드와 같은 코드의 가장 작은 단위가 예상대로 동작하는지 검증하는 테스트입니다. Python에서는 unittest 모듈을 이용해 단위 테스트를 작성하고 실행할 수 있습니다.

표준라이브러리는 아니지만 nose2, pytest와 같은 패키지를 사용하기도 합니다. 함께 사용하는 모듈로는 coverage(보고서 형태)가 있습니다.

해당 코드는 colab에서는 작동하지 않습니다. test.py로 만들어 실습하세요.

import unittest def add(x, y): return x + y class TestAdd(unittest.TestCase): def test_add(self): self.assertEqual(add(1, 2), 3) if __name__ == '__main__': unittest.main()

테스트 주도 개발(Test-Driven Development, TDD)은 테스트를 먼저 작성하고 그 테스트를 통과하도록 코드를 구현하는 개발 방법론입니다. 이를 통해 코드의 품질을 향상시키고, 버그를 줄일 수 있습니다.

다음은 애러가 없을 경우 출력하는 화면입니다.

다음은 애러가 있을 경우 출력하는 화면입니다. 애러가 여러개 도출이 되었어도 애러는 1개만 출력합니다.

import unittest def add(x, y): return x + y class TestAdd(unittest.TestCase): def test_add(self): self.assertEqual(add(1, 2), 3) self.assertEqual(add(10, 2), 13) self.assertEqual(add(10, 20), 31) if __name__ == '__main__': unittest.main()

아래와 같은 다양한 메서드를 제공합니다.

self.assertEqual(1 + 2, 3)
self.assertTrue(10 == 10)
self.assertFalse(1 == 10)
self.assertGreater(10, 1)
self.assertLess(1, 10)
self.assertIn(1, [1, 2, 3, 4, 5])
self.assertIsInstance('a', str)
self.assertEqual(1 + 2, 3)
self.assertTrue(10 == 10)
self.assertFalse(1 == 10)
self.assertGreater(10, 1)
self.assertLess(1, 10)
self.assertIn(1, [1, 2, 3, 4, 5])
self.assertIsInstance('a', str)

아래와 같이 여러가지 함수를 만들어 작동시켜볼 수 있습니다. 메서드 이름은 꼭 test로 시작해야 합니다. 함수 이름 순으로 실행합니다. 구현순서와는 상관 없습니다.

import unittest def add(x, y): return x + y def sub(x, y): return x - y class TestAdd(unittest.TestCase): def test_add(self): self.assertEqual(add(1, 2), 3) def test_sub(self): self.assertEqual(sub(10, 2), 8) if __name__ == '__main__': unittest.main()

테스트 주도 개발(TDD, Test-Driven Development)은 소프트웨어에 개발방법 중 하나입니다. 테스트를 작성하는데 시간이 걸리지만 반면 적어도 테스트를 통과한 코드를 만들었기 때문에 어느정도의 품질을 보장할 수 있습니다.

  1. 명세 기준으로 테스트 케이스 정의
  2. 테스트 케이스를 통과할 수 있는 코드 작성
  3. 통과하면 새로은 기능 추가

이렇게 개발 할 경우 개발자는 요구사항 명세에 대해 보다 잘 이해할 수 있습니다. Django와 같은 프레임워크에는 이러한 TDD가 가능하도록 테스트할 수 있는 코드가 기본으로 내장되어 있습니다.

3. 디버깅 기법과 도구 활용

pdb — 파이썬 디버거 — Python 3.7.17 문서 pdb — 파이썬 디버거 — Python 3.7.17 문서

디버깅은 프로그램에서 오류를 찾아내고 그 원인을 알아내어 수정하는 작업을 말합니다. Python에서는 pdb 모듈을 이용하여 디버깅을 할 수 있습니다. 또한, 대부분의 통합개발환경(IDE)들은 디버깅 도구를 제공합니다.

import pdb
 
def add_to_ten(num):
    result = num + 10
    pdb.set_trace()  # 디버거를 실행합니다. break 포인트입니다.
    return result
 
add_to_ten(5)
import pdb
 
def add_to_ten(num):
    result = num + 10
    pdb.set_trace()  # 디버거를 실행합니다. break 포인트입니다.
    return result
 
add_to_ten(5)
  • h : 도움말
  • n : 현재 라인 실행 후 다음 라인으로 넘어갑니다.
  • s : 현재 라인 실행 후 다음 스탭을 진행합니다.
  • c : break point가 있을 때까지 계속 실행합니다.
  • q : 중단합니다.
  • p 변수명 : 변수의 값을 출력합니다.
  • pp 변수명 : 변수의 값을 보기좋게 출력합니다.
  • l : 현재 라인을 포함한 주변 코드를 보여줍니다.
  • ll : 현재 함수 전체를 보여줍니다.
  • locals() : 현재 함수의 로컬 변수를 출력합니다.
import pdb
 
def add(a, b):
    return a + b
 
def test():
    for i in range(10):
        x = add(i, 10)
        pdb.set_trace()
    for i in range(10):
        y = add(i, 100)
        pdb.set_trace()
 
test()
import pdb
 
def add(a, b):
    return a + b
 
def test():
    for i in range(10):
        x = add(i, 10)
        pdb.set_trace()
    for i in range(10):
        y = add(i, 100)
        pdb.set_trace()
 
test()

Python 3.7 버전에서는 breakpoint()로 간단하게 디버깅 할 수 있습니다.

def add(a, b):
    return a + b
 
def test():
    for i in range(10):
        x = add(i, 10)
        breakpoint()
    for i in range(10):
        y = add(i, 100)
        breakpoint()
 
test()
def add(a, b):
    return a + b
 
def test():
    for i in range(10):
        x = add(i, 10)
        breakpoint()
    for i in range(10):
        y = add(i, 100)
        breakpoint()
 
test()

colab이 아니라 .py 파일을 만들어 아래와 같이 실행하면 다음과 같은 결과를 얻을 수 있습니다.

4. 코드 품질 개선을 위한 정적 분석

정적 분석은 코드 품질을 개선하고, 유지보수를 용이하게 만드는 중요한 과정입니다. 이는 소프트웨어 개발 과정에서 다양한 테스팅 방법과 함께 사용되어 효율성을 높입니다.

4.1 소프트웨어 테스팅의 범위
  • 기능 테스팅 (Functional Testing): 애플리케이션의 기능이 요구사항에 맞게 동작하는지 검증합니다.
    • 화이트 박스 테스팅: 코드의 내부 구조를 검사하며, 개별 기능, 메서드, 클래스, 모듈을 테스트합니다. 이 과정에서 정적 분석이 큰 역할을 합니다.
    • 블랙 박스 테스팅: 코드 내부에 대한 지식 없이, 애플리케이션의 기능만을 테스트합니다. 예를 들어, 웹 애플리케이션에서는 Selenium과 같은 도구가 사용됩니다.
  • 성능 테스팅: 애플리케이션의 성능, 예를 들어 부하 테스팅이나 스트레스 테스팅을 포함합니다.
  • 보안 테스팅, 사용성 테스팅, 설치 테스팅, 접근성 테스팅: 각각 보안, 사용자 경험, 설치 과정, 접근성을 검증합니다.
4.2 정적 분석의 중요성 및 방법

정적 분석(static analysis)은 프로그램을 실행하지 않고 코드를 분석하여 버그, 코드 스멜(code smell), 안티 패턴 등을 찾아내는 방법입니다. Python에서는 black formatter, pylint, flake8, Pyflakes등의 도구를 이용하여 정적 분석을 수행할 수 있습니다.

  • 표준 준수: PEP 8과 같은 코딩 표준을 준수하는지 확인합니다.
  • 코드 품질 검사: 구문 오류, 들여쓰기, 로직 오류, 코드 스멜(code smells) 등을 검사합니다. 이를 통해 코드의 가독성과 유지보수성을 향상시킵니다.
  • 로직이나 나쁜 냄새(code smells): 과도한 분기, 루프, 파라미터 등으로 인한 코드의 복잡성을 평가합니다. 복잡한 코드는 버그 발생 위험이 높고, 테스트하기 어렵습니다. 코드의 나쁜 냄세검색으로 좀 더 많은 케이스를 확인할 수 있습니다.
pip install pylint
 
pylint test.py
pip install pylint
 
pylint test.py
def 구구단(): for i in range(2, 10): for j in range(1, 10): print('{} * {} = {}'.format(i, j, i*j)) 구구단()

위와 같이 pylint를 이용하여 test.py 파일을 정적 분석할 수 있습니다. 분석 결과를 통해 코드의 품질을 개선하는 데 도움을 받을 수 있습니다.

  • R (Refactor): 리팩토링이 필요한 코드
  • C (Convention): 코딩 컨벤션 위반
  • W (Warning): 경고, 잠재적 문제
  • E (Error): 명백한 오류
  • F (Fatal): 치명적인 오류, 분석을 방해하는 심각한 문제

VSC를 사용한다면 Black formatter를 권합니다. 저장을 하는 순간 표준에 맞게 코드를 강제로 변경합니다. 처음 보는 사람과 협업을 하거나, 기간이 짧은 프로젝트를 할 때 사용이 용이합니다. 저희 회사는 Black formatter를 사용하고 있습니다.

{"packages":["numpy","pandas","matplotlib","lxml"]}
14.6 비동기와 코루틴14.8 정규표현식