don't stop believing

blockchain 테스트에 사용한 python 함수 - callRPC 본문

Python/Basic

blockchain 테스트에 사용한 python 함수 - callRPC

Tongchun 2018. 12. 20. 12:06

blockchain 테스트에 사용한 python 함수를 설명합니다.

  • getDateStrings - 날짜 - 년, 월, 일, 시, 분, 초를 확인하는 함수입니다.
  • getLogfileNameWithPath - 로그 기록을 위해 폴더를 생성하고 경로를 반환합니다.
  • getRequestId - ramdom으로 숫자를 반환합니다.
  • moveFile - 특정 파일을 로그 폴더로 이동시킵니다.
  • saveResultData - 데이터를 파일에 저장합니다.
  • convertToHex - String을 hex로 변환합니다.
  • leftPad64 - 왼쪽에 (또는 오른쪽에) 0을 채워 고정된 길이로 만듭니다.
  • getTestConfig - json 형태의 config 파일을 만들고 불러옵니다.
  • waitingCount - time.sleep() 할 때 동적으로 카운트를 셉니다.
  • callRPC - requests 모듈을 사용해 http API를 호출합니다.
  • callWS - websocket 모듈을 사용해 API 통신을 합니다.


이번에는 requests 모듈을 이용해 http post method를 호출하는 함수입니다.

Blockchain의 RPC에 연결해 어러 API를 호출하고 결과를 가져옵니다.


그리고 로그를 기록하는 함수까지 확인해 보겠습니다.

# coding=utf-8
import requests
import unittest
import json, random
import time, datetime
import sys, pathlib
from pyunitreport import HTMLTestRunner


class nGleUtils():

	def getRequestId(self):
		'''API 호출 시 request dat 중 id에 넣을 int 데이터를 1부터 99중 랜덤으로 선택합니다.'''
		return random.randint(1,100)

	def getDateStrings(self):
		'''날짜를 확인하기 위해 여러 형태로 날자 데이터를 불러옵니다.'''
		i = datetime.datetime.now()
		dt = datetime.datetime(i.year, i.month, i.day, i.hour, i.minute, i.second)
		ts = time.mktime(dt.timetuple())
		return (i, dt, ts)

	def getLogfileNameWithPath(self):
		'''
		테스트 실행할 때 마다 log file을 생성합니다.
		로그 파일에 사용할 폴더 경로와 로그파일 전체 경로를 리턴합니다.
		'''

		# 로그 폴더를 만들기 위해 여러 형태의 날짜 데이터를 가저옵니다.
		i, dt, ts = self.getDateStrings()

		# 로그 폴더는 날짜형식입니다. (년월일시분초:'%Y%m%d%H%M%S')
		logFolderName = datetime.datetime.fromtimestamp(int(ts)).strftime('%Y%m%d%H%M%S')

		# 로그를 쌓을 경로를 확인하고 폴더를 만들어 줍니다.
		logPath = F"./reports/{logFolderName}"
		pathlib.Path(logPath).mkdir(parents=True, exist_ok=True)

		# 로그파일 경로를 지정합니다.
		logFileName = F"{logPath}/klaytn.log"

		# 로그폴더 경로와 로그파일 경로를 반환합니다.
		return logPath, logFolderName, logFileName

	def waitingCount(self, frontString, second, rearString):
		'''
		기다리기 심심하니 숫자라도 찍어봅시다.
		ex) self.ngle.waitingCount("Waiting for", 5, "seconds until writing in block a transaction.")
		'''
		for i in range(second, -1, -1):
			sys.stdout.write(F"\r{frontString} {i} {rearString}")
			sys.stdout.flush()
			time.sleep(1)

		sys.stdout.write("\n\n")

	def callRPC(self, nodeHost, rpcPort, methodName, params, logfile, saveResult=False):
		# 전달받은 methodName과 param으로 Klaytn RPC로 request를 전달합니다.
		HOST = F"http://{nodeHost}:{str(rpcPort)}"
		HEADERS = {'Content-Type': 'application/json'}
		isErrer = False

		# blockchain의 RPC 데이터 형식입니다. (일반적인 RESTful API 형태입니다.)
		payload = {'jsonrpc':'2.0','method':methodName,'params':params,'id':self.getRequestId()}
		
		# 테스트에서는 post method만을 사용했습니다. method를 input으로 받아 처리해도 됩니다.
		response = requests.post(HOST, data=json.dumps(payload), headers=HEADERS, timeout=300)

		# request와 response 모두 로그에 기록합니다.
		self.writeLog(logfile, methodName, HOST, payload, response.text, saveFile=saveResult)

		try:
			# 정상적인 결과값에는 result key가 존재합니다.
			resultText = json.loads(response.text)['result']
		except KeyError:
			# 에러가 발생할 경우 errer key가 존재합니다.
			resultText = json.loads(response.text)['error']
			isErrer = True

		return (response.status_code, resultText, isErrer)

	def writeLog(self, logFileName, klayMethod, host, data, resultText, testingStatus="", testingUnit="", saveFile=False):
		'''
		로그 파일에 Testing 시작을 기록합니다.
		debug_dumpBlock 등 result 데이터 사이즈가 큰 데이터는 별도 파일로 저장됩니다.
		'''

		# 결과 데이터를 파일에 저장할 경우 result 데이터를 변경합니다.
		if saveFile:
			resultText = F"{{\"nGle_Message\": \"{klayMethod} data was saved at a specific file.\"}}"

		# testingStatus가 "Start"또는 "Finish"라면 로그 기록의 처음과 끝을 나타내기 위한 구분 절 입니다.
		if testingStatus == "Start":
			logString = F"------------ Testing Start ({testingUnit}) ------------- \n\n"
		elif testingStatus == "Finish":
			logString = F"------------ Testing Finish ({testingUnit}) ------------ \n\n"
		else:
			# testingStatus가 "Start"또는 "Finish"가 아니라면 실제 requests를 이용해 request, response받은 데이터입니다.
			# 로그를 기록할 때 curl로 바로 확인해 볼 수 있도록 curl command 형식으로 저장합니다.
			curlString = F"curl -H \"Content-Type: application/json\" --data '{str(json.dumps(data))}' {host}"
		
			jsonResult = json.loads(resultText)

			logString = (
				F"[{str(datetime.datetime.now())}] [{klayMethod}]\n"
				F"{curlString}\n"
				F"{json.dumps(jsonResult, indent=4, sort_keys=True)} \n\n"
			)

		print(logString)

		with open(logFileName, 'at') as f:
			f.write(F"\n{logString}")



class BlockchainSample(unittest.TestCase):

	ngle = nGleUtils()

	LOGFOLDER, LOGDATE, LOGPATH = ngle.getLogfileNameWithPath()

	blockHost	= "10.10.1.168"
	rpcPort		= 8551

	def setUp(self):
		# unittest에서 TestCase가 시작할 때 호출됩니다.
		print('Test Start')

	def test_Accounts_RPC(self):
		logTitle = "Account_RPC"

		# test log 파일에 Unit Test 시작을 기록합니다.
		self.ngle.writeLog(self.LOGPATH, "", "", "", "", testingStatus="Start", testingUnit=logTitle)

		# klay_accounts ##############################################################################
		methodName = "klay_accounts"
		params = []
		statusCode, result, isError = self.ngle.callRPC(self.blockHost, self.rpcPort, methodName, params, self.LOGPATH)
		self.assertEqual(200, statusCode, "Status Code is not 200.")
		self.assertFalse(isError, result)

		# 5초간 기다렸다 다음 RPC를 호출합니다.
		self.ngle.waitingCount("Waiting for", 5, "seconds until writing in block a transaction.")

		# klay_blockNumber ##############################################################################
		methodName = "klay_blockNumber"
		params = []
		statusCode, result, isError = self.ngle.callRPC(self.blockHost, self.rpcPort, methodName, params, self.LOGPATH)
		self.assertEqual(200, statusCode, "Status Code is not 200.")
		self.assertFalse(isError, result)

		# test log 파일에 Unit Test 종료를 기록합니다.
		self.ngle.writeLog(self.LOGPATH, "", "", "", "", testingStatus="Finish", testingUnit=logTitle)

	def tearDown(self):
		# unittest에서 TestCase가 끝났을 때 호출됩니다.
		print('Test Finish')




def suite():
	# TestSuite를 사용해 Test 항목을 묶어줄 수 있고 addTest한 순서대로 실행할 수 있습니다.
	suite = unittest.TestSuite()

	suite.addTest(BlockchainSample('test_Accounts_RPC'))

	return suite


# testcase를 수행합니다.
if __name__== "__main__":
	runner = unittest.TextTestRunner()

	# HTMLTestRunner 설정입니다.
	kwargs = {
		"output": BlockchainSample.LOGDATE,
		"report_name": "klayTestReport",
		"failfast": True
	}

	runner = HTMLTestRunner(**kwargs)
	runner.run(suite())

먼저 nGleUtils 클래스를 확인해 보겠습니다.


getRequestId()

앞서 설명한 random 함수를 이용해 1에서 99까지의 id를 리턴합니다.

http://dejavuqa.tistory.com/294


getDateStrings()

여러 형태로 날짜와 시간을 확인하는 함수입니다.

http://dejavuqa.tistory.com/292


getLogfileNameWithPath()

로그를 기록할 폴더와 파일명 (경로)를 확인하는 함수입니다.

http://dejavuqa.tistory.com/293


waitingCount()

두 API 호출 사이 시간 간격을 주기위해 카운트를 셈니다.

http://dejavuqa.tistory.com/300


callRPC()

이번에 설명할 함수입니다. 호출은 아래와 같이 합니다.

callRPC(nodeHost, rpcPort, methodName, params, logfile, saveResult=False)

nodeHost는 실제 RPC(http)가 실행된 blockchain node IP 입니다.

rpcPort는 말그대로 접속 port 입니다. port는 node 실행할때 설정되며 node마다 다를 수 있습니다.

methodName은 API 종류입니다.

params는 API에 따른 input parameter입니다. params의 타입은 list 입니다.

logfile은 로그가 기록된 파일 경로입니다. getLogfileNameWithPath() 함수에서 가져옵니다.

saveResult는 API 호출 후 결과가 파일로 남을때 로그 기록용으로 사용합니다. False일때는 API의 결과를 그대로 사용하고 True라면 지정된 결과를 기록합니다.


리턴되는 데이터는 아래와 같습니다.

return (response.status_code, resultText, isErrer)

response.status_code는 http 결과 코드 입니다. 성공이라면 200, 경로를 찾을 수 없다면 404, 내부 오류라면 500 등이 리턴되게 됩니다.

resultText는 response.text 데이터 중 'result'에 해당하는 데이터를 반환합니다.

isError는 에러 유무를 리턴합니다. blockchain 로직에 대한 에러가 발생할 경우 status_code는 200으로 리턴되지만 request.text의 데이터는 errer message가 옵니다. json parsing 중 에러가 난다면 error message로 판단하고 isError를 True로 반환합니다.


writeLog()

request와 response를 그대로 로그파일에 기록합니다.

writeLog() 함수는 callRPC() 함수 안에서 호출됩니다. input parameter는 아래와 같습니다.

writeLog(logFileName, klayMethod, host, data, resultText, testingStatus="", testingUnit="", saveFile=False)

logFileName은 callRPC의 input으로 넘긴 logfile과 같습다.

klayMethod는 Blockchain의 API method입니다.

host는 callRPC에서 nodeHost와 rpcPort를 조합한 host 입니다.

data는 API 호출의 body 데이터 입니다. callRPC에서 payload 변수로 만들어집니다.

resultText는 response.text를 그대로 넘겨줍니다.

testingStatus는 로그 파일을 만들 때 Testcase 별로 구분해 주기위해 사용됩니다. "Start"또는 "Finish"로 넘겨주면 구분선이 기록됩니다.

testingUnit 는 Testcase 이름이나 테스트 영역에 대한 구분을 넣으면 됩니다. 타입은 String입니다.

saveFile은 callRPC()의 saveResult를 그대로 넘겨 줍니다.


BlockchainSample 클래스는 위 함수들을 이용해 unittest를 실행한 것입니다.

설명은 생략하겠습니다.


위 스크립트를 실행하면 아래와 같이 Terminal에 출력됩니다.

$ python callRPC.py 

Running tests... 
----------------------------------------------------------------------
Test Start
------------ Testing Start (Account_RPC) ------------- 


[2018-12-20 11:31:43.436163] [klay_accounts]
curl -H "Content-Type: application/json" --data '{"jsonrpc": "2.0", "method": "klay_accounts", "params": [], "id": 51}' http://10.10.1.168:8551
{
    "id": 51,
    "jsonrpc": "2.0",
    "result": [
        "0x859ba4df391ffb81a67a050097527a2c02aef596",
        "0x2259cfdae62f9853f84298aaf20c999391b1c6a3",
        "0x9789e98e28cbc7f6ff2d6a0f71586e1c10cf829a",
        "0xc346de6bd0f3f75d852f481f9f35e3a6452a977e",
        "0xabd8ec30f8f896844f01ecb9e7ddb418354d90e5"
    ]
} 


Waiting for 0 seconds until writing in block a transaction.

[2018-12-20 11:31:49.467644] [klay_blockNumber]
curl -H "Content-Type: application/json" --data '{"jsonrpc": "2.0", "method": "klay_blockNumber", "params": [], "id": 1}' http://10.10.1.168:8551
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": "0x67d6bd"
} 


------------ Testing Finish (Account_RPC) ------------ 


Test Finish
 test_Accounts_RPC (__main__.BlockchainSample) ... OK (6.053954)s

----------------------------------------------------------------------
Ran 1 test in 6.054s

OK



Generating HTML reports... 
Template is not specified, load default template instead.
Reports generated: /Users/tongchunkim/Documents/Test_Python/used_functions/reports/20181220113143/klayTestReport.html

실행 후 reports 폴더와 하위에 날짜와 시간으로 구성된 폴더(20181220113143)가 생성됩니다.

안에 보면 HTMLTestRunner에 의해 생성된 klayTestReport.html 파일과 writeLog() 함수로 작성된 klaytn.log 파일이 생성되어 있습니다.


klayTestReport.html 파일은 아래와 같이 unittest의 결과가 출력됩니다.

HTMLTestRunner에 대한 소개는 아래 링크에서 확인할 수 있습니다.

http://dejavuqa.tistory.com/286


klaytn.log 파일은 아래와 같이 작성되어 있습니다.

------------ Testing Start (Account_RPC) ------------- 


[2018-12-20 11:31:43.436163] [klay_accounts]
curl -H "Content-Type: application/json" --data '{"jsonrpc": "2.0", "method": "klay_accounts", "params": [], "id": 51}' http://10.10.1.168:8551
{
    "id": 51,
    "jsonrpc": "2.0",
    "result": [
        "0x859ba4df391ffb81a67a050097527a2c02aef596",
        "0x2259cfdae62f9853f84298aaf20c999391b1c6a3",
        "0x9789e98e28cbc7f6ff2d6a0f71586e1c10cf829a",
        "0xc346de6bd0f3f75d852f481f9f35e3a6452a977e",
        "0xabd8ec30f8f896844f01ecb9e7ddb418354d90e5"
    ]
} 


[2018-12-20 11:31:49.467644] [klay_blockNumber]
curl -H "Content-Type: application/json" --data '{"jsonrpc": "2.0", "method": "klay_blockNumber", "params": [], "id": 1}' http://10.10.1.168:8551
{
    "id": 1,
    "jsonrpc": "2.0",
    "result": "0x67d6bd"
} 


------------ Testing Finish (Account_RPC) ------------ 


여기까지 callRPC() 함수와 unittest를 이용한 테스트 설명이었습니다.



Comments