14. Testing, Debugging, and Exceptions

14.1. Testing Output Sent to Stdout

P : 표준 출력에 출력되는지를 테스트하고 싶다.

S : unittest.mock() 모듈의 patch()를 이용한다.

from io import StringIO
from unittest import TestCase
from unittest.mock import patch
import mymodule

class TestURLPrint(TestCase):
    def test_url_gets_to_stdout(self):
        protocol = 'http'
        host = 'www'
        domain = 'example.com'
        expected_url = '{}://{}.{}\n'.format(protocol, host, domain)

        with patch('sys.stdout', new=StringIO()) as fake_out:
            mymodule.urlprint(protocol, host, domain)
            self.assertEqual(fake_out.getvalue(), expected_url)

14.2. Patching Objects in Unit Tests

P : 단위 테스트에 특정 오브젝트를 대입하고 싶다.

S : unittest.mock() 모듈의 patch()를 이용한다.

from unittest.mock import patch
import example

@patch('example.func')
def test1(x, mock_func):
    example.func(x)       # Uses patched example.func
    mock_func.assert_called_with(x)

14.3. Testing for Exceptional Conditions in Unit Tests

P : 단위 테스트에서 예외 조건을 테스트하고 싶다.

S : assertRaises()를 이용한다.

import unittest

# A simple function to illustrate
def parse_int(s):
    return int(s)

class TestConversion(unittest.TestCase):
    def test_bad_int(self):
        self.assertRaises(ValueError, parse_int, 'N/A')

14.4. Logging Test Output to a File

P : 테스트 출력을 파일에 로깅하고 싶다.

S : 다음의 방식을 사용한다.

import sys
def main(out=sys.stderr, verbosity=2):
    loader = unittest.TestLoader()
    suite = loader.loadTestsFromModule(sys.modules[__name__])
    unittest.TextTestRunner(out,verbosity=verbosity).run(suite)

if __name__ == '__main__':
    with open('testing.out', 'w') as f:
        main(f)

14.5. Skipping or Anticipating Test Failures

P : 실패할 테스트를 스킵하고 싶다.

S : unittest 모듈의 기능을 이용한다.

import unittest
import os
import platform

class Tests(unittest.TestCase):
    def test_0(self):
        self.assertTrue(True)

    @unittest.skip('skipped test')
    def test_1(self):
        self.fail('should have failed!')

    @unittest.skipIf(os.name=='posix', 'Not supported on Unix')
    def test_2(self):
        import winreg

    @unittest.skipUnless(platform.system() == 'Darwin', 'Mac specific test')
    def test_3(self):
        self.assertTrue(True)

    @unittest.expectedFailure
    def test_4(self):
        self.assertEqual(2+2, 5)

if __name__ == '__main__':
    unittest.main()

14.6. Handling Multiple Exceptions

P : 복수의 예외를 처리하고 싶다.

S : 튜플로 그룹핑한다.

try:
    client_obj.get_url(url)
except (URLError, ValueError):
    client_obj.remove_url(url)
except SocketTimeout:
    client_obj.handle_url_timeout(url)

14.7. Catching All Exceptions

P : 모든 예외를 처리하고 싶다.

S : 다음과 같이 처리한다.

try:
   ...
except Exception as e:
   ...
   log('Reason:', e)       # Important!

14.8. Creating Custom Exceptions

P : 사용자 정의 예외를 만들고 싶다.

S : Exception을 상속받는 class로 만든다.

class NetworkError(Exception):
    pass

class HostnameError(NetworkError):
    pass

class TimeoutError(NetworkError):
    pass

class ProtocolError(NetworkError):
    pass

14.9. Raising an Exception in Response to Another Exception

P : 다른 예외를 받았을 때 예외를 발생시키고 싶다.

S : raise from을 이용한다.

>>> def example():
...     try:
...             int('N/A')
...     except ValueError as e:
...             raise RuntimeError('A parsing error occurred') from e
...
>>> example()
Traceback (most recent call last):
  File "<stdin>", line 3, in example
ValueError: invalid literal for int() with base 10: 'N/A'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in example
RuntimeError: A parsing error occurred
>>>

14.10. Reraising the Last Exception

P : 잡은 예외를 재발생시키고 싶다.

S : raise를 쓴다.

>>> def example():
...     try:
...             int('N/A')
...     except ValueError:
...             print("Didn't work")
...             raise
...
>>> example()
Didn't work
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in example
ValueError: invalid literal for int() with base 10: 'N/A'
>>>

14.11. Issuing Warning Messages

P : 경고 메시지를 출력하고 싶다.

S : warnings.warn()을 사용한다.

import warnings

def func(x, y, logfile=None, debug=False):
    if logfile is not None:
         warnings.warn('logfile argument deprecated', DeprecationWarning)
    ...

14.12. Debugging Basic Program Crashes

P : 프로그램 크래시를 디버깅하고 싶다.

S : python -i 옵션을 쓴다.

bash % python3 -i sample.py
Traceback (most recent call last):
  File "sample.py", line 6, in <module>
    func('Hello')
  File "sample.py", line 4, in func
    return n + 10
TypeError: Can't convert 'int' object to str implicitly
>>> func(10)
20
>>>

14.13. Profiling and Timing Your Program

P : 프로그램을 프로파일링하고 싶다.

S : Unix의 time 명령어나, cProfile 모듈을 쓴다.

bash % python3 -m cProfile someprogram.py
         859647 function calls in 16.016 CPU seconds

   Ordered by: standard name


   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   263169    0.080    0.000    0.080    0.000 someprogram.py:16(frange)
      513    0.001    0.000    0.002    0.000 someprogram.py:30(generate_mandel)
   262656    0.194    0.000   15.295    0.000 someprogram.py:32(<genexpr>)
        1    0.036    0.036   16.077   16.077 someprogram.py:4(<module>)
   262144   15.021    0.000   15.021    0.000 someprogram.py:4(in_mandelbrot)
        1    0.000    0.000    0.000    0.000 os.py:746(urandom)
        1    0.000    0.000    0.000    0.000 png.py:1056(_readable)
        1    0.000    0.000    0.000    0.000 png.py:1073(Reader)
        1    0.227    0.227    0.438    0.438 png.py:163(<module>)
      512    0.010    0.000    0.010    0.000 png.py:200(group)
    ...
bash %

14.14. Making Your Programs Run Faster

P : 프로그램을 더 빠르게 하고 싶다.

S : 여러 가지 방법이 있다.

첫째로는 함수를 쓴다.

# somescript.py
import sys
import csv

def main(filename):
    with open(filename) as f:
         for row in csv.reader(f):
             # Some kind of processing
             ...

main(sys.argv[1])

둘째로는 attribute 접근을 최소화한다.

from math import sqrt

def compute_roots(nums):
    result = []
    result_append = result.append
    for n in nums:
        result_append(sqrt(n))
    return result

셋째로는 지역 변수를 사용한다.

import math

def compute_roots(nums):
    sqrt = math.sqrt
    result = []
    result_append = result.append
    for n in nums:
        result_append(sqrt(n))
    return result

넷째로는 불필요한 추상화를 줄인다. 아래처럼 짜지 말라.

class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    @property
    def y(self):
        return self._y
    @y.setter
    def y(self, value):
        self._y = value

다섯째로는 빌트인 컨테이너를 쓴다.

여섯째로는 불필요한 자료구조나 카피를 만들지 않는다. 이렇게 하지 말라.

values = [x for x in sequence]
squares = [x*x for x in values]

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중