2022년 6월 5일 일요일

android monkey test with python adbutils(파이썬을 이용한 몽키 테스트)

Monkey Test

Monkey는 애플리케이션을 반복 랜덤 방식으로 스트레스 테스트할 수 있는 도구 입니다.

이번에는 python을 이용해서 monkey test를 수행하고 에러가 발생하면 로그를 획득하는 코드입니다. 90% 이상은 이전에 작성한 코드를 재사용 하였습니다.


도움말 링크

https://developer.android.com/studio/test/monkey?hl=ko


기본 구문

$ adb shell monkey [options] <event-count>

예제

$ adb shell monkey -p your.package.name -v 500

명령어 옵션

카테고리옵션설명
일반--help간단한 사용 가이드를 인쇄합니다.
-v명령줄의 각 -v는 상세 레벨을 증가합니다. 레벨 0(기본값)에서는 시작 알림, 테스트 완료, 최종 결과 이외의 정보를 거의 제공하지 않습니다. 레벨 1에서는 실행되는 테스트에 관한 세부정보를 제공합니다(예: 활동에 전송되는 개별 이벤트). 레벨 2에서는 테스트에 선택되거나 선택되지 않은 활동과 같은 더 자세한 설정 정보를 제공합니다.
이벤트-s <seed>의사 랜덤 숫자 생성기의 시드값입니다. 같은 시드값으로 Monkey를 다시 실행하면 동일한 이벤트 시퀀스가 생성됩니다.
--throttle <milliseconds>이벤트 사이에 고정 지연을 삽입합니다. 이 옵션을 사용하여 Monkey의 속도를 늦출 수 있습니다. 지정하지 않으면 지체 없이 최대한 빠르게 이벤트가 생성됩니다.
--pct-touch <percent>터치 이벤트 비율을 조정합니다 (터치 이벤트는 화면의 단일 위치에서의 다운/업 이벤트입니다).
--pct-motion <percent>모션 이벤트 비율을 조정합니다 (모션 이벤트는 화면 어딘가의 다운 이벤트와 일련의 의사 랜덤 이동, 업 이벤트로 구성됩니다).
--pct-trackball <percent>트랙볼 이벤트 비율을 조정합니다 (트랙볼 이벤트는 하나 이상의 랜덤 이동으로 구성되며 때로는 클릭이 뒤따릅니다).
--pct-nav <percent>'기본' 탐색 이벤트 비율을 조정합니다 (탐색 이벤트는 방향 입력 기기의 입력처럼 위/아래/왼쪽/오른쪽으로 구성됩니다).
--pct-majornav <percent>'주요' 탐색 이벤트 비율을 조정합니다 (5방향 패드의 가운데 버튼 또는 메뉴 키와 같이 일반적으로 UI 내에 작업을 발생시키는 탐색 이벤트입니다).
--pct-syskeys <percent>'시스템' 키 이벤트 비율을 조정합니다 (이러한 키는 홈, 돌아가기, 통화 시작, 통화 종료, 볼륨 조절과 같이 일반적으로 시스템에서 사용하도록 예약됩니다).
--pct-appswitch <percent>활동 실행 비율을 조정합니다. 랜덤 간격으로 Monkey는 startActivity() 호출을 실행하며 이는 패키지 내의 모든 활동의 적용 범위를 최대화하는 방법입니다.
--pct-anyevent <percent>다른 유형의 이벤트 비율을 조정합니다. 이것은 다른 모든 유형의 이벤트를 포괄합니다(예: 키 누르기, 기기에서 덜 사용되는 다른 버튼 등).
제약 조건-p <allowed-package-name>이런 식으로 하나 이상의 패키지를 지정하면 Monkey는 시스템이 지정된 패키지 내의 활동 방문하도록 허용합니다. 애플리케이션이 다른 패키지(예: 연락처 선택)의 활동에 액세스해야 한다면 그 패키지도 지정해야 합니다. 어떤 패키지도 지정하지 않으면 Monkey는 시스템이 모든 패키지에서 활동을 시작하도록 허용합니다. 여러 패키지를 지정하려면 -p 옵션을 여러 번 사용하세요(패키지당 -p 옵션 하나).
-c <main-category>이 방법으로 하나 이상의 카테고리를 지정하면 Monkey는 시스템이 지정된 카테고리 중 하나와 함께 나열된 활동 방문하도록 허용합니다. 어떤 카테고리도 지정하지 않으면 Monkey는 Intent.CATEGORY_LAUNCHER 또는 Intent.CATEGORY_MONKEY 카테고리와 함께 나열된 활동을 선택합니다. 여러 카테고리를 지정하려면 -c 옵션을 여러 번 사용하세요(카테고리당 -c 옵션 하나).
디버깅--dbg-no-events지정된 경우 Monkey는 테스트 활동으로 초기 실행을 이행하지만 더 이상의 이벤트를 생성하지는 않습니다. 최상의 결과를 얻으려면 -v와 하나 이상의 패키지 제약 조건, 0이 아닌 스로틀을 결합하여 Monkey가 30초 이상 계속 실행되도록 합니다. 이는 애플리케이션에서 호출한 패키지 전환을 모니터링할 수 있는 환경을 제공합니다.
--hprof설정된 경우 이 옵션은 Monkey 이벤트 시퀀스 직전 및 직후에 프로파일링 보고서를 생성합니다. 이 경우 데이터/misc에 대용량(~5Mb) 파일이 생성되므로 주의해서 사용하세요. 프로파일링 보고서 분석에 관한 자세한 내용은 앱 성능 프로파일링을 참조하세요.
--ignore-crashes일반적으로 Monkey는 애플리케이션이 비정상 종료되거나 처리되지 않은 예외 유형이 발생하면 중지됩니다. 이 옵션을 지정하면 Monkey는 계산이 완료될 때까지 시스템에 이벤트를 계속 전송합니다.
--ignore-timeouts일반적으로 Monkey는 애플리케이션에 모든 유형의 시간 제한 오류가 발생하면 중지됩니다(예: '애플리케이션 응답 없음' 대화상자). 이 옵션을 지정하면 Monkey는 계산이 완료될 때까지 시스템에 이벤트를 계속 전송합니다.
--ignore-security-exceptions일반적으로 Monkey는 애플리케이션에 모든 유형의 권한 오류가 발생하면 중지됩니다(예: 특정 권한이 필요한 활동을 시작하려고 하는 경우). 이 옵션을 지정하면 Monkey는 계산이 완료될 때까지 시스템에 이벤트를 계속 전송합니다.
--kill-process-after-error일반적으로 Monkey가 오류로 인해 중지되면 실패한 애플리케이션은 계속 실행됩니다. 이 옵션을 설정하면 시스템에 오류가 발생한 프로세스를 중지하라는 신호를 보냅니다. 참고: 정상적(성공적)으로 완료가 되면 실행된 프로세스는 중지되지 않으며 기기는 최종 이벤트 후 마지막 상태로 유지됩니다.
--monitor-native-crashesAndroid 시스템 네이티브 코드에서 발생하는 비정상 종료를 감시하고 보고합니다. --kill-process-after-error를 설정하면 시스템은 중지됩니다.
--wait-dbg디버거가 연결될 때까지 Monkey 실행을 중지합니다.


!!주의!!

이벤트 갯수는 제일 마지막에 넣어야 합니다. 

틀린 예제
./adb shell monkey -p your.company.name -v 50 --throttle 1000

맞는 예제
./adb shell monkey -p your.company.name --throttle 1000 -v 50


소스 코드

from adbutils import adb
import re

class adb_utils_rp():
	def adb_connect(self, serial=None):
		self.d = adb.device(serial=serial)
		if self.d.serial==None :
			return None
		print("adb conneced",self.d.serial)
		return self.d

	def get_prop(self, keyname=None):
		ret_dict = {}
		# 전체 poperty 는 getprop 명령으로 획득 가능합니다.
		ret = self.d.shell("getprop")
		#print(ret)
		retlines = ret.replace("]\n","]\0")
		retlines = retlines.split("\0")
		
		for one_line in retlines:
			one_line = one_line.replace("]: [","]"+"\0"+"[")
			dat = one_line.split("\0")
			#print(dat)
			dat1 = dat[1].strip()
			ret_dict[dat[0][1:len(dat[0])-1]] = dat1[1:len(dat1)-1]
			#print(dat[0][1:len(dat[0])-1],ret_dict[dat[0][1:len(dat[0])-1]])
		
		if keyname==None:
			return ret_dict
		
		return ret_dict.get(keyname)

	def make_bugreportz(self, log_zip_filename="log.zip"):
		# bugreportz 는 /data/user_de/0/com.android.shell/files/bugreports/ 에 로그를 생성하며 생성시 이전로그가 삭제 됩니다.
		#            리턴값은 OK:로 시작하며 뒤에는 경로명이 넘어 옵니다.
		#            예) OK:/data/user_de/0/com.android.shell/files/bugreports/dumpstate-2022-05-14-17-33-03.zip
		#
		# 동작중 다시 호출하면 아래와 같은 string이 리턴됩니다.
		# Previous sys dump or full dump is running, so skip this one

		ret = self.d.shell("bugreportz")
		print(ret)

		isok = ret.split(":")
		if len(isok)!=2 or isok[0]!='OK':
			return -1
		
		# 로그 뜬뒤 로그 버퍼를 초기화한다.
		self.d.shell("logcat -b all -c")
		
		# return 은 int size가 넘어옵니다.
		# 파일이 없으면 exception 발생합니다.
		#    adbutils.errors.AdbError: open failed: No such file or directory
		ret=self.d.sync.pull(isok[1], log_zip_filename)
		if ret==0:
			return -2
		
		# int size 가 리턴됩니다.
		return ret
		
	#EVENT LOG (logcat -b events -v threadtime -v printable -v uid -d *:v)
	def get_event_log(self):
		ret = self.d.shell("logcat -b events -v threadtime -v printable -v uid -d *:v")
		#print(ret)
		return ret
		
	# @return
	# None : 오류 없음
	# else : 오류 있음
	def check_event_log(self):
		find_str = "(am_crash)|(am_anr)"
		ret = self.get_event_log()
		#ret = " \n\n\n\n am_cras \n  am_anr "
		finded = re.search(find_str,ret)
		return finded
		
	def monkey(self,package,count,seed=0,throttle=300,extra=""):
		return self.d.shell(f"monkey -p {package} -s {seed} --throttle {throttle} {extra} -v {count}")
		
if __name__ == "__main__":
	adbrp = adb_utils_rp()
	adbrp.adb_connect()
	print(adbrp.get_prop("ro.serialno"))
	print(adbrp.get_prop("ro.build.fingerprint"))
	#print(adbrp.make_bugreportz())
	
	# test가 종료될때까지 기다리게 됩니다.
	print(adbrp.monkey("com.android.chrome",500))
	
	#print(adbrp.get_event_log())
	#print(adbrp.check_event_log())
	if adbrp.check_event_log()!=None:
		adbrp.make_bugreportz()


예제 설명

기존 예제 재활용입니다. monkey를 실행시키고 앱이 중지했거나 anr이 발생하면 dump를 생성하도록 만들었습니다.

앱에서 에러가 발생했는지는 event log를 통해 확인합니다. monkey의 리턴값을 이용해도 될듯 한데 아직 crash 되었을때 monkey의 리턴값이 어떻게 넘어오는지 확인을 하지 못한 상태입니다.

monkey test에서 중요한 인자는 다음과 같습니다.

-p 테스트 하고자 하는 특정 패키지를 실행시킵니다.

--throttle 인자는 테스트 하는 앱으로 키를 보내게 되는데 ms 단위로 보내게됩니다. 즉 너무 짧으면 순식간에 휙 지나가 버리고 테스트도 제대로 안되는 경우가 많습니다. 그래서 시간을 넉넉히 여유를 주도록 합니다.

adbrp.monkey("com.android.chrome",500)

여기에서는 크롬앱을 500개 이벤트를 보내서 테스트 하도록 합니다. 이벤트라는것은 키를 누르거나 스크롤을 하거나 특정 좌표를 클릭하는것도 하나의 이벤트가 됩니다.

기본적으로 throttle은 300ms으로 해두었습니다. 너무 짧으면 순식간에 테스트가 끝나버리고 동작도 제대로 안되는 경우가 많습니다.




댓글 없음:

댓글 쓰기