https://github.com/NetEaseGame/ATX

系统环境

cmd@TR:~$ lsb_release -a
No LSB modules are available.
Distributor ID:    Ubuntu
Description:    Ubuntu 16.04.3 LTS
Release:    16.04
Codename:    xenial

安装记录

cmd@TR:~$pyenv install --list

cmd@TR:~$ pyenv install 3.6.3
Downloading Python-3.6.3.tar.xz...
-> https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tar.xz
Installing Python-3.6.3...
WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib?
WARNING: The Python readline extension was not compiled. Missing the GNU readline lib?
WARNING: The Python sqlite3 extension was not compiled. Missing the SQLite3 lib?
Installed Python-3.6.3 to /home/cmd/.pyenv/versions/3.6.3

cmd@TR:~$ pyenv versions
* system (set by /home/cmd/.pyenv/version)
  3.6.3
  3.6.3/envs/HttpRunner363
  HttpRunner363

cmd@TR:~$ pyenv virtualenv 3.6.3 ATX363
Requirement already satisfied: setuptools in /home/cmd/.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages
Requirement already satisfied: pip in /home/cmd/.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages

cmd@TR:~$ pyenv versions
* system (set by /home/cmd/.pyenv/version)
  3.6.3
  3.6.3/envs/ATX363
  3.6.3/envs/HttpRunner363
  ATX363
  HttpRunner363

cmd@TR:~$ pyenv activate ATX363
pyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.

(ATX363) cmd@TR:~$ pip list
DEPRECATION: The default format will switch to columns in the future. You can use --format=(legacy|columns) (or define a format=(legacy|columns) in your pip.conf under the [list] section) to disable this warning.
pip (9.0.1)
setuptools (28.8.0)


(ATX363) cmd@TR:~$ pip install --upgrade --pre atx
Collecting atx
  Using cached atx-1.1.3.dev36-py3-none-any.whl
Collecting requests>=2.9.1 (from atx)
  Using cached requests-2.18.4-py2.py3-none-any.whl
Collecting atx-uiautomator==0.3.3 (from atx)
  Using cached atx_uiautomator-0.3.3-py3-none-any.whl
Collecting numpy>=1.11.0 (from atx)
  Using cached numpy-1.14.0-cp36-cp36m-manylinux1_x86_64.whl
Collecting imageio>=1.5 (from atx)
  Using cached imageio-2.2.0.tar.gz
Collecting Pillow>=2.7.0 (from atx)
  Using cached Pillow-5.0.0-cp36-cp36m-manylinux1_x86_64.whl
Collecting futures==3.0.5 (from atx)
  Using cached futures-3.0.5.tar.gz
Collecting tornado>=4.4 (from atx)
  Using cached tornado-5.0a1.tar.gz
  Requested tornado>=4.4 from https://pypi.python.org/packages/78/6d/2edcda81b05aa1142bc33e0f4917314e898aec044b826efa7fdc2e5c94a1/tornado-5.0a1.tar.gz#md5=9a0198c4b04f22e2dbd3d1ae6cb2dc8c (from atx), but installing version None
Collecting AxmlParserPY==0.0.3 (from atx)
  Using cached AxmlParserPY-0.0.3.tar.gz
Collecting tqdm==4.5.0 (from atx)
  Using cached tqdm-4.5.0-py2.py3-none-any.whl
Collecting facebook-wda>=0.2.0 (from atx)
  Using cached facebook_wda-0.2.2.dev1-py3-none-any.whl
Collecting colorama>=0.3.7 (from atx)
  Using cached colorama-0.3.9-py2.py3-none-any.whl
Collecting PyYAML==3.11 (from atx)
  Using cached PyYAML-3.11.zip
Collecting maproxy==0.0.12 (from atx)
  Using cached maproxy-0.0.12.zip
Collecting aircv==1.4.6 (from atx)
  Using cached aircv-1.4.6.tar.gz
Collecting chardet<3.1.0,>=3.0.2 (from requests>=2.9.1->atx)
  Using cached chardet-3.0.4-py2.py3-none-any.whl
Collecting idna<2.7,>=2.5 (from requests>=2.9.1->atx)
  Using cached idna-2.6-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests>=2.9.1->atx)
  Using cached certifi-2018.1.18-py2.py3-none-any.whl
Collecting urllib3<1.23,>=1.21.1 (from requests>=2.9.1->atx)
  Using cached urllib3-1.22-py2.py3-none-any.whl
Collecting six (from atx-uiautomator==0.3.3->atx)
  Using cached six-1.11.0-py2.py3-none-any.whl
Installing collected packages: chardet, idna, certifi, urllib3, requests, six, atx-uiautomator, numpy, Pillow, imageio, futures, tornado, AxmlParserPY, tqdm, facebook-wda, colorama, PyYAML, maproxy, aircv, atx
  Running setup.py install for imageio ... done
  Running setup.py install for futures ... done
  Running setup.py install for tornado ... done
  Running setup.py install for AxmlParserPY ... done
  Running setup.py install for PyYAML ... done
  Running setup.py install for maproxy ... done
  Running setup.py install for aircv ... done
Successfully installed AxmlParserPY-0.0.3 Pillow-5.0.0 PyYAML-3.11 aircv-1.4.6 atx-1.1.3.dev36 atx-uiautomator-0.3.3 certifi-2018.1.18 chardet-3.0.4 colorama-0.3.9 facebook-wda-0.2.2.dev1 futures-3.0.5 idna-2.6 imageio-2.2.0 maproxy-0.0.12 numpy-1.14.0 requests-2.18.4 six-1.11.0 tornado-5.0a1 tqdm-4.5.0 urllib3-1.22



(ATX363) cmd@TR:~$  pip install opencv_contrib_python
Collecting opencv_contrib_python
  Using cached opencv_contrib_python-3.4.0.12-cp36-cp36m-manylinux1_x86_64.whl
Requirement already satisfied: numpy>=1.11.3 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from opencv_contrib_python)
Installing collected packages: opencv-contrib-python
Successfully installed opencv-contrib-python-3.4.0.12

检查输出和一些帮助提示

(ATX363) cmd@TR:~$ python -m atx version
1.1.3.dev36
(ATX363) cmd@TR:~$ python -m atx doctor
Android Debug Bridge version 1.0.39
Revision 3db08f2c6889-android
Installed as /opt/android-sdk-linux/platform-tools/adb
✔ adb found in env PATH

(pyenv350) cmd@TR:~$ python -m atx -h
usage: __main__.py [-h] [-s SERIAL] [-H HOST] [-P PORT]
                   {tcpproxy,gui,minicap,apkparse,monkey,install,screen,screencap,screenrecord,web,run,version,info,doctor}
                   ...

positional arguments:
  {tcpproxy,gui,minicap,apkparse,monkey,install,screen,screencap,screenrecord,web,run,version,info,doctor}

optional arguments:
  -h, --help            show this help message and exit
  -s SERIAL, --serial SERIAL, --udid SERIAL
                        Android serial or iOS unid (default: None)
  -H HOST, --host HOST  Adb host (default: 127.0.0.1)
  -P PORT, --port PORT  Adb port (default: 5037)



(ATX363) cmd@TR:~$ python -m atx gui -h
usage: __main__.py gui [-h] [-p {auto,android,ios}] [-s SERIAL]
                       [--scale SCALE]

GUI tool to help write test script

optional arguments:
  -h, --help            show this help message and exit
  -p {auto,android,ios}, --platform {auto,android,ios}
                        platform (default: auto)
  -s SERIAL, --serial SERIAL
                        android serial or WDA device url (default: None)
  --scale SCALE         scale size (default: 0.5)

配置和使用 weditor
参考:
https://testerhome.com/topics/7978
https://github.com/openatx/weditor
https://github.com/openatx/uiautomator2

(ATX363) cmd@TR:~$ pip install --pre --upgrade weditor
Collecting weditor
  Using cached weditor-0.0.4.dev6.tar.gz
Requirement already up-to-date: tornado>=4.3 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from weditor)
Requirement already up-to-date: futures==3.0.5 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from weditor)
Requirement already up-to-date: atx>=1.1.1.dev47 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from weditor)
Requirement already up-to-date: six in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from weditor)
Requirement already up-to-date: PyYAML==3.11 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: aircv==1.4.6 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: numpy>=1.11.0 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: requests>=2.9.1 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: atx-uiautomator==0.3.3 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: imageio>=1.5 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: maproxy==0.0.12 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: AxmlParserPY==0.0.3 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: tqdm==4.5.0 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: facebook-wda>=0.2.0 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: Pillow>=2.7.0 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: colorama>=0.3.7 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: idna<2.7,>=2.5 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from requests>=2.9.1->atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: urllib3<1.23,>=1.21.1 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from requests>=2.9.1->atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: certifi>=2017.4.17 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from requests>=2.9.1->atx>=1.1.1.dev47->weditor)
Requirement already up-to-date: chardet<3.1.0,>=3.0.2 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from requests>=2.9.1->atx>=1.1.1.dev47->weditor)
Installing collected packages: weditor
  Running setup.py install for weditor ... done
Successfully installed weditor-0.0.4.dev6


(ATX363) cmd@TR:~$ pip install --pre uiautomator2
Collecting uiautomator2
  Using cached uiautomator2-0.0.4.dev1.tar.gz
Requirement already satisfied: requests>=2.7.0 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from uiautomator2)
Requirement already satisfied: six in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from uiautomator2)
Collecting humanize (from uiautomator2)
  Using cached humanize-0.5.1.tar.gz
Collecting fire (from uiautomator2)
  Using cached fire-0.1.2.tar.gz
Collecting progress>=1.3 (from uiautomator2)
  Using cached progress-1.3.tar.gz
Collecting retry>=0.9.2 (from uiautomator2)
  Using cached retry-0.9.2-py2.py3-none-any.whl
Requirement already satisfied: urllib3<1.23,>=1.21.1 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from requests>=2.7.0->uiautomator2)
Requirement already satisfied: chardet<3.1.0,>=3.0.2 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from requests>=2.7.0->uiautomator2)
Requirement already satisfied: certifi>=2017.4.17 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from requests>=2.7.0->uiautomator2)
Requirement already satisfied: idna<2.7,>=2.5 in ./.pyenv/versions/3.6.3/envs/ATX363/lib/python3.6/site-packages (from requests>=2.7.0->uiautomator2)
Collecting py<2.0.0,>=1.4.26 (from retry>=0.9.2->uiautomator2)
  Using cached py-1.5.2-py2.py3-none-any.whl
Collecting decorator>=3.4.2 (from retry>=0.9.2->uiautomator2)
  Using cached decorator-4.2.1-py2.py3-none-any.whl
Installing collected packages: humanize, fire, progress, py, decorator, retry, uiautomator2
  Running setup.py install for humanize ... done
  Running setup.py install for fire ... done
  Running setup.py install for progress ... done
  Running setup.py install for uiautomator2 ... done
Successfully installed decorator-4.2.1 fire-0.1.2 humanize-0.5.1 progress-1.3 py-1.5.2 retry-0.9.2 uiautomator2-0.0.4.dev1

启动模拟器

cmd@TR:~$ adb devices -l
List of devices attached
192.168.56.101:5555    device product:vbox86p model:Samsung_Galaxy_S7___6_0_0___API_23___1440x2560 device:vbox86p

Genymotion 用 weditor 先需要打补丁http://blog.csdn.net/yyongchao/article/details/72896907

向模拟器安装 apk, atx-agent, minicap, minitouch 后手机会启动 atx-agent 也就是手机里图标 uiautomator 这个

(ATX363) cmd@TR:~$ python -m uiautomator2 init
2018-01-23 10:48:48,565 - __main__.py:241 - INFO - Device(192.168.56.101:5555) initialing ...
2018-01-23 10:48:48,610 - __main__.py:120 - INFO - install minicap
2018-01-23 10:48:48,636 - __main__.py:127 - INFO - install minitouch
2018-01-23 10:48:48,674 - __main__.py:142 - INFO - apk(1.0.9) already installed, skip
2018-01-23 10:48:48,681 - __main__.py:175 - INFO - atx-agent(0.1.5) is installing, please be patient
atx-agent_0.1.5_linux_386.tar.gz |################################| 3.0 MiB / 3.0 MiB
2018-01-23 10:48:59,786 - __main__.py:211 - INFO - atx-agent output: 2018/01/23 02:49:23 tunnelproxy.go:47: get cpuinfo error: Invalid cpuinfo data
server started, listening on 192.168.66.110:7912
2018-01-23 10:48:59,786 - __main__.py:212 - INFO - success

启动 weditor

(ATX363) cmd@TR:~$ python -m weditor

启动后会自动打开一网址,点击下里边的 Connect 连接 只有一个设备的话可为空直接点

换小米 mix2 真机
开启开发者模式,开启 usb 调试模式

步骤同上:

  1. adb 查看是否连接到真机
  2. 向真机安装 apk, atx-agent, minicap, minitouch
  3. 启动 weditor
(ATX363) cmd@TR:~$ adb devices -l
List of devices attached
serialnumber               device usb:3-1 product:chiron model:MIX_2 device:chiron

(ATX363) cmd@TR:~$ python -m uiautomator2 init


(ATX363) cmd@TR:~$ python -m weditor

配置 pycharm 使用 ATX363
先找到 python 虚拟环境 ATX363 的路径

(ATX363) cmd@TR:~$ cd .pyenv/versions/
(ATX363) cmd@TR:~/.pyenv/versions$ pwd
/home/cmd/.pyenv/versions
(ATX363) cmd@TR:~/.pyenv/versions$ ls -al
总用量 12
drwxrwxr-x  3 cmd cmd 4096 1月  23 17:10 .
drwxrwxr-x 13 cmd cmd 4096 12月 22 10:07 ..
drwxr-xr-x  7 cmd cmd 4096 1月  23 16:44 3.6.3
lrwxrwxrwx  1 cmd cmd   43 1月  23 17:10 ATX363 -> /home/cmd/.pyenv/versions/3.6.3/envs/ATX363
lrwxrwxrwx  1 cmd cmd   50 1月  23 16:58 HttpRunner363 -> /home/cmd/.pyenv/versions/3.6.3/envs/HttpRunner363

在 ubuntu 上使用 pyenv 实现 Python 多版本共存后,pyenv 安装的 Python 版本存在 ubuntu 下的 ~/.pyenv/versions/下。
在 Pycharm 时,选择此目录下对应的版本即可。
具体操作步骤 (以 ubuntu 版本为例):
File-> Settings-> Project -> Project Interpreter -> Add Local
在 Existing environment 中选择/home/cmd/.pyenv/versions/ATX363/bin/python 即可

atx gui 工具

启动报错

(ATX363) cmd@TR:~$ python -m atx gui
Traceback (most recent call last):
  File "/home/cmd/.pyenv/versions/3.6.3/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/home/cmd/.pyenv/versions/3.6.3/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/cmd/.pyenv/versions/ATX363/lib/python3.6/site-packages/atx/__main__.py", line 171, in <module>
    main()
  File "/home/cmd/.pyenv/versions/ATX363/lib/python3.6/site-packages/atx/__main__.py", line 168, in main
    args.func(args)
  File "/home/cmd/.pyenv/versions/ATX363/lib/python3.6/site-packages/atx/__main__.py", line 45, in _inner
    __import__(module_path)
  File "/home/cmd/.pyenv/versions/ATX363/lib/python3.6/site-packages/atx/cmds/tkgui.py", line 20, in <module>
    import Tkinter as tk
ModuleNotFoundError: No module named 'Tkinter'

解决方法:
\atx\cmds\tkgui.py 可以看到 import 的相关代码如下:

import Tkinter as tk
import tktkSimpleDialog
import tkFileDialog
from Queue import Queue
相关的写法是基于 Python2 的,在 Python3 的环境下运行时会存在出错的情况
根据网上的资料:http://blog.csdn.net/u011127242/article/details/54950238

将 tkgui.py 相关 import 的代码修改为:

import tkinter as tk
from tkinter import simpledialog as tkSimpleDialog
from tkinter import filedialog as tkFileDialog
from queue import Queue
保存文件之后,再次执行 python -m atx gui

这工具没啥用,貌似就是截图用的

atx-webide
https://github.com/openatx/atx-webide
https://testerhome.com/topics/5923
已经不维护了,别用了

ATX 包使用

随着 ATX 不断的发展,UI 层面的 Android 以及 iOS 操作已经分离出来两个不同的项目。
对于 Android 应用的测试,如果不需要用到图像识别,推荐使用这个项目 uiautomator2
对于 iOS 应用的测试,如果不需要用到图像识别,推荐使用这个项目 facebook-wda
atx 是基于坐标识别的,与传统的 android 的 uiautomator 基于元素识别不同,请具体看 atx API 或 atx-uiautomator2 API.

参考:
https://testerhome.com/topics/5420
https://github.com/NetEaseGame/ATX/blob/master/docs/API.md

参考写的简单样例:

# coding: utf-8
import atx

d = atx.connect()
print('CellPhone serial number:',d.serial)
start = d.start_app('com.***.***','.ui.MainActivity')
print('start time:',start)
print('current PackageName:',d.current_package_name)
print('current App:',d.current_app())
d.screenshot('screen.png')

def main():
    d(text='我的').click()
    d(text='登录/注册').click()
    print('current input method:',d.current_ime())
    d(resourceId='com.huasheng.stock:id/account_num').set_text('13699987852')

if __name__ == '__main__':
    main()

pycharm 里 点不出来 atx 包里的方法 咋解决...
https://my.oschina.net/pierrecai/blog/1142711 暂时无法解决。。。得作者写法中支持。

找到了这个 http://atx.readthedocs.io/en/latest/ 可以直接看 atx api 直接写了,试验了几个都可以

atx 封装的 python 的 uiautomator2

Android Uiautomator2 Python Wrapper 这是一个可以完成 Android 的 UI 自动化的 python 库。该项目还在火热的开发中
google 提供的 uiautomator 库功能做起安卓自动化来非常强大,唯独有两个缺点:1. 只能在手机上运行 2. 只能使用 java 语言。 所以为了能更简单快捷的使用 uiautomator,这个项目通过在手机上运行了一个 http 服务的方法,将 uiautomator 中的函数开放了出来。然后再将这些 http 接口,封装成了 python 库。这里要非常感谢 Xiaocong He (@xiaocong),他将这个想法实现了出来,uiautomator2 这个项目则是对原有 xiaocong 的项目 uiautomator 进行了 bug 的修改,功能进行了加强。具体有以下
修复 uiautomator 经常性退出的问题
代码进行了重构和精简,方便维护
增加了脱离数据线运行测试的功能
通过 minicap 加快截图速度

参考:
https://testerhome.com/topics/11357
https://github.com/openatx/uiautomator2
https://github.com/openatx/uiautomator2#basic-api-usages
https://developer.android.com/reference/android/support/test/uiautomator/package-summary.html

参考写的简单样例:

# coding=utf-8
import uiautomator2 as u2
import time

d = u2.connect('')
print(d.device_info)
d.app_start('com.***.***','.ui.MainActivity')

def main():
   d(text='我的').click()
   d(text='登录/注册').click()
   d(resourceId='com.huasheng.stock:id/account_num').send_keys('13751077575')
   d(resourceId='com.huasheng.stock:id/pwd').send_keys('%^&*($%^')
   d(text='登录').click()
   time.sleep(3)
   assert d(textContains="帐号或密码错误").exists

if __name__ == '__main__':
   main()

因为没自动补全
为了找到是否有 text 包含或 match 方法关键词
痛苦.... 我 debug 才找到 d(text='我的').click() 这里用的 text
super 自 这里 uiautomator2.Selector

也就是官方的这个封装的:
https://developer.android.com/reference/android/support/test/uiautomator/BySelector.html?hl=zh-cn

引入 unittest 写法后改造

# coding=utf-8
import uiautomator2 as u2
import time
import unittest

packagename = 'com.***.***'
activity = '.ui.MainActivity'

class U2Tests(unittest.TestCase):

   def setUp(self):
      self.d = u2.connect('')
      print(self.d.device_info)
      self.d.app_start(packagename,activity)


   def test_login(self):
      time.sleep(3)
      self.d(text='我的').click()
      self.d(text='登录/注册').click()
      self.d(resourceId='com.huasheng.stock:id/account_num').send_keys('13751077575')
      self.d(resourceId='com.huasheng.stock:id/pwd').set_text('12!@#$%^&*')
      self.d(text='登录').click()
      time.sleep(3)
      assert self.d(textContains="帐号或密码错误").exists,"弹窗注册内容错误提示"

 # result = self.d(textContains="帐号或密码错误").exists
 # if result is True:
 # print('Test Pass')
 # else:
 # print('Test Failed')

 def tearDown(self):
      try:
         self.d.app_stop(packagename)
      except:
         pass

if __name__ == '__main__':
   try:
      suite = unittest.TestLoader().loadTestsFromTestCase(U2Tests)
      unittest.TextTestRunner(verbosity=2).run(suite)
   except SystemExit:
      pass


↙↙↙阅读原文可查看相关链接,并与作者交流