这是我写的一个小测试框架,case 和 lib 可以自己定义,提供了 log 和 controler 接口。log 用来打印日志,controler 用来控制设备。
针对 android 操作系统 UI 进行自动化测试,可以进行绝大多数的操作,多个 app 之间的交互。还可以在多台设备之间进行交互,譬如互相打电话,接电话等。
自己顶一下 
厉害厉害!!!
测试用例:
import os
import unittest
import sys
import pickle
sys.path.append(os.path.abspath(os.path.join(os.getcwd(),os.pardir)))
from container.base import settings
from engine import log
dir_path = sys.argv[1]
f = open(dir_path,'rb')
config_dictory = pickle.load(f)
f.close()
name = os.path.basename(config_dictory['TC']).split('.')[0]
workpath = config_dictory['work_dir']
logpath = config_dictory['log_path']    
log_path = os.path.join(workpath,logpath,name)
l = log.Logger(name,log_path)
mylogger = l.logger()
class test_setup_wifi_class(unittest.TestCase):
    def setUp(self):
        self.test_class = settings.Settings(config_dictory,mylogger)
    def tearDown(self):
        self.test_class.op.hardware().press_back(3)
        self.test_class.op.hardware().return_home()
    def test_skip_wizard(self):
        self.assertEqual(self.test_class.setup_wifi(),True,'test setup_wifi case fail')    
if __name__ == "__main__":
    my_suite = unittest.TestLoader().loadTestsFromTestCase(test_setup_wifi_class)
    result = unittest.TextTestRunner(verbosity=0).run(my_suite)
    if len(result.failures) != 0 or len(result.errors) != 0:
        flag = 'Fail'
    else:
        flag = 'Pass'
    print flag,(len(result.failures)+len(result.errors))
自定义生成测试报告:
import os
import sys
from pyh import *
import pickle
def main(address,report_path):
    f = open(address,'rb')
    database1 = pickle.load(f)
    f.close()
    case_name_pre = report_path.split('/')[-1]
    report_path_base = os.path.abspath(os.path.join(report_path,os.pardir))
    report_file_path = os.path.join(report_path_base,'report.html')
    content_dic = database1['suite_content']    
    page = PyH('test report')
    page << h1('this is report of yath project')
    order_list = page << ul(type='square')
    order_list << li('device id : %s'%database1['device_id'])
    order_list << li('suite mode : %s'%database1['suite_mode'])
    order_list << li('suite loop : %s'%database1['suite_loop'])
    order_list << li('suite name : %s'%database1['suite_name'])
    page << hr()
    p1_content = 'Loop_'+database1['suite_loop']+':'
    page << p(i(p1_content))
    t = page << table(border='1',bgcolor='#bbbb99')
    t << tr(th('test case',align='left')+th('test loop',align='center')+th('test result',align='center')+th('start time',align='center')+th('stop time',align='center')+th('duration(s)',align='center')+th('crashes',align='center'))
    for k in content_dic:
        item = content_dic[k]
        if len(item) == 2:
            continue
        test_case = item[0]
        test_loop = item[1]    
        if item[1] == '0':
            test_result,start_time,stop_time,duration,crashes = 'NA','NA','NA','NA','0'
        else:
            test_result = item[2]
            start_time = item[3]
            stop_time = item[4]
            duration = item[5]
            crashes = item[6]
        if test_result == 'Pass':
            attr_co_test_result = 'green'
        elif test_result == 'Fail':
            attr_co_test_result = 'red'
        elif test_result == 'NA':
            attr_co_test_result = 'grey'
        if int(crashes) > 0:
            crashes_dic = item[7]
            generate_crashes_page(crashes_dic,test_case,report_path)
            link_crash = 'file://'+os.path.join(report_path,'crash.html')
            t << tr(td(test_case,align='left')+td(test_loop,align='center')+td(test_result,align='center',bgcolor=attr_co_test_result)+td(start_time,align='center')+td(stop_time,align='center')+td(duration,align='center')+td(a(crashes,href=link_crash),align='center'))
        else:              
            t << tr(td(test_case,align='left')+td(test_loop,align='center')+td(test_result,align='center',bgcolor=attr_co_test_result)+td(start_time,align='center')+td(stop_time,align='center')+td(duration,align='center')+td(crashes,align='center'))    
    page.printOut(report_file_path)
def generate_crashes_page(dictory,case_name,crash_path):
    crash_file_path = os.path.join(crash_path,'crash.html')
    crash_page = PyH('Crash Page')
    crash_page << h1('this is a page of crash.')
    crash_page << p('detail info : ')
    for j in dictory:
        div1 = crash_page << div()
        h2_content = 'Crash %s occurs when execute %s : '%(str(j),case_name)
        div1 << h2(h2_content)
        crash_table = div1 << table(frame='box')
        crash_table << tr(th('key',align='left')+th('value'),align='left')
        crash_table << tr(td('crash info',align='left')+td(dictory[j]['crash_info']),align='left')
        crash_table << tr(td('data 0',align='left')+td(dictory[j]['crash_data0']),align='left')
        crash_table << tr(td('data 1',align='left')+td(dictory[j]['crash_data1']),align='left')
        crash_table << tr(td('data 2',align='left')+td(dictory[j]['crash_data2']),align='left')
    crash_page.printOut(crash_file_path)  
if __name__ == "__main__":
    main()
入口程序:
import xml.etree.ElementTree as ET
import os
import sys
sys.path.append(os.path.abspath(os.path.join(os.getcwd(),os.pardir,os.pardir)))
import argparse
import random
import subprocess as sp
import pickle
import datetime
import re
import time
from gramary.runner import iter_event,get_different_list,parse_history,get_crash_path,get_crashfile_conetnt
import generate_report
class Configuration:
    def __init__(self):
        self.log_path = ''
        self.execute_main_campaign = ''
        self.device_id = ''
        self.work_dir = os.path.abspath(os.path.join(os.getcwd(),os.pardir))
    def load(self,filepath):
        temp_dict = {}
        try:
            execfile(filepath,{},temp_dict)
            for key in temp_dict.keys():
                setattr(self,key,temp_dict[key])
        except TypeError,errormsg:
            print "Error : No file path given!"
            print "you can add -h option to view more info"
            sys.exit(-1)
        except IOError,msg1:
            print msg1
            print "Error : Please specify a path which contain a config file!"
            sys.exit(-1)
def get_testcase_from_xml(config):
    xml_path = os.path.join(config.work_dir,config.execute_main_campaign)
    parser = ET.parse(xml_path)
    root = parser.getroot()
    item_instance_list = root.iter('item')
    return item_instance_list
def get_testsuite_from_xml(config):
    xml_path = os.path.join(config.work_dir,config.execute_main_campaign)
    parser = ET.parse(xml_path)
    root = parser.getroot()
    suite_instance_list = root.iter('suite')
    return suite_instance_list
def get_full_path(config,string):
    return os.path.join(config.work_dir,string) + '.py'
def generate_file(config,dictory):
    temporary_path = os.path.join(config.work_dir,'temporary',config.device_id)
    if not os.path.isdir(temporary_path):
        try:
            os.makedirs(temporary_path)
        except OSError:
            pass        
    pickle_dump_path = os.path.join(temporary_path,'settings_temp.pkl')
    f1 = open(pickle_dump_path,'wb')
    pickle.dump(dictory,f1,True)
    f1.close()
    return pickle_dump_path
def save_data_for_html(config,dictory):
    temporary_path = os.path.join(config.work_dir,'temporary',config.device_id)
    if not os.path.isdir(temporary_path):
        try:
            os.makedirs(temporary_path)
        except OSError:
            pass        
    pickle_dump_path = os.path.join(temporary_path,'html_raw_data.pkl')
    f1 = open(pickle_dump_path,'wb')
    pickle.dump(dictory,f1,True)
    f1.close()
    return pickle_dump_path
def main(config):
    r1=r'Fail|Pass'
    r2=r'(\d+)'
    r_crash_data0 = r'DATA0=(\S+\s)[\s\S]*'
    r_crash_data1 = r'DATA1=(\S+\s)[\s\S]*'
    r_crash_data2 = r'DATA2=(\S+\s)[\s\S]*'
    seq_num = 1
    html_result_data = {}
    settings_list = dir(config)
    for _ in settings_list:
        if _ == '__doc__':
            settings_list.remove(_)
        if _ == '__module__':
            settings_list.remove(_)
        if _ == 'load':
            settings_list.remove(_)    
    settings_list.remove('__init__')
    settings_dictory = {}
    for _ in settings_list:
        settings_dictory[_] = getattr(config,_)
    rs = 'run_set_'+datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S')    
    settings_dictory['log_path'] = os.path.join(settings_dictory['log_path'],config.device_id,rs)
    execue_case_sequence = {}
    for testsuite in get_testsuite_from_xml(config):
        execue_case_sequence['suite_name'] = testsuite.get('name')
        execue_case_sequence['suite_loop'] = testsuite.get('loop')
        execue_case_sequence['suite_mode'] = testsuite.get('mode')
        execue_case_sequence['suite_content'] = {}
    execue_case_sequence['device_id'] = settings_dictory['device_id']    
    for testcase in get_testcase_from_xml(config):
        execue_case_sequence['suite_content'][seq_num] = [testcase.text,testcase.get('loop')]   
        seq_num += 1
    suite_loop = int(execue_case_sequence['suite_loop'])
    suite_mode = execue_case_sequence['suite_mode']
    tc_symbol_list = execue_case_sequence['suite_content'].keys()
    suite_content = execue_case_sequence['suite_content']
    if suite_loop == 0 or tc_symbol_list == []:
        sys.exit(0)
        print "there is no loop specify for running testsuite or no cases need to be executed!"
    need_to_change_sequence = False    
    if suite_mode == 'regular':
        need_to_change_sequence = False
    elif suite_mode == 'random':
        need_to_change_sequence = True
    else:
        need_to_change_sequence = False
    html_result_data = execue_case_sequence
    original_history = parse_history(settings_dictory['device_id'])
    for sl in range(1,suite_loop+1):
        print "Start to run testsuite : {0} , loop {1}".format(execue_case_sequence['suite_name'],sl)
        if need_to_change_sequence:
            random.shuffle(tc_symbol_list)
        for i in range(len(tc_symbol_list)):
            item_loop = int(suite_content[tc_symbol_list[i]][1]) if len(suite_content[tc_symbol_list[i]]) == 2 else 1
            item_path = get_full_path(config,suite_content[tc_symbol_list[i]][0])
            case_name = os.path.basename(item_path).split('.')[0]
            report_path = os.path.join(config.work_dir,settings_dictory['log_path'],case_name)
            if item_loop == 0:
                continue    
            for j in range(1,item_loop+1):
                print "Start to run case : {0} , loop {1}".format(case_name,j)
                start_time = time.time()
                start_time_dateformat = datetime.datetime.now().strftime('%Y/%m/%d_%H:%M:%S')
                settings_dictory['TC'] = item_path
                dir_path = generate_file(config,settings_dictory)
                execute_command = 'python ' + item_path + ' ' + dir_path
                proc=sp.Popen(execute_command.split(' '),stdout=sp.PIPE)
                out = proc.stdout.read().strip('\n')
                proc.wait()
                current_history = parse_history(settings_dictory['device_id'])
                stop_time = time.time()
                stop_time_dateformat = datetime.datetime.now().strftime('%Y/%m/%d_%H:%M:%S')
                time_taken = stop_time - start_time
                crash_list = iter_event(get_different_list(original_history,current_history))
                print_crashfile = ''
                crashes = {}
                crash_dic = {}
                c_count = 1
                for crash in crash_list:
                    crashlog_path = get_crash_path(crash)
                    if crashlog_path is not None:
                        print_crashfile = get_crashfile_conetnt(settings_dictory['device_id'],crashlog_path)
                    match_data0 = re.findall(r_crash_data0,print_crashfile)
                    match_data1 = re.findall(r_crash_data1,print_crashfile)
                    match_data2 = re.findall(r_crash_data2,print_crashfile)
                    crash_dic['crash_info'] = crash.strip('\r')
                    crash_dic['crash_data0'] = match_data0[0] if match_data0 != [] else ''
                    crash_dic['crash_data1'] = match_data1[0] if match_data1 != [] else '' 
                    crash_dic['crash_data2'] = match_data2[0] if match_data2 != [] else ''
                    crashes[c_count] = crash_dic
                    c_count+=1
                crash_number = len(crash_list)
                original_history = current_history
                html_result_data['suite_content'][i+1].append(re.findall(r1,out)[0])
                html_result_data['suite_content'][i+1].append(start_time_dateformat)
                html_result_data['suite_content'][i+1].append(stop_time_dateformat)
                html_result_data['suite_content'][i+1].append(round(time_taken,3))
                html_result_data['suite_content'][i+1].append(crash_number)
                html_result_data['suite_content'][i+1].append(crashes)  
            address = save_data_for_html(config,html_result_data)
            generate_report.main(address,report_path)
if __name__ == '__main__':
    parse=argparse.ArgumentParser(usage='%(prog)s [options]')
    parse.add_argument('configuration_file',type=str,nargs='?',help="A path of config file")
    args=parse.parse_args()
    config_file = args.configuration_file
    configuration = Configuration()
    configuration.load(config_file)
    main(configuration)
某个库文件如下:
 # coding: utf-8 
import os
import time
import sys
import subprocess as sp
sys.path.append(os.path.abspath(os.path.join(os.getcwd(),os.pardir)))
from engine import controler
max_retry = 30
class Commons:
    def __init__(self,conf_dic,ins_logger=None):
        self.conf_dic = conf_dic
        name = os.path.basename(conf_dic['TC']).split('.')[0]
        workpath = conf_dic['work_dir']
        dev = conf_dic['device_id']
        logpath = conf_dic['log_path']    
        log_path = os.path.join(workpath,logpath,name)
        self.mylogger = ins_logger   
        self.op = controler.Operator(device_id=dev,log_class=self.mylogger,log_path=log_path)
    def warm_reboot(self):
        if not self.op.hardware().check_boot_status():
            self.mylogger.error("[Common] Device is not ready for test reboot case. ")
            return False
        self.mylogger.info("[Common] Device is ready. ")    
        self.mylogger.info("[Common] Start to reboot device {0}".format(self.op.device_id))
        self.get_boot_infomation()
        self.op.hardware().reboot_device()
        time.sleep(15)
        self.mylogger.info("[Common] Start to check if execute reboot command? ")
        if not self.op.hardware().check_reboot_progress():
            self.mylogger.error("[Common] unknow error, reboot fail. ")
            return False
        self.mylogger.info("[Common] Device start booting...")    
        while 1:  
            if self.op.hardware().check_boot_status():
                self.mylogger.info("[Common] Device reboot successfully. ")
                break       
        return True
    def get_boot_infomation(self):
        get_boot_infomation_command = 'adb -s ' + self.op.device_id + ' shell getprop | grep boot'
        out = sp.Popen(get_boot_infomation_command.split(' '),stdout=sp.PIPE).stdout.read().strip('\n\r')
        self.mylogger.info(out)
    def log_on_google_account(self):
        self.mylogger.info("[Common] launch Settings app ")
        self.op.hardware().launch_app_with_intent(app='Settings')
        try:
            self.mylogger.info("[Common] click Accounts item ")
            self.op.search(text="Accounts",retry_time="3").click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find Accounts item . Got Exception {0}".format(e))
            return False 
        try:
            self.mylogger.info("[Common] click Add account item ")
            self.op.search(text="Add account").click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find Add account item . Got Exception {0}".format(e))
            return False
        try:
            self.mylogger.info("[Common] click Google item ")
            self.op.search(text="Google").click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find Google item . Got Exception {0}".format(e))
            return False
        retry_count = 0    
        while 1:
            try:
                self.mylogger.info("[Common] click enter your email input edittext ")
                self.op.search(description="Enter your email ").click()
                retry_count = 0
                break
            except AttributeError as e:
                if retry_count <= max_retry:
                    self.mylogger.debug("[Common] Can't find Enter your email input edittext . try again")
                    time.sleep(2)
                    retry_count+=1
                else:
                    self.mylogger.error("[Common] poor network course can't register google account . Got Exception {0}".format(e))
                    return False
        self.mylogger.info("[Common] input google account : {0}".format(self.conf_dic['google_account']))
        self.op.hardware().input_text(self.conf_dic['google_account'])
        try:
            self.mylogger.info("[Common] click NEXT button ")
            self.op.search(description="NEXT").click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find NEXT button . Got Exception {0}".format(e))
            return False
        time.sleep(5)    
        try:
            self.mylogger.info("[Common] click password edittext ")
            self.op.search(id="password").click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find password edittext . Got Exception {0}".format(e))
            return False
        self.mylogger.info("[Common] input google password : {0}".format(self.conf_dic['google_password']))
        self.op.hardware().input_text(self.conf_dic['google_password'])            
        try:
            self.mylogger.info("[Common] click NEXT button ")
            self.op.search(description="NEXT").click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find NEXT button . Got Exception {0}".format(e))
            return False
        while 1:
            try:
                self.mylogger.info("[Common] click ACCEPT button ")
                self.op.search(description="ACCEPT").click()
                retry_count = 0
                break
            except AttributeError as e:
                if retry_count <= max_retry:
                    self.mylogger.debug("[Common] Can't find ACCEPT button . try again")
                    time.sleep(2)
                    retry_count+=1
                else:
                    self.mylogger.error("[Common] poor network course can't register google account . Got Exception {0}".format(e))
                    return False        
        while 1:
            try:
                self.mylogger.info("[Common] check Google services shown??? ")
                self.op.search(text="Google services").click()
                retry_count = 0
                break
            except AttributeError as e:
                if retry_count <= max_retry:
                    self.mylogger.debug("[Common] Can't Google services keywords. try again")
                    time.sleep(5)
                    retry_count+=1
                else:
                    self.mylogger.error("[Common] poor network course can't register google account . Got Exception {0}".format(e))
                    return False
        try:
            self.mylogger.info("[Common] click Next button ")
            self.op.search(text="Next",enabled='true',retry_time='3').click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find NEXT button . Got Exception {0}".format(e))
            return False
        while 1:
            try:
                self.mylogger.info("[Common] click No thanks button ")
                self.op.search(text="No thanks").click()
                retry_count = 0
                break
            except AttributeError as e:
                if retry_count <= max_retry:
                    self.mylogger.debug("[Common] Can't find No thanks button . try again")
                    time.sleep(2)
                    retry_count+=1
                else:
                    self.mylogger.error("[Common] poor network course can't register google account . Got Exception {0}".format(e))
                    return False
        try:
            self.mylogger.info("[Common] click Continue button ")
            self.op.search(text="Continue").click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find Continue button . Got Exception {0}".format(e))
            return False
        time.sleep(2)
        return True
    def check_google_account_added(self):
        self.mylogger.info("[Common] launch Settings app ")
        self.op.hardware().launch_app_with_intent(app='Settings')
        try:
            self.mylogger.info("[Common] click Accounts item ")
            self.op.search(text="Accounts",retry_time="3").click()
        except AttributeError as e:
            self.mylogger.error("[Common] Can't find Accounts item . Got Exception {0}".format(e))
            return False
        if self.op.search(text='Google') is not None:
            self.mylogger.info("[Common] add google account successfully. ")
            return True
        else:
            self.mylogger.error("[Common] add account fail. ")
            return False        
ui_hooks 模块如下:
import controler
import time
import inspect
import subprocess as sp
"""
It provides a way to get through kinds of instability pup up box 
"""
class Hooks:
    def __init__(self,device_id):   
        self.device_id=device_id
    def get_generators(self,generator=None):
        global frame
        frame={}
        a,b,c,d,e,f,g=[],[],[],[],[],[],[]
        for element in generator:
            a.append(element.get('package'))
            b.append(element.get('index'))
            c.append(element.get('text'))
            d.append(element.get('content-desc'))
            e.append(element.get('resource-id'))
            f.append(element.get('bounds'))
            g.append(element.get('enabled'))
        for i in range(len(a)):
            frame[i+1]={'package':a[i],'index':b[i],'text':c[i],'description':d[i],'id':e[i],'bounds':f[i],'enabled':g[i]}
    def parse(self):
        status=False
        status=self.divider()
        return status
    def divider(self):
        flag=False
        main_key = frame[1]['package']
        seducer001 = inspect.getargspec(self.hook_maps)[3][0]
        seducer002 = inspect.getargspec(self.hook_contact)[3][0]
        seducer003 = inspect.getargspec(self.hook_gms)[3][0]
        seducer004 = inspect.getargspec(self.hook_settings)[3][0]
        seducer005 = inspect.getargspec(self.hook_stk)[3][0]
        seducer006 = inspect.getargspec(self.hook_android)[3][0]        
        if main_key == seducer001:
            flag = self.executer(seducer001,self.hook_maps())       
        if main_key ==  seducer002:
            flag = self.executer(seducer002,self.hook_contact())
        if main_key ==  seducer003:
            flag = self.executer(seducer003,self.hook_gms())
        if main_key ==  seducer004:
            flag = self.executer(seducer004,self.hook_settings())
        if main_key ==  seducer005:
            flag = self.executer(seducer005,self.hook_stk()) 
        if main_key ==  seducer006:
            flag = self.executer(seducer006,self.hook_android())                                            
        return flag        
    def executer(self,sed,func):
        e_flag=False
        print "this app is %s"% sed
        coodinate=func
        if coodinate is None:
            e_flag = False
        else:
            pointseq=self.get_point(coodinate)
            self.tap(pointseq)  
            e_flag =True
        return e_flag           
    def hook_maps(self,seducer='com.google.android.apps.maps'):
        coodinate=None
        zipers=[]
        print "hook_maps method is invoked"
        return coodinate
    def hook_stk(self,seducer='com.android.stk'):
        coodinate=None
        print "hook_stk method is invoked"          
        for j in range(1,len(frame.keys())+1):
            if 'button_ok' in frame[j]['id'].strip('\n'):
                coodinate=frame[j]['bounds']        
        return coodinate
    def hook_android(self,seducer='android'):
        coodinate=None
        print "hook_android method is invoked"          
        for j in range(1,len(frame.keys())+1):
            if 'button1' in frame[j]['id'].strip('\n'):
                coodinate=frame[j]['bounds']        
        return coodinate
    def hook_settings(self,seducer='com.android.settings'):
        coodinate=None
        zipers=["Update preferred SIM card?"]
        print "hook_settings method is invoked"
        zipers_match=False
        for zi in zipers:
            for i in range(1,len(frame.keys())+1):
                if zi == frame[i]['text'].strip('\n'):
                    zipers_match=True
                    break
        if zipers_match:          
            for j in range(1,len(frame.keys())+1):
                if 'NO' in frame[j]['text'].strip('\n'):
                    coodinate=frame[j]['bounds']        
        return coodinate
    def hook_gms(self,seducer='com.google.android.gms'):
        coodinate=None
        zipers=["Couldn't sign in"]
        print "hook_gms method is invoked"
        zipers_match=False
        for zi in zipers:
            for i in range(1,len(frame.keys())+1):
                if zi == frame[i]['text'].strip('\n'):
                    zipers_match=True
                    break
        if zipers_match:          
            for j in range(1,len(frame.keys())+1):
                if 'Next' in frame[j]['text'].strip('\n'):
                    coodinate=frame[j]['bounds']        
        return coodinate
    def hook_contact(self,seducer='com.google.android.packageinstaller'):
        coodinate=None  
        zipers = ["Allow Contacts to access photos, media, and files on your device?"]
        print "hook_contact method is invoked"
        zipers_match=False
        for zi in zipers:
            for i in range(1,len(frame.keys())+1):
                if zi == frame[i]['text'].strip('\n'):
                    zipers_match=True
                    break
        if zipers_match:          
            for j in range(1,len(frame.keys())+1):
                if 'Allow' in frame[j]['text'].strip('\n'):
                    coodinate=frame[j]['bounds']
                if 'Next' in frame[j]['text'].strip('\n'):
                    coodinate=frame[j]['bounds']  
        return coodinate
    def get_point(self,coodinate):
        s = coodinate
        temp = s.replace('[','').replace(']',',').split(',')
        temp.remove('')
        point_c = []
        co_x1 = temp[0]
        co_x2 = temp[2]
        point_c.append(str((int(co_x1)+int(co_x2))/2))
        co_y1 = temp[1]
        co_y2 = temp[3]
        point_c.append(str((int(co_y1)+int(co_y2))/2))
        return point_c
    def tap(self,point_list):
        tap_coordinate = point_list
        tap_command = self.adb_shell() + 'input tap ' + tap_coordinate[0] + ' ' + tap_coordinate[1]
        self.execute_command(tap_command).wait()                      
    def adb_shell(self):
        adb_shell_command = 'adb -s ' + self.device_id + ' shell '
        return adb_shell_command
    def execute_command(self,cmd,ignore_print=True):    
        ccmd = cmd
        if ignore_print:
            print ccmd
        else:
            pass    
        proc = sp.Popen(ccmd.split(' '),stdout=sp.PIPE)    
        return proc
logger 代码如下:
#!/usr/bin/python
import logging
import os
class Logger():
    def __init__(self,name,log_path):
        self.log_path = log_path
        self.name = name
        self.log_file = os.path.join(log_path,'log.txt')
        if not os.path.isdir(log_path):
            try:
                os.makedirs(log_path)
            except OSError:
                pass             
    def modlogger(self):
        mylogger = logging.getLogger(self.name)
        mylogger.setLevel(logging.DEBUG)
        fh = logging.FileHandler(self.log_file)
        sh = logging.StreamHandler()
        formatter = logging.Formatter("%(asctime)s  %(levelname)s\t%(message)s")
        fh.setFormatter(formatter)
        sh.setFormatter(formatter)
        mylogger.addHandler(fh)
        mylogger.addHandler(sh)
        return mylogger
    def get_instance(self):
        logger = Logger(self.name,self.log_path)
        return logger
    def logger(self):
        methods = self.get_instance()
        return Methods(methods)  
class Methods(object):
    def __init__(self,obj):
        self.ml = obj.modlogger()
    def info(self,string):
        self.ml.info(string)
    def debug(self,string):
        self.ml.debug(string)
    def warning(self,string):
        self.ml.warning(string)
    def error(self,string):
        self.ml.error(string)
if __name__=="__main__":
    l = Logger('test0','/home/buildbot/bluesea/reporter')
    m = l.logger()
    m.info('------this is info message-------')
    m.error('tap ok button fail')
    m.debug('-----this is debug message-------')
    m.warning('you have not set timeout')