appium学习(三)-----(yaml数据配置,日志收集)

前面我们配置Capability时,各个参数都是在代码里面写死的,比如:desired_caps['platformVersion']='5.1.1' 一旦设备和测试的app发生改变则需要去代码里面一个个修改,要么同时根据不同设备不同App来维护多套代码,这样显示是不符合规范而且是低效的!违背了自动化的初衷,那么如何改进这样的现状呢?

解决思路

针对这种可能频繁变动的部分,可以将数据和代码分离。将数据单独抽离出来放在配置文件里面, 代码直接从配置文件去读取数据,这样能够减少代码冗余,提高效率。PS:类似的Web前端的html标签和css分离。

配置数据该如何管理?这里我们推荐使用yaml来管理配置数据。

yaml概述

yaml简介

正如YAML所表示的YAML Ain’t Markup Language,YAML 是一种简洁的非标记语言。YAML以数据为中心,使用空白,缩进,分行组织数据,从而使得表示更加简洁易读。

由于实现简单,解析成本很低,YAML特别适合在脚本语言中使用。列一下现有的语言实现:Ruby,Java,Perl,Python,PHP,JavaScript等。

YAML 是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。比如同一段数据Josn和Yaml的表示分别如下:

Json
{ name: 'Tom Smith',age: 37,spouse: { name: 'Jane Smith', age: 25 },children: [ { name: 'Jimmy Smith', age: 15 },{ name: 'Jenny Smith', age: 12 } ] }
yaml
name: Tom Smith
age: 37
spouse:
    name: Jane Smith
    age: 25
children:
 - name: Jimmy Smith
   age: 15
 - name: Jenny Smith
   age: 12

 

语法特点

  • 大小写敏感
  • 使用缩进表示层级关系
  • 缩进时不允许使用Tab键,只允许使用空格。
  • 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可

yaml下载安装

 

  • 安装:下载完成后点击运行安装包即可。
  • 安装完成后在python引入yaml检测是否安装成功。

支持数据类型

  1. 纯量(scalars):单个的、不可再分的值
  2. 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list)
  3. 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)

纯量

数据最小的单位,不可以再分割。类似于Python中单个变量

flag

list数组

Python的list数组结构类似,数组元素使用“-”开头,也可以根据缩进进行数组嵌套。

- Jack
- Harry
- Sunny

# 也可以写成一行
[Jack,Harry,Sunny]

对应到python的list写法如下:

['Jack','Harry','Sunny']

对象

对象的一组键值对,使用冒号结构表示。类似Python中的字典数据结构。

platformName: Android
platformVersion: 6.0.1

# Yaml 也允许另一种写法,将所有键值对写成一个行内对象。
{platformName: Android,platformVersion: 6.0.1}

注意:冒号后面一定要有空格!对应到python字典的写法如下:

{'platformName': 'Android', 'platformVersion': '6.0.1'}

数据嵌套

yaml数据嵌套表示可以将上面的各类数据根据实际场景进行组合嵌套。

数据场景

Tom Smith 37岁,他有一个妻子叫 Jane Smith,25岁。 另外他有2个孩子,一个叫Jimmy Smith,15岁;另外一个叫Jenny Smith ,12岁。

yaml语法表示如下:

familyInfo.yaml

name: Tom Smith
age: 37
spouse:
    name: Jane Smith
    age: 25
children:
 - name: Jimmy Smith
   age: 15
 - name: Jenny Smith
   age: 12

 

转化为Python的写法为:

{'name':'Tom Smith','age':37,'spouse':{'name':'Jane Smith','age':25},'childern':[{'name':'Jimmy Smith','age':15},{'name':'Jenny Smith','age':12}]}

yaml数据操作

数据读取

测试场景
  • 读取配置中的所有信息
  • 读取yaml数据表中Tom Smith的姓名、年龄、信息
  • 单独读取配偶的姓名和年龄信息
  • 分别读取两个孩子的姓名、年龄信息
load方法

load(stream, Loader=Loader) 解析文件流中的第一个YAML文档并生成相应的Python对象。

代码实现

yaml_load.py

import yaml

file=open('familyInfo.yaml','r')
data=yaml.load(file)

print(data)

print(data['name'])
print(data['age'])

print(data['spouse'])
print(data['spouse']['name'])
print(data['spouse']['age'])

print(data['children'])
print(data['children'][0]['name'])
print(data['children'][0]['age'])

print(data['children'][1]['name'])
print(data['children'][1]['age'])

数据修改

如果想改变某个数据,可以使用如下方法:

data['name']='51zxw'
print(data['name'])

注意:此处只是变量类型的数据变更,不会真正修改到yaml配置表中的数据。

数据转化

方法:dump()可以将Python对象序列化成YAML流。如果stream为None,则返回生成的字符串。

测试场景

将下面python数据类型转化为yaml数据类型

slogan=['welcome','to','51zxw']
website={'url':'www'}
代码实践
import yaml

slogan=['welcome','to','51zxw']
website={'url':'www'}

#python data
print(slogan)
print(website)
 
#yaml data
print(yaml.dump(slogan))
print(yaml.dump(website))

运行结果:

C:\Python35\python.exe E:/AppiumScript/advance/yaml/yaml_down.py
['welcome', 'to', '51zxw']
{'url': 'www'}
[welcome, to, 51zxw]
{url: www}
Process finished with exit code 0

Capability配置数据分离实践

测试场景

capability的各项参数值与代码进行分离。

场景分析

结合前面所学习的知识,我们可以把之前capability中各项写死的配置信息来抽离出来,存放在一个yaml配置文件中,使用 对象数据类型来存储数据;然后调用load()方法读取数据,从而实现数据和代码的分离。

代码实现

1.参数配置表:desired_caps.yaml

rom appium import webdriver
import yaml

file=open('desired_caps.yaml','r')
data=yaml.load(file)

desired_caps={}
desired_caps['platformName']=data['platformName']

desired_caps['platformVersion']=data['platformVersion']
desired_caps['deviceName']=data['deviceName']

desired_caps['app']=data['app']
desired_caps['noReset']=data['noReset']

desired_caps['appPackage']=data['appPackage']
desired_caps['appActivity']=data['appActivity']

driver = webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub', desired_caps)
报错分析
yaml.scanner.ScannerError: mapping values are not allowed here

该报错说明map对象数据类型写法错误,一般为“:”后面没有留空格。如:platformName:Android

问题思考

在自动化脚本运行过程中,IDE控制台一般都会输出运行日志。但是如果测试项目是在liunx服务器上面运行,没有IDE控制台输出log,那么我们该如何采集日志?

日志概述

日志作用

不管是在项目开发还是测试过程中,项目运行一旦出现问题日志信息就非常重要了。日志是定位问题的重要手段,就像侦探人员要根据现场留下的线索来推断案情。

日志级别

脚本运行会有很多的情况,比如调试信息、报错异常信息等。日志要根据这些不同的情况来继续分级管理,不然对于排查问题的筛选会有比较大的干扰。 。日志一般定位的级别如下:

级别

何时使用

DEBUG

调试信息,也是最详细的日志信息。

INFO

证明事情按预期工作。

WARNING

表明发生了一些意外,或者不久的将来会发生问题(如‘磁盘满了’)。软件还是在正常工作。

ERROR

由于更严重的问题,软件已不能执行一些功能了。

CRITICAL

严重错误,表明软件已不能继续运行了。

  

首先我们日志需要按照info、debug、error等级别来进行区分的。当然这个级别可以自己去设置。在一般的情况下我们普通的输出我们直接用info类型,调试的时候用debug类型,如果预计有错误时那么我们就需要用error类型的日志,一般情况去info级别最为合适。

日志格式

日志格式化是为了提高日志的可阅读性,比如:时间+模块+行数+日志具体信息 的内容格式。如果日志信息杂乱无章的全部输出来,这样也不利于定位问题。如下所示就是日志格式化输出,非常便于阅读查看。

2018-01-10 18:02:35,633 backup.py[line:18] INFO ============test backup================
2018-01-10 18:02:39,253 backup.py[line:20] INFO click backup button
2018-01-10 18:02:54,025 backup.py[line:23] INFO click next button
2018-01-10 18:03:09,280 common_fun.py[line:83] INFO Start send Email..
2018-01-10 18:03:11,840 common_fun.py[line:91] INFO Send Email finish!
2018-01-10 18:03:13,305 common_fun.py[line:168] INFO get backup screenshot
2018-01-10 19:36:00,238 backup.py[line:17] INFO ============test backup================
2018-01-10 19:36:04,530 backup.py[line:19] INFO click backup button
2018-01-10 19:37:20,107 backup.py[line:17] INFO ============test 

日志位置

一个项目中会有很多的日志采集点,而日志采集点必须结合业务属性来设置。比如在登录代码执行前可以插入“准备登录..”日志信息,如果登录完成之后,再设置登录的提示日志就会给人造成误解,无法判断到底是登录之前的问题还是登录之后的问题,因此日志采集点的位置很重要。

logging模块

简介

Python的logging模块提供了通用的日志系统,这个模块提供不同的日志级别,并可以采用不同的方式记录日志,比如文件,HTTP GET/POST,SMTP,Socket等,甚至可以自己实现方式记录日志。

#导入logging模块
import logging

logging构成

logging模块包括logger,Handler,Filter,Formatter四个部分。

  • Logger 记录器,用于设置日志采集。
  • Handler 处理器,将日志记录发送至合适的路径。
  • Filter 过滤器,提供了更好的粒度控制,它可以决定输出哪些日志记录。
  • Formatter 格式化器,指明了最终输出中日志的格式。

Logger 记录器

Logger是一个树形层级结构,在使用接口debug,info,warn,error,critical;使用之前必须创建Logger实例,即创建一个记录器,如果没有显式的进行创建,则默认创建一个root logger,并应用默认的日志级别(WARN),Handler和Formatter。

方法:

basicConfig(**kwargs) 为日志记录系统做基本配置。

部分参数

filename 指定日志文件名称

filemode 指定打开文件的模式,如果指定了filename(如果文件模式未指定,则默认为'a)

Tips:文件读写模式

  • w 以写方式打开,
  • W 文件若存在,首先要清空,然后(重新)创建
  • a 以追加模式打开 (从 EOF 开始, 必要时创建新文件)
  • r+ 以读写模式打开
  • w+ 以读写模式打开 (参见 w )
  • a+ 以读写模式打开 (参见 a )

format 为处理程序使用指定的格式字符串。

datefmt 使用指定的日期/时间格式。样式如果指定了格式字符串,则使用它来指定 格式字符串的类型.

level 将根记录器级别设置为指定级别。

import logging

# logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.INFO)

logging.debug('debug info')
logging.info('hello 51zxw !')
logging.warning('warning info')
logging.error('error info')
logging.critical('critical info')

Handler 处理器

Handler 处理器,将日志记录发送至合适的路径,Handler处理器类型有很多种,比较常用的有三个:

1.StreamHandler

将日志记录输出发送到诸如sys.stdout,sys.stderr或任何类似文件流的对象。上面例子就是输出到控制台

2.FileHandler

将日志记录输出发送到磁盘文件。 它继承了StreamHandler的输出功能。

logging.basicConfig(filename='runlog.log',level=logging.DEBUG)
 
3.NullHandler

不做任何格式化或输出。 它本质上是一个开发人员使用的“无操作”处理程序。

Filter 过滤器

Handlers和Loggers可以使用Filters来完成比级别更复杂的过滤。

Formatter

使用Formatter对象设置日志信息最后的规则、结构和内容,默认的时间格式为%Y-%m-%d %H:%M:%S。

格式

描述

%(levelno)s

打印日志级别的数值

%(levelname)s

打印日志级别名称

%(pathname)s

打印当前执行程序的路径

%(filename)s

打印当前执行程序名称

%(funcName)s

打印日志的当前函数

%(lineno)d

打印日志的当前行号

%(asctime)s

打印日志的时间

%(thread)d

打印线程id

%(threadName)s

打印线程名称

%(process)d

打印进程ID

%(message)s

打印日志信息

使用方法:

logging.basicConfig(filename='runlog.log',level=logging.DEBUG,
                  format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s')
                  

输出结果:

2018-04-19 17:25:04,679 logging_test.py[line:12] DEBUG debug info
2018-04-19 17:25:04,680 logging_test.py[line:13] INFO hello 51zxw 2020
2018-04-19 17:25:04,680 logging_test.py[line:14] WARNING waning info
2018-04-19 17:25:04,680 logging_test.py[line:15] ERROR error info
2018-04-19 17:25:04,680 logging_test.py[line:16] CRITICAL critical info

Logging实战操作

测试场景

将前面所学的启动考研帮App的脚本增加log采集功能,设置指定的日志格式输出,并将日志保存到指定文件。

代码实现

kyb_logger.py

from appium import webdriver
import yaml
from selenium.common.exceptions import NoSuchElementException
import logging

file=open('../yaml/desired_caps.yaml','r')
data=yaml.load(file)

logging.basicConfig(level=logging.INFO,filename='runlog.log',
                    format='%(asctime)s %(filename)s[line:%(lineno)d]%(levelname)s%(message)s')

desired_caps={}
desired_caps['platformName']=data['platformName']
desired_caps['platformVersion']=data['platformVersion']
desired_caps['deviceName']=data['deviceName']

desired_caps['app']=data['app']
desired_caps['appPackage']=data['appPackage']
desired_caps['appActivity']=data['appActivity']
desired_caps['noReset']=data['noReset']

logging.info('start app...')
driver=webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub',desired_caps)

def check_cancelBtn():
    logging.info('check cancelBtn')

    try:
        cancelBtn = driver.find_element_by_id('android:id/button2')
    except NoSuchElementException:
        logging.info('no cancelBtn')
    else:
        cancelBtn.click()

def check_skipBtn():
    logging.info('check skipBtn')

    try:
        skipBtn = driver.find_element_by_id('com.tal.kaoyan:id/tv_skip')
    except NoSuchElementException:
        logging.info('no skipBtn')
    else:
        skipBtn.click()

check_cancelBtn()
check_skipBtn()

日志格式配置

log输出格式,输出路径等参数抽离出来作为一个配置表,如下所示:

[loggers]
keys=root,infoLogger

[logger_root]
level=DEBUG
handlers=consoleHandler,fileHandler

[logger_infoLogger]
handlers=consoleHandler,fileHandler
qualname=infoLogger
propagate=0

[handlers]
keys=consoleHandler,fileHandler

[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=form02
args=(sys.stdout,)

[handler_fileHandler]
class=FileHandler
level=INFO
formatter=form01
args=('runlog.log', 'a')

[formatters]
keys=form01,form02

[formatter_form01]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s

[formatter_form02]
format=%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s


在需要调用的模块增加如下代码:
import logging
import logging.config


CON_LOG='log.conf'
logging.config.fileConfig(CON_LOG)
logging=logging.getLogger()

方法:

fileConfig(fname, defaults=None, disable_existing_loggers=True)

该放在作用是从ConfigParser格式的文件中读取日志配置,同时如果当前脚本有配置log参数,则覆盖当前log配置选项。

代码实现

kyb_logconf.py

from appium import webdriver
import yaml
from selenium.common.exceptions import NoSuchElementException

import logging
import logging.config

stream=open('../yaml/desired_caps.yaml','r')
data=yaml.load(stream)

CON_LOG='log.conf'
logging.config.fileConfig(CON_LOG)
logging=logging.getLogger()


desired_caps={}
desired_caps['platformName']=data['platformName']

desired_caps['platformVersion']=data['platformVersion']
desired_caps['deviceName']=data['deviceName']

desired_caps['app']=data['app']
desired_caps['noReset']=data['noReset']

desired_caps['appPackage']=data['appPackage']
desired_caps['appActivity']=data['appActivity']

driver = webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub', desired_caps)

def check_updateBtn():
    logging.info("check_pdateBtn")

    try:
        element = driver.find_element_by_id('android:id/button2')
    except NoSuchElementException:
        logging.info('update element is not found!')
    else:
        element.click()


def check_skipBtn():
    logging.info("check_skipBtn")
    try:
        element = driver.find_element_by_id('com.tal.kaoyan:id/tv_skip')
    except NoSuchElementException:
        logging.info('skipBtn element is not found!')
    else:
        element.click()

check_updateBtn()
check_skipBtn()
import os
import time
import logging


class Logeer:
    def __init__(self,logger):
        base_path=os.path.join(os.path.dirname(os.path.abspath(".")),"logs")
        log_file_path=os.path.join(base_path,time.strftime("%Y%m%d%H%M%S"))+".log"
        if not os.path.exists(base_path): os.mkdir(base_path)
        self.logger=logging.getLogger(logger)
        self.logger.setLevel(logging.DEBUG)
        #创建日志生成器handle
        fh=logging.FileHandler(log_file_path,"w",encoding="utf-8")
        ch=logging.StreamHandler()


        #设置日志生成格式
        formmter=logging.Formatter("%(asctime)s %(module)s %(levelname)s %(funcName)s  %(message)s ")

        #将格式添加到生成器中

        fh.setFormatter(formmter)
        ch.setFormatter(formmter)

        #添加handle

        self.logger.addHandler(fh)
        self.logger.addHandler(ch)


    def get_logs(self):
        return self.logger

前面我们都是基于线性模型来编写测试脚本,而且元素定位方式和属性值都是写死的。在业务场景简单的情况下这样写无可厚非,但是一旦遇到产品需求变更,业务逻辑比较复杂需要维护的时候就非常麻烦了,那么该如何应对这种情况呢?

场景案例

结合前面我们所学,测试考研帮App登录场景,按照线性模型来构造出脚本如下: 考研帮登录测试场景

from appium import webdriver
import yaml
from selenium.common.exceptions import NoSuchElementException
import logging
import logging.config


CON_LOG='../log/log.conf'
logging.config.fileConfig(CON_LOG)
logging=logging.getLogger()


stream=open('../yaml/desired_caps.yaml','r')
data=yaml.load(stream)

desired_caps={}
desired_caps['platformName']=data['platformName']

desired_caps['platformVersion']=data['platformVersion']
desired_caps['deviceName']=data['deviceName']

desired_caps['app']=data['app']
desired_caps['noReset']=data['noReset']

desired_caps['unicodeKeyboard']=data['unicodeKeyboard']
desired_caps['resetKeyboard']=data['resetKeyboard']

desired_caps['appPackage']=data['appPackage']
desired_caps['appActivity']=data['appActivity']

driver = webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub', desired_caps)

def check_updateBtn():
     logging.info("check_updateBtn")

    try:
        element = driver.find_element_by_id('android:id/button2')
    except NoSuchElementException:
        logging.info('update element is not found!')
    else:
        element.click()


def check_skipBtn():
    logging.info("check_skipBtn")
    try:
        element = driver.find_element_by_id('com.tal.kaoyan:id/tv_skip')
    except NoSuchElementException:
        logging.info('skipBtn element is not found!')
    else:
        element.click()

check_updateBtn()
check_skipBtn()
logging.info('start login...')
 
driver.find_element_by_id('com.tal.kaoyan:id/login_email_edittext').send_keys('自学网2018')
driver.find_element_by_id('com.tal.kaoyan:id/login_password_edittext').send_keys('zxw2018')
driver.find_element_by_id('com.tal.kaoyan:id/login_login_btn').click()
logging.info('login finished')

上面的脚本看似都比较完善,有了log采集,参数配置、启动时页面元素自动检测。但是也存在一些不足之处:

  • 公共模块和业务模块混合在一起显得代码冗余等
  • 测试场景单一(如果要实现如下测试场景该怎么办?)
  • 元素定位属性和代码混杂在一起

以上这些都是需要优化的地方。

测试场景

操作步骤

预期结果

多账号登录

不同的用户名密码来进行登录

能够正常登录

异常登录

用户名或者密码错误、或者为空进行登录,

登录失败,同时界面要给出相应的提示

注册

点击注册,然后进行注册信息填写

能够注册成功

重构优化思路

  • 将一些公共的内容(如:check_updateBtn,check_skipBtn,capability)抽离出来。
  • 元素定位方法和元素属性值与业务代码分离
  • 登录功能模块封装为一个独立的模块
  • 使用unittest进行用例综合管理

Page Object

Page Object是Selenium自动化测试项目开发实践的最佳设计模式之一,通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化,只需要调整页面元素封装的代码,提高测试用例的可维护性。

脚本实现

封装App启动配置信息 desired_caps.py

import yaml
import logging.config
from appium import webdriver


CON_LOG = '../log/log.conf'
logging.config.fileConfig(CON_LOG)
logging = logging.getLogger()


def appium_desired():

    stream = open('../yaml/desired_caps.yaml', 'r')
    data = yaml.load(stream)

    desired_caps={}
    desired_caps['platformName']=data['platformName']

    desired_caps['platformVersion']=data['platformVersion']
    desired_caps['deviceName']=data['deviceName']

    desired_caps['app']=data['app']
    desired_caps['noReset']=data['noReset']

    desired_caps['unicodeKeyboard']=data['unicodeKeyboard']
    desired_caps['resetKeyboard']=data['resetKeyboard']

    desired_caps['appPackage']=data['appPackage']
    desired_caps['appActivity']=data['appActivity']

    logging.info('start run app...')
    driver = webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub', desired_caps)

    driver.implicitly_wait(8)
    return driver

if __name__ == '__main__':
    appium_desired()

记得在原来的yaml配置表desired_caps.yaml补充如下内容:

unicodeKeyboard: True 
resetKeyboard: True

封装基类: baseView.py

class BaseView(object):
    def __init__(self,driver):
        self.driver=driver

    def find_element(self,*loc):
        return self.driver.find_element(*loc)

封装通用公共类 common_fun.py

from appium_advance.page_object.baseView import BaseView
from selenium.common.exceptions import NoSuchElementException
import logging
from selenium.webdriver.common.by import By
from appium_advance.page_object.desired_caps import appium_desired
 
class Common(BaseView):
 
    cancelBtn=(By.ID,'android:id/button2')
    skipBtn=(By.ID,'com.tal.kaoyan:id/tv_skip')
 
    def check_cancelBtn(self):
        logging.info("============check_cancelBtn===============")
 
        try:
            element = self.driver.find_element(*self.cancelBtn)
        except NoSuchElementException:
            logging.info('update element is not found!')
        else:
            logging.info('click cancelBtn')
            element.click()
 
    def check_skipBtn(self):
        logging.info("==========check_skipBtn===========")
        try:
            element = self.driver.find_element(*self.skipBtn)
        except NoSuchElementException:
            logging.info('skipBtn element is not found!')
        else:
            logging.info('click skipBtn')
            element.click()
 
if __name__ == '__main__':
 
    driver=appium_desired()
    com=Common(driver)
    com.check_updateBtn()
    com.check_skipBtn()
 

补充资料:By方式元素定位视频讲解

封装登录操作 loginView.py

import logging
from  appium_advance.page_object.common_fun import Common
from  appium_advance.page_object.desired_caps import appium_desired
from selenium.webdriver.common.by import By
 
class LoginView(Common):
 
    username_type=(By.ID,'com.tal.kaoyan:id/login_email_edittext')
    password_type=(By.ID,'com.tal.kaoyan:id/login_password_edittext')
    loginBtn=(By.ID,'com.tal.kaoyan:id/login_login_btn')
 
 
    def login_action(self,username,password):
        self.check_cancelBtn()
        self.check_skipBtn()
 
        logging.info('===============login===============')
        logging.info('input username:%s'%username)
        self.driver.find_element(*self.username_type).send_keys(username)
 
        logging.info('input password:%s'%password)
        self.driver.find_element(*self.password_type).send_keys(password)
 
        logging.info('click loginBtn.')
        self.driver.find_element(*self.loginBtn).click()
        logging.info('login finished ')
 
if __name__ == '__main__':
    driver=appium_desired()
    l=LoginView(driver)
    l.login_action('自学网2018','zxw2018')

unittest用例封装

测试场景

使用如下账号进行分别登录测试

用户名

密码

自学网2018

zxw2018

自学网2017

zxw2017

666

222

Tips必备基础知识:Selenium自动化第六章-unittest单元测试框架

1.封装用例启动结束时的配置: myunit.py

import unittest
from appium_advance.page_object.desired_caps import appium_desired
import logging
from  time import sleep

class StartEnd(unittest.TestCase):

    def setUp(self):
        logging.info('======setUp=========')
        self.driver=appium_desired()


    def tearDown(self):
        logging.info('======tearDown=====')
        sleep(5)
        self.driver.close_app()


2.用例封装 test_login.py
from  appium_advance.unittest.myunit import StartEnd
from appium_advance.page_object.loginView import LoginView
import unittest
import logging

class TestLogin(StartEnd):


    def test_login_zxw2018(self):
        logging.info('=========test_login_zxw2018============')
        l=LoginView(self.driver)
        l.login_action('自学网2018','zxw2018')


    def test_login_zxw2017(self):
        logging.info('==========test_login_zxw2017========')
        l=LoginView(self.driver)
        l.login_action('自学网2017','zxw2017')


    def test_login_error(self):
        logging.info('=======test_login_error=========')
        l=LoginView(self.driver)
        l.login_action('666','222')


if __name__ == '__main__':
    unittest.main()

 

 

  

  

  

 
 

  

  

  

  

  

  

  

  

  

  

  

    

  

  

  

  

  

 

  

  

  

  

  

 

posted @ 2020-11-25 17:28  自学随笔  阅读(171)  评论(0)    收藏  举报