前言:

    在前面提到了unittest的主要功能,但是如果只用它来写Web自动化测试,则仍稍显不足,比如,它不能生成Html格式的报告,它不能提供参数化功能等。不过我们可以借助下面要说的第三方扩展来密布这些不足。

1 HTML测试报告

HTMLTestRunner是unittest的一个扩展,它可以生成易于使用的HTML测试报告,HTMLTestRunner是在BSD许可证下发布的。

下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html

因为该扩展不支持python3,所以需要修改。

修改后的文件位置:

链接:https://pan.baidu.com/s/1pM84OvIpoNZsKDEpowIaTQ 
提取码:n6c0 

1.1 下载与安装

HTMLTestRunner使用很简单,它是一个独立的py文件,既可以把它当做python的第三方库类似使用,也可以把它当做项目的一部分来使用。

打开上面链接,下载后,将HTMLTestRunner文件放到你python的安装的Lib目录下:例 C\:Python36\Lib\。

打开python shell或者其他IDE,测试是否安装成功,import HTMLTestRunner,如果没有报错,说明安装成功。

如果把HTMLTestRunner当做项目的一部分使用,就把它放到项目目录中。

1.2 生成HTML测试报告

def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=None, save_last_run=True)

上面方法是HTMLTestRunner类的初始化方法,主要看其中的参数

stream:指定生成HTML测试报告的文件,必填

verbosity:指定日志的级别,默认为1,。如果想得到更详细的日志,则可以将参数修改为2

title:指定测试用例的标题,默认为None

description: 指定测试用例的描述,默认为None

拿上一篇测试discover的用例修改一下测试下HTMLTestRunner  (放置于testHtmlTestRunner.py文件中)

testHtmlTestRunner.py



from HTMLTestRunner import HTMLTestRunner

import unittest
import os
test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)))
suits = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")


if __name__ == '__main__':
    # 生成HTML测试报告
    fp = open("./test.html", "wb")
    runner = HTMLTestRunner(stream=fp, title="百度搜索测试报告", description="运行环境:Windows 10 ,chrome浏览器")
    runner.run(suits)
    fp.close()

测试结果输出test.html打开后的效果:

unittest怎样安装 unittest下载_测试用例


1.3 更容易读的测试报告

现在生成的测试报告并不易读,因为它仅显示测试文件名,类名和测试方法。如果随意命名的话,那将很难明白这些测试所测试的功能。

在编写功能测试用例时,每条测试用例都有标题或说明,那么能否为自动化测试用例加上中文的标题或说明呢?当然是可以的!!

在类或方法的下面使用三引号(""" """)/(''' ''')添加doc string类型的注释,这类注释在平时调用时不会显示,只有通过help()方法查看时才会被显示出来。

下面将一部分修改后的代码展示在下面(三引号里面就是新加的doc 注释):

class Calculator(unittest.TestCase):
    """这是运算类1"""
    def test_add(self):
        """这是加法运算2"""
        c = Cal(3, 5)
        result = c.add()
        self.assertEqual(result, 8)

    def test_sub(self):
        """这是减法运算2"""
        c = Cal(7, 2)
        result = c.sub()
        self.assertEqual(result, 5)

    def test_mul(self):
        """这是乘法运算2"""
        c = Cal(3, 3)
        result = c.mul()
        self.assertEqual(result, 10)

    def test_div(self):
        """这是除法运算2"""
        c = Cal(6, 2)
        result = c.div()
        self.assertEqual(result, 3)


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

还是使用上面testHtmlTestRunner.py文件进行测试,结果如下(加了注释测试测试报告):

unittest怎样安装 unittest下载_软件测试_02

1.4 测试报告文件名

从上面可以看到每次生成的测试报告文件名都是写死在文件里的,这样,每次运行都会覆盖上一次的运行结果,如果想让其动态生成,可以使用time时间模块或者uuid等,这里不做详细说明

2 数据驱动说明

数据驱动是自动化测试的一个重要功能。前面也介绍了数据文件的使用。虽然不使用单元测试框架一样可以写测试代码和使用数据文件,但这就意味着放弃了单元测试框架提供给我们的所有功能,如测试用例的断言,灵活的运行机制,结果统计以及测试报告等。这些都需要自己实现,显然非常麻烦。所以,抛开单元测试框架谈数据驱动的使用是没有意义的。

2.1 数据驱动

在unittest中,使用读取数据文件来实现参数化可以吗?当然!!!以读取csv文件为例,数据如下:

unittest怎样安装 unittest下载_测试用例_03

下面创建测试用例:

import csv
import codecs
import unittest
from time import sleep
from itertools import islice
from selenium import webdriver

chrome_driver_path = r"C:\Users\Administrator\Envs\selenuimAutoTest\Lib\site-packages\selenium\webdriver\chrome\chromedriver.exe"
driver = webdriver.Chrome(executable_path=chrome_driver_path)


class TestBaidu(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome(executable_path=chrome_driver_path)
        cls.base_url = "http://www.baidu.com"

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()


    def baidu_search(self, search_key):
        self.driver.get(self.base_url)
        self.driver.find_element_by_id("kw").send_keys(search_key)
        self.driver.find_element_by_id("su").click()
        sleep(3)

    def test_search(self):
        with codecs.open("test.csv", "r", "utf_8_sig") as f:
            data = csv.reader(f)
            for line in islice(data, 1, None):
                search_key = line[1]
                self.baidu_search(search_key)


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

结果如下:

unittest怎样安装 unittest下载_测试用例_04

由上面结果我们知道所有测试数据被当做一条用例来执行了,因为unittest是以“test”开头的测试方法来划分测试用例的,因此会当做一条用例来执行。

这样划分并不合理,因为,只要有一条数据执行失败,那么整个测试用例就失败了,所以每条数据对应一条用例更为合适,就算其中一条数据的测试用例执行失败了,也不会影响其他的,并且在定位问题时会更加简单。下面我们改一下:

import csv
import codecs
import unittest
from time import sleep
from itertools import islice
from selenium import webdriver

chrome_driver_path = r"C:\Users\Administrator\Envs\selenuimAutoTest\Lib\site-packages\selenium\webdriver\chrome\chromedriver.exe"
driver = webdriver.Chrome(executable_path=chrome_driver_path)


class TestBaidu(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome(executable_path=chrome_driver_path)
        cls.base_url = "http://www.baidu.com"

        cls.test_data = []
        with codecs.open("test.csv", "r", "utf_8_sig") as f:
            data = csv.reader(f)
            for line in islice(data, 1, None):
                cls.test_data.append(line)

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()


    def baidu_search(self, search_key):
        self.driver.get(self.base_url)
        self.driver.find_element_by_id("kw").send_keys(search_key)
        self.driver.find_element_by_id("su").click()
        sleep(3)

    def test_search_selenium(self):
        self.baidu_search(self.test_data[0][1])

    def test_search_unittest(self):
        self.baidu_search(self.test_data[1][1])

    def test_search_parameterized(self):
        self.baidu_search(self.test_data[2][1])


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

测试结果如下:

unittest怎样安装 unittest下载_chrome_05

从上面测试结果可以看出,三条数据被当做了三个用例来执行,那么是不是完美解决问题了呢?读取数据文件有没有那些问题?当然!!

(1) 增加了读取的成本,不管什么样的数据文件,在运行自动化测试用例前都需要将文件中的数据读取到程序中,这一步是必不可少的。

(2) 不方便维护。读取数据文件是为了方便维护,但事实上恰恰相反。在csv数据文件中,并不能只管体现出每一条数据对应的测试用例。而在测试用例中通过test_data[0][1]方式获取数据也存在很多问题,如果在csv文件中间插入一条数据,那么测试用例获取到的测试数据很可能就是错误的。

(3) 如果测试过程中需要的数据很多怎么办?我们知道测试脚本并不是用来存放数据的地方,如果待测试的数据很多,那么全部放到测试脚本中显然不合适。

当然,比如一些自动化测试的配置是可以放到数据文件中的,如运行环境,运行的浏览器放置到配置文件中很容易管理。

2.2 Parameterized

parameterizes是python的一个擦书画库,同时支持unittest, pytest,Nose单元测试框架。

下面通过parameterizes实现参数化

import unittest
from time import sleep
from selenium import webdriver
from parameterized import parameterized


chrome_driver_path = r"C:\Users\Administrator\Envs\selenuimAutoTest\Lib\site-packages\selenium\webdriver\chrome\chromedriver.exe"
driver = webdriver.Chrome(executable_path=chrome_driver_path)


class TestBaidu(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome(executable_path=chrome_driver_path)
        cls.base_url = "http://www.baidu.com"

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()


    def baidu_search(self, search_key):
        self.driver.get(self.base_url)
        self.driver.find_element_by_id("kw").send_keys(search_key)
        self.driver.find_element_by_id("su").click()
        sleep(3)
    
    # 通过parameterized实现参数化

    @parameterized.expand([
        ("case1", "selenium"),
        ("case2", "unittest"),
        ("case3", "parameterized")
    ])
    def test_search(self, name, search_key):
        print(name, search_key)
        self.baidu_search(search_key)
        self.assertEqual(self.driver.title, search_key + "_百度搜索")


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

结果:

unittest怎样安装 unittest下载_unittest怎样安装_06

用法说明:(1)通过@parameterized.expand来装饰测试用例;(2)expand中,每个元组都被认为是一条测试用例。元组中的数据为该条测试用例变化的值。(3)在test_search中,name参数对应元组的第一个元素,用来定义测试用例的名称,search_key参数对应元组的第二个元素,用来定义搜索关键字。

结果分析:测试用例的个数跟装饰器中元组的个数有关,test_search为定义的测试用例的名称。参数化会自动加上“0”,“1”,“2”来区分每条用例,在元组中定义的“case1”,“case2”,“case3”也会出现在每条测试用例名称的后缀中。

2.3 DDT

DDT(Data-Driver Tests)是针对unittest单元测试框架设计的扩展库。允许使用不同的测试数据来运行一个测试用例,并将其展示为多个测试用例。和上面介绍的parameterized一样,需要pip 安装

下面来看看DDT的用法:

import unittest
from time import sleep
from selenium import webdriver
from ddt import ddt, data, file_data, unpack


chrome_driver_path = r"C:\Users\Administrator\Envs\selenuimAutoTest\Lib\site-packages\selenium\webdriver\chrome\chromedriver.exe"
driver = webdriver.Chrome(executable_path=chrome_driver_path)


@ddt
class TestBaidu(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Chrome(executable_path=chrome_driver_path)
        cls.base_url = "http://www.baidu.com"

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()


    def baidu_search(self, search_key):
        self.driver.get(self.base_url)
        self.driver.find_element_by_id("kw").send_keys(search_key)
        self.driver.find_element_by_id("su").click()
        sleep(3)

    # 参数化使用方式一
    @data(["case1", "selenium"], ["case2", "unittest"], ["case3", "ddt"])
    @unpack
    def test_search_list(self, name, search_key):
        print("第一组测试用例:", name)
        self.baidu_search(search_key)
        self.assertEqual(self.driver.title, search_key + "_百度搜索")

    # 参数化使用方式二
    @data(("case1", "selenium"), ("case2", "unittest"), ("case3", "ddt"))
    @unpack
    def test_search_tuple(self, name, search_key):
        print("第二组测试用例:", name)
        self.baidu_search(search_key)
        self.assertEqual(self.driver.title, search_key + "_百度搜索")

    # 参数化使用方式三
    @data({"search_key": "selenium"}, {"search_key": "unittest"}, {"search_key": "ddt"})
    @unpack
    def test_search_dict(self, search_key):
        print("第三组测试用例:", search_key)
        self.baidu_search(search_key)
        self.assertEqual(self.driver.title, search_key + "_百度搜索")

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

测试结果:

unittest怎样安装 unittest下载_chrome_07

用法说明:(1)使用ddt装饰测试类; (2) ddt提供了不同形式的参数化,这里列举了三种,列表,元组,字典。其中字典需要注意,字典的key与测试方法中的参数要保持一致!!


ddt同样支持数据文件的参数化。它封装了数据文件的读取,让我们更专注于数据文件的内容,以及在测试用例中的使,而不需要关心数据文件是如何读取来的。

准备json数据文件如下:

{
  "case1": {"search_key": "python"},
  "case2": {"search_key": "ddt"},
  "case3": {"search_key": "selenium"}
}

在上面测试文件中添加测试用例如下:

@file_data("ddt_data.json")
    def test_search_json_file(self, search_key):
        print("第四组测试用例:", search_key)
        self.baidu_search(search_key)
        self.assertEqual(self.driver.title, search_key + "_百度搜索")

注意:如果json文件和测试文件不在同一目录,需要指定数据文件的目录

3 自动发送邮件

3.1 python自带的发送邮件功能

自动发送邮件功能是自动化测试项目的重要需求之一,当自动化测试用例运行完成之后,可以自动向相关人员的邮箱发送测试报告,下面就介绍一下发送邮件模块,但是其不属于unittest扩展。

SMTP(Smiple Mail Transfer Protocol)是简单邮件传输协议,是一组由源地址到目的地址的规则,可以控制信件的中转方式。python的smtplib模块提供了简单的API用来实现发送邮件功能,它对SMTP进行了简单的封装

在实现发送邮件功能之前,先介绍一下,在给其他人发送邮件之前,首先需要有一个自己的邮箱,通过浏览器打开邮箱网址,或打开邮箱客户端,登录自己的账号。如果是客户端,则还需配置邮箱服务器地址。然后填写收件人地址,邮件的主题和正文,以及添加附件等。即便通过Python发送邮件,也需要设置这些信息。

3.1.1 发送邮件正文

import smtplib
from email.mime.text import MIMEText
from email.header import Header

# 发送主题
subject = "接口测试报告"

# 编写HTML类型的邮件正文
msg = MIMEText("<html><h1>生日快乐</h1></html>", "html", "utf-8")
msg["Subject"] = Header(subject, "utf-8")

# 要指定发件人和收件人
msg["from"] = "mrli_mrli@163.com"
msg["to"] = "15101006331@163.com"

# 发送邮件
smtp = smtplib.SMTP()
smtp.connect("smtp.163.com")
smtp.login("mrli_mrli@163.com", "xxxxxxxx")  # 后面xxxx是邮箱开启POP3/SMTP时的授权码
smtp.sendmail("mrli_mrli@163.com", "15101006331@163.com", msg.as_string())
smtp.quit()

3.1.2 发送带附件的邮件

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart


# 发送主题
subject = "接口测试报告"

# 发送附件
with open("test1.html", "rb") as f:
    send_att = f.read()

att = MIMEText(send_att, "html", "utf-8")
att["Content-Type"] = "text/html; charset=utf-8"  # "application/octet-stream"
att["Content-Disposition"] = "attachment; filename=test1.html"

# 编写HTML类型的邮件主题并添加附件
msg = MIMEMultipart()
msg["Subject"] = subject
msg.attach(att)
msg["from"] = "mrli_mrli@163.com"
msg["to"] = "15101006331@163.com"

# 发送邮件
smtp = smtplib.SMTP()
smtp.connect("smtp.163.com")
smtp.login("mrli_mrli@163.com", "xxxxxxxx")
smtp.sendmail("mrli_mrli@163.com", "15101006331@163.com", msg.as_string())
smtp.quit()

3.2 用yagmail发送邮件(推荐!!!!!)

yagmail是python的第三方库,可以让我们以非常简单的方式实现自动发送邮件的功能。pip 安装

import yagmail

# 连接邮箱服务器
yag = yagmail.SMTP(user="mrli_mrli@163.com", password="xxxxxxxxx", host="smtp.163.com")

# 邮件正文
contents = ["这就是测试报告正文"]

# 发送
yag.send("15101006331@163.com", "subject", contents)

# 这是发送邮件的方法 

def send(
        self,
        to=None,
        subject=None,
        contents=None,
        attachments=None,
        cc=None,
        bcc=None,
        preview_only=False,
        headers=None,
        prettify_html=True,
        message_id=None,
        group_messages=True,
    ):

如果想发送给多个用户,那么只需要把接收人放到一个list中就可以了, 如果想发送附件,那么指定本地附件的路径就可以了

3.3 整合自动发送邮件

from HTMLTestRunner import HTMLTestRunner
import unittest
import yagmail


# 把测试报告作为附件发送到指定邮箱
def send_mail(report):
    yag = yagmail.SMTP(user="mrli_mrli@163.com", password="xxxxxxx", host="smtp.163.com")
    subject = "自动化测试报告"
    contents = "请查看附件"
    yag.send("15101006331@163.com", subject, contents, report)
    print("email has been sended!!")

test_dir = r"C:\Users\Administrator\Desktop\自己练习\selenuim自动化测试\unittest_use"
suits = unittest.defaultTestLoader.discover(test_dir, pattern="test_cal_*.py")


if __name__ == '__main__':
    # 生成HTML测试报告
    fp = open("./last_test.html", "wb")
    runner = HTMLTestRunner(stream=fp, title="百度搜索测试报告", description="运行环境:Windows 10 ,chrome浏览器")
    runner.run(suits)
    fp.close()
    send_mail("./last_test.html")