九、Airtest引入与调用

1. 前言

很多同学在编写自动化脚本,引入别的 .py/.air 脚本时,经常遇到类似无法引入模块、或者找不到某个模块这样的报错:

  1. ImportError: cannot import name 'Select'
  2. ModuleNotFoundError: No module named 'test'

今天我们就单独聊一下这个问题,希望能给同学们一些解决思路。

2. Python标准库与第三方库

首先我们来看一个基础问题,区分下Python的标准库与第三方库。如下图所示:

image

1)Python标准库

Python的标准库,实际上就是一些内置模块,只要我们安装了Python,就自带这些内置模块,比如 randomos 等等。

使用这些库也非常简单,我们无需额外的安装,就可以直接 import 进来使用:

  1. import random
  2. print(random.random())

2)Python第三方库

那Python的第三方库呢,就是一些非官方组织或者个人,写出来的具有某些特定功能的框架,比如我们的Airtest,就是用来做UI自动化测试的一个Python第三方库;还有图中示例的Django,是用来进行web开发的一个Python第三方库。

使用这些第三方库呢,除了要有一个Python环境之外,还要在这个Python环境里面,安装上我们要用到的第三方库,比如使用Airtest和Poco:

  1. pip install airtest
  2. pip install pocoui

成功把这些第三方库安装到我们的Python环境之后,我们才能够从这些库里面 import 我们想要的功能:

  1. from airtest.core.api import *
  2. auto_setup(__file__)
  3. snapshot(msg="请填写测试点.")

3)出现 no module named 'airtest' 的解决办法

如果同学们理解了Python标准库和第三方库的区别后,再遇到类似 no module named 'airtest' 或者no moudle named 'airtest-selenium' 等问题时,就会知道是因为当前Python环境里没有安装好这些第三方库的原因了。

解决办法也非常简单,找到当前使用的Python环境,然后在该环境下安装好我们所需的第三方库即可。(特别注意如本地有多个Python环境时,需要在同学们使用的那个Python环境里安装所需的第三方库)

3. Python导入模块的几种方式

1)import os

这种导入方式常用于导入Python的标准库:

  1. # 打印当前的工作目录
  2. import os
  3. print(os.getcwd())

2)from os import chmod

这种导入方式常用于包文件的导入,当然也有同学习惯这样使用:

  1. from airtest.report.report import *

这样导入尽管不会报错,但是可读性相对较差。

3)import logging as log

这种方式给导入的模块起了别名,后续我们再用到该模块的方法时,可以用别名来调用:

  1. import logging as log
  2. log.info('this is a info message')

4. 如何知道该从哪个模块里面引入你所需的方法

我们就以Airtest框架的使用来说,我们在IDE上新建了1个 .air 脚本,默认就会带上这句引入的脚本:

  1. from airtest.core.api import *

这句话的意思是引入了 airtest.core.api 这个模块里面的所有内容,也就是Airtest的核心API。

像我们常用到的 auto_setupstart_apptouchswipe 等都是这个模块下面的。

1)出现 name 'xxx' is not defined 的解决办法

但是airtest还有很多其它模块,比如报告模块。在教程文档中,我们知道了可以用 simple_report 来生成Airtest报告,但实际编写脚本时,就可能出现如下问题:

  1. from airtest.core.api import *
  2. auto_setup(__file__,logdir=True)
  3. snapshot(msg="请填写测试点.")
  4. simple_report(__file__,logpath=True)
  5. -----
  6. Traceback (most recent call last):
  7. File "airtest\cli\runner.py", line 73, in runTest
  8. File "site-packages\six.py", line 703, in reraise
  9. File "airtest\cli\runner.py", line 70, in runTest
  10. File "D:\test\taikang_test.air\taikang_test.py", line 11, in <module>
  11. simple_report(__file__,logpath=True)
  12. NameError: name 'simple_report' is not defined
  13. -----

这是因为 simple_report 不在 airtest.core.api 这个模块里面,我们需要额外引入 simple_report 所在的模块。

那么问题来了,怎么知道 simple_report 在哪个模块下面呢?最简单的办法,打开Airtest的API文档,然后搜一下 simple_report 即可:

image

可以看到 simple_report 所在的模块是 airtest.report.report ,我们只需要在脚本里添加该模块的引入即可:

  1. from airtest.report.report import simple_report
  2. simple_report(__file__,logpath=True)

再举个例子,在模拟滑动的案例中,我们看到可以使用如下的方法来模拟长按并拖动的操作:

  1. # 长按删除应用
  2. longtouch_event = [
  3. DownEvent([908, 892]),# 待删除应用的坐标
  4. SleepEvent(2),
  5. MoveEvent([165,285]),# 删除应用的垃圾桶坐标
  6. UpEvent(0)]
  7. device().touch_proxy.perform(longtouch_event)

我们依然可以通过上述的查API文档的方式,来知道需要引入的模块:

image

这里我们附上Airtest和Poco的API文档地址:

5. 引入其它的 .air 脚本的 using() 接口

那么聊完库的使用和模块引入的方法之后,我们再来看下关于引入的另一个问题,如何引入其它的 .air 脚本。

Airtest提供了 using 接口,能够将需要引用的脚本加入 sys.path 里,其中包含的图片文件也会被加入 Template 的搜索路径中。

举个例子,我们有俩个同级的 .air 脚本,要在1个 .air 脚本引入另外一个 .air 脚本,可以这么实现:

  1. # -*- encoding=utf8 -*-
  2. __author__ = "AirtestProject"
  3. from airtest.core.api import *
  4. auto_setup(__file__)
  5. using("common.air")
  6. from common import common_function
  7. common_function()

但如果我们脱离AirtestIDE来运行这个脚本,比如在pycharm中打开这个脚本来运行,有可能会出现如下报错:

  1. Traceback (most recent call last):
  2. File "D:/test/taikang_test.air/taikang_test.py", line 13, in <module>
  3. from common import common_function
  4. ModuleNotFoundError: No module named 'common'

此时,最简单粗暴的方式是将 using 里面的相对路径改成绝对路径:

  1. using(r"D:/test/common.air")
  2. from common import common_function
  3. common_function()

如果不想改动 using 的脚本,我们还可以额外添加1个待引入脚本的 pathsys.path 里:

  1. import sys
  2. sys.path.append("D:/test/common.air")
  3. using(r"common.air")
  4. from common import common_function
  5. common_function()

添加项目根目录也可以达到类似的效果:

  1. # PROJECT_ROOT需要填写绝对路径
  2. ST.PROJECT_ROOT = "D:/test/user/project"
  3. using(r"common.air")
  4. from common import common_function
  5. common_function()

1)路径斜杠问题

填写路径的时候,需要注意使用的是/而非反斜杠\,特别是你从计算机上复制路径的时候,计算机的路径使用的都是反斜杠\,我们在代码中使用时需要手动转换过来,不然会出现以下报错:

图片

图中示例的路径中包含了很多反斜杠\(特殊字符),方法一是我们可以通过添加反斜杠\来转义,也就是说路径字符串可以写成类似这样:D:\\test\\user\\project

或者是我们可以在字符串前加一个r来定义原始字符串(忽略所有的转义字符,比如这里的反斜杠\),类似这样: r"D:\test\user\project"

2)调用的模块不存在

在调用的过程中,我们经常会遇到以下报错:

图片

上述报错的意思是,程序找不到我们想要调用的名为test1的这个模块。这时候我们可以从以下几方面来思考:

  1. 仔细核对调用的模块是否存在于你填写的路径里面;
  2. 设定默认的项目根目录PROJECT_ROOT时是否使用了相对路径而没有使用绝对路径。

如果以上都没有问题,但程序还是找不到你想调用的模块,你还可以尝试把路径加入到sys.path里面去,Python就会在sys.path的路径下搜索对应的.py文件,以上述的main.air脚本为例:

  1. # main.air
  2. from airtest.core.api import *
  3. auto_setup(__file__)
  4. #将test1.air的路径添加到sys.path里面
  5. sys.path.append(r"D:\test\user\project\test1.air")
  6. using("test1.air")
  7. from test1 import test
  8. test()

这种方法是在运行时修改sys.path,运行结束后失效。

另外你还可以通过设置环境变量PYTHONPATH来增加模块搜索的路径,设置方式与设置Path环境变量类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。

3)在IDE可以运行的脚本到命令行运行就报错

上文中,我们介绍了俩个方法实现调用,里面的例子是在IDE中运行的;但如果我们直接在命令行中使用airtest run 脚本绝对路径来运行,有可能又会出现找不到模块的情况:

图片

这是因为,在IDE里面默认会把父路径加到sys.path里面,而使用命令行跑脚本的时候,不会默认添加父路径;为避免以上错误,我们可以在脚本里手动把路径添加到sys.path里面,避免不必要的错误。

  1. sys.path.append(r"D:\test\user\project\test1.air")