don't stop believing

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

Python/Basic

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

Tongchun 2018. 12. 25. 13:32

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 통신을 합니다.


blockchain 테스트 대상에는 websocket 도 있습니다. method와 전달하는 data는 RPC와 동일합니다.

Python의 websocket 모듈을 사용해 websocket을 사용해 확인하겠습니다. socket 연결을 맺고 listing을 하는 것은 아닙니다. 단지 websocket 연결해 데이터를 받고 바로 끊습니다. 

함수와 unittest script는 callRPC와 동일하거나 유사합니다.

# coding=utf-8
from websocket import create_connection, WebSocket
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 callWS(self, nodeHost, wsPort, methodName, params, logfile, saveResult=False):
		# 전달받은 methodName과 param으로 Klaytn RPC로 request를 전달합니다.
		HOST = F"ws://{nodeHost}:{str(wsPort)}"
		payload = {'jsonrpc':'2.0','method':methodName,'params':params,'id':self.getRequestId()}
		isErrer = False

		ws = create_connection(HOST)
		ws.send(json.dumps(payload))
		response = ws.recv()
		ws.close()

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

		self.writeLog(logfile, methodName, HOST, payload, response, saveFile=saveResult)
		return (resultJson, 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}"
			curlString = F"[\"{host}\"]\n{str(json.dumps(data))}"
		
			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"
	wsPort		= 8552

	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 = []
		result, isError = self.ngle.callWS(self.blockHost, self.wsPort, methodName, params, self.LOGPATH)
		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 = []
		result, isError = self.ngle.callWS(self.blockHost, self.wsPort, methodName, params, self.LOGPATH)
		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())


먼저 request 모듈이 아닌 아래와 같은 websocket 모듈입니다.

from websocket import create_connection, WebSocket


그리고 callRPC가 아닌 callWS을 넣었습니다.


callWS()

input parameter는 아래와 같습니다.

callWS(nodeHost, wsPort, methodName, params, logfile, saveResult=False)

nodeHost는 blockchain node IP 입니다.

wsPort는 websocket port입니다.

methodName, params, logifle, saveResult는 callRPC와 동일합니다.

websocket에 대한 처리는 아래와 같습니다.


ws = create_connection(HOST)

ws.send(json.dumps(payload))

response = ws.recv()

ws.close()


연결하고, payload를 json 형태로 변경해 send 합니다. 그리고 recv()로 데이터를 받으면 websocket을 종료합니다.


writeLog()에서는 callRPC에서 사용한 curl 형태의 string 대신 host와 data(payload)를 그냥 써줍니다.

curlString = F"[\"{host}\"]\n{str(json.dumps(data))}"


unittest의 test 함수에서는 아래와 같이 callWS을 호출합니다.

result, isError = self.ngle.callWS(self.blockHost, self.wsPort, methodName, params, self.LOGPATH)


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

$ python callWS.py 

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


[2018-12-25 13:07:06.716017] [klay_accounts]
["ws://10.10.1.168:8552"]
{"jsonrpc": "2.0", "method": "klay_accounts", "params": [], "id": 16}
{
    "id": 16,
    "jsonrpc": "2.0",
    "result": [
        "0x859ba4df391ffb81a67a050097527a2c02aef596",
        "0x2259cfdae62f9853f84298aaf20c999391b1c6a3",
        "0x9789e98e28cbc7f6ff2d6a0f71586e1c10cf829a",
        "0xc346de6bd0f3f75d852f481f9f35e3a6452a977e",
        "0xabd8ec30f8f896844f01ecb9e7ddb418354d90e5"
    ]
} 


Waiting for 0 seconds until writing in block a transaction.

[2018-12-25 13:07:12.852365] [klay_blockNumber]
["ws://10.10.1.168:8552"]
{"jsonrpc": "2.0", "method": "klay_blockNumber", "params": [], "id": 77}
{
    "id": 77,
    "jsonrpc": "2.0",
    "result": "0x6e8498"
} 


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


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

----------------------------------------------------------------------
Ran 1 test in 6.311s

OK



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

callRPC에서와 마찬가지로 klayTestReport.html가 reports 폴더 안에 남았네요.

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

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


[2018-12-25 13:07:06.716017] [klay_accounts]
["ws://10.10.1.168:8552"]
{"jsonrpc": "2.0", "method": "klay_accounts", "params": [], "id": 16}
{
    "id": 16,
    "jsonrpc": "2.0",
    "result": [
        "0x859ba4df391ffb81a67a050097527a2c02aef596",
        "0x2259cfdae62f9853f84298aaf20c999391b1c6a3",
        "0x9789e98e28cbc7f6ff2d6a0f71586e1c10cf829a",
        "0xc346de6bd0f3f75d852f481f9f35e3a6452a977e",
        "0xabd8ec30f8f896844f01ecb9e7ddb418354d90e5"
    ]
} 


[2018-12-25 13:07:12.852365] [klay_blockNumber]
["ws://10.10.1.168:8552"]
{"jsonrpc": "2.0", "method": "klay_blockNumber", "params": [], "id": 77}
{
    "id": 77,
    "jsonrpc": "2.0",
    "result": "0x6e8498"
} 


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

여기까지 blockchain 테스트에 사용한 함수 설명이었습니다.



Comments