── 표준 단위 테스트 라이브러리로 assert
메서드로 점검한다
assert
메서드가 여럿일 때, 한개라도 실패하면 Test Fail을 출력한다assertEqual(a,b)
, assertTrue(x:bool)
, assertIsNone(x:None)
assertIn(a:list, b)
, assertAlmostEqual(a:round(a-b,7)==0, b)
import unittest
def booksearch():
return {}
class BookSearchTest(unittest.TestCase):
def test_booksearch(self):
self.assertEqual({}, booksearch())
# $ python -m unittest -v file.py
test_result = unittest.main(argv=[''], verbosity=3, exit=False)
# assert len(test_result.result.failures) == 0
test_booksearch (__main__.BookSearchTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
import pathlib
import unittest
from unittest.mock import patch
from urllib.error import URLError
from tempfile import TemporaryDirectory
THUMBNAIL_URL = (
'http://books.google.com/books/content'
'?id=OgtBw76OY5EC&printsec=frontcover'
'&img=1&zoom=1&edge=curl&source=gbs_api'
)
class SaveThumbnailsTest(unittest.TestCase):
def setUp(self):
# 임시 디렉터리 작성
self.tmp = TemporaryDirectory()
def tearDown(self):
# 임시 디렉터리 정리
self.tmp.cleanup()
def test_save_thumbnails(self):
from data.booksearch.core import Book
book = Book({'id': '', 'volumeInfo': {
'imageLinks': {
'thumbnail': THUMBNAIL_URL
}}})
# 처리를 실행하고 파일이 작성되었음을 확인
filename = book.save_thumbnails(self.tmp.name)[0]
self.assertTrue(pathlib.Path(filename).exists())
# 테스트 대상의 save_thumbnail()가 이용할 참조 이름을 지정
@patch('booksearch.core.get_data')
def test_save_thumbnails(self, mock_get_data):
from data.booksearch.core import Book
# 앞에서 얻은 섬네일 이미지 데이터를 목의 반환값으로 설정
data_path = pathlib.Path(__file__).with_name('data')
with open(data_path / 'YkGmfbil6L4C_thumbnail.jpeg', 'rb') as f:
data = f.read()
mock_get_data.return_value = data
book = Book({'id': '', 'volumeInfo': {
'imageLinks': {
'thumbnail': THUMBNAIL_URL
}}})
filename = book.save_thumbnails(self.tmp.name)[0]
# get_data() 호출 시의 인수 확인
mock_get_data.assert_called_with(THUMBNAIL_URL)
# 저장된 데이터 확인
with open(filename, 'rb') as f:
self.assertEqual(data, f.read())
class GetBooksTest(unittest.TestCase):
def test_get_books_no_connection(self):
from data.booksearch.core import get_books
# 임시로 네트워크 접속 단절
with patch('socket.socket.connect') as mock:
# connect()가 호출되면 정확하지 않은 값을 반환함
mock.return_value = None
with self.assertRaisesRegex(URLError, 'urlopen error'):
# 예외가 발생하는 처리를 with 블록 안에서 실행
get_books(q='python')
# $ python -m unittest -v file.py
test_result = unittest.main(argv=[''], verbosity=3, exit=False)
# assert len(test_result.result.failures) == 0
test_booksearch (__main__.BookSearchTest) ... ok test_get_books_no_connection (__main__.GetBooksTest) ... ok test_save_thumbnails (__main__.SaveThumbnailsTest) ... ERROR ====================================================================== ERROR: test_save_thumbnails (__main__.SaveThumbnailsTest) ---------------------------------------------------------------------- Traceback (most recent call last): File "/usr/lib/python3.8/unittest/mock.py", line 1325, in patched return func(*newargs, **newkeywargs) File "/tmp/ipykernel_46845/1025652656.py", line 39, in test_save_thumbnails data_path = pathlib.Path(__file__).with_name('data') NameError: name '__file__' is not defined ---------------------------------------------------------------------- Ran 3 tests in 0.014s FAILED (errors=1)
annotation
을 추가 할 수 있지만, 실질적인 제약은 기능하지 않습니다__annotations__
내장 객체를 사용 합니다.age: int = 25
num: int = 1
name: str = "John Doe"
emails: list = ["john1@doe.com", "john2@doe.com"]
address: dict = {
"street": "54560 Daugherty Brooks Suite 581",
"city": "Stokesmouth",
"state": "NM",
"zip": "80556"
}
__annotations__
{'age': int, 'num': int, 'name': str, 'emails': list, 'address': dict}
def repeat(message: str, times: int = 2) -> list:
return [message] * times
repeat.__annotations__
{'message': str, 'times': int, 'return': list}
── Python 3.5 부터 타입힌트(Type Hint)를 언어 차원에서 지원하고 있습니다.
! pip install mypy
모듈을 사용해야 디버깅 내용을 명확하게 안내 한다python -m mypy --strict example.py
와 같이 외부 모듈을 사용 합니다from typing import List, Set, Dict, Tuple
nums: List[int] = [1]
unique_nums: Set[int] = {6, 7}
vision: Dict[str, float] = {'left': 1.0, 'right': 0.9}
john: Tuple[int, str, List[float]] = (25, "John Doe", [1.0, 0.9])
from typing import Set
chars: Set[str] = {"A", "B", "C"}
__annotations__
{'age': int, 'num': int, 'name': str, 'emails': list, 'address': dict, 'nums': typing.List[int], 'unique_nums': typing.Set[int], 'vision': typing.Dict[str, float], 'john': typing.Tuple[int, str, typing.List[float]], 'chars': typing.Set[str]}
재할당이 불가능한 상수변수에 타입을 추가하는 경우에 활용
from typing import Final
TIME_OUT: Final[int] = 10
여러 개의 타입이 허용될 수 있는 상황에서는 typing 모듈의 Union을 사용할 수 있습니다
from typing import Union
def toString(num: Union[int, float]) -> str:
return str(num)
toString(1), toString(1.5), toString('python')
('1', '1.5', 'python')
None
이 허용되는 함수의 매개 변수에 대한 타입을 명시할 때 유용합니다.
from typing import Optional
def repeat(message: str, times: Optional[int] = None) -> list:
if times:
return [message] * times
else:
return [message]
함수에 대한 타입 어노테이션을 추가할 때 사용합니다.
repeat
함수는 첫 번째 매개 변수 greet
인자를 갖는다.Callable[[str], str]
를 추가 한다.str
타입의 인자를 하나 받고, 결과값은 str
로 반환하는 함수가 된다.from typing import Callable
greet: Callable[[str], str] = lambda name: f"Hi, {name}!"
def repeat(
greet: Callable[[str], str],
name: str,
times: int = 2) -> None:
for _ in range(times):
print(greet(name))
repeat(greet, "Dale")
Hi, Dale! Hi, Dale!
타입을 추상적으로 명시
해주는 것이 유리한 경우가 많습니다.from typing import Iterable, List
def toStrings(nums: Iterable[int]) -> List[str]:
return [str(x) for x in nums]
# 140.227.238.217:3128 JP Japan elite proxy no no 12 mins ago
# 140.227.201.6:3128 JP Japan anonymous no yes 12 mins ago
url = 'https://javgg.net'
proxy = 'socks5://92.42.109.187:1080'
proxies = {"http": proxy, 'https': proxy}
headers = {
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36",
"content-type": "application/json",
}
import requests
# response = requests.get(url, headers=headers, proxies=proxies, timeout=10)
# response