Selenium之学习杂记(八)

本文深入探讨了单元测试的重要性和实施方法,包括为何编写单元测试、如何编写,以及使用Python的unittest库进行单元测试的示例。同时介绍了Selenium在网页测试中的应用,以及如何使用HTMLTestRunner生成更友好的测试报告。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


顾名思义,单元测试指的是对软件中的最小可测试单元进行检查和验证。

为什么要写单元测试

程序员的智慧是有限的,但系统的复杂度是无限的。随着系统复杂性的增加,你必须依靠其他工具来帮助减少问题。
在这里插入图片描述
单元测试的作用更多地体现在代码的维护上,而不是在代码的编写上。有人可能会想,我们打开程序界面,一个一个地单击按钮,不就知道功能是否实现了吗?何必要浪费时间再写一个单元测试呢?这么想确实没有错,并且在早期确实有一部分公司是这么做的。但随着项目越来越庞大,我们对照检查表逐个排查所花费的时间,加上程序运行过程中的切换动画所花费的时间,很可能需要很长时间。单元测试的理念是将程序模块化,一个模块一个模块地进行检查,并且使用计算机执行测试,而不是手动测试或调试,这样自动化的测试不但节约了时间和成本,而且降低了出错率。

怎样写单元测试

前面提到,单元测试是对软件中的最小可测试单元进行检查和验证,这体现的是“分而治之”的思想。我们在写单元测试的时候,一般会针对某一个方面的功能单独写一个测试,然后把这些测试封装到一个继承自unittest.TestCase的类中。假如我们要对一个图书馆书库管理系统进行测试,那么要求存入的书名不能为空。另外,我们还需要测试用户的借书卡内的余额是否为一个浮点型数据且大于零。单元测试代码一般是和生产代码分开的,保存在独立的目录中,使用的时候导入待测试的代码。继承自unittest.TestCase的类的函数在书写上有以下规范。
●使用setUp()函数开始单元测试,使用tearDown()函数结束单元测试。
●所有以test_开头的函数都会当作单元测试代码被执行,其余的函数都会忽略(除了setUp()和tearDown()以外)。
●编写不同类型的断言语句来应对程序测试成功或者失败。
一般来说,编写的单元测试要求能够覆盖常用的输入组合、异常、边界条件等。需注意,单元测试的作用只是排除一些容易发生的浅层次的bug,即便生产代码通过了单元测试,也并不意味着没有bug。
在单元测试中,常用的断言有以下几种。
●assertEqual(self,[assert 1],[assert 2])。
●assertTrue([bool])。
●assertRaises([exception])。

单元测试的演示

unittest是Python的标准库,所有版本的Python都可以直接使用,不需要进行安装。下面的代码不是对具体的内容进行测试的代码,仅仅用于演示。

# 导入库
import unittest
# 继承类,编写单元测试
class calc(unittest.TestCase):
    # 初始化函数
    def setUp(self):
        print("unittest start")
    # 结束函数
    def tearDown(self):
        print("part of unittest end")
    # 具体测试部分
    def test_add(self):
        result = 1 + 1
        self.assertEqual(2, result)
    def test_minus(self):
        result = 2 -1
        self.assertEqual(1, result)

# 定义主函数
def main():
    unittest.main()
if __name__ == '__main__':
    main()

执行结果
在这里插入图片描述
我们首先从unittest.TestCase继承类,然后定义初始化和结束的函数(没有实际内容,仅仅是一个print()函数)。接下来,写两个测试部分的样例,使用self.assertEqual() 判断参数中的两个值是否相等。unittest.main()用于开始执行单元测试,setUp()函数和tearDown()函数在每个测试的开始和结束的时候都会运行一次。如果只仅仅在类开始或结束的时候运行一次,可以考虑使用setUpClass()和tearDownClass()。setUpClass()会在类中所有test开始之前执行一次,而tearDownClass()会在类中在所有test结束之后执行一次。
另外,请注意,unittest.TestCase中的函数和断言的名称与平常的命名规范不太相同,在这个测试实例中一般格式为第一个单词全部小写,第二个单词首字母大写。使用__name__ == 'main’是为了使程序只在自我运行的时候才会直接开始测试。如果通过其他文件导入,则导入的只有一个单元测试类。下面介绍一个断言错误的代码。
在这里插入图片描述
断言错误会格式化地告诉我们进行了多少个测试,多少个测试出现错误,并且清楚地说明错误原因,方便我们后期进行bug排查。
在这里插入图片描述
在我们进行断言的时候,一种重要的方式就是要求执行的程序抛出指定类型的error。下面这个例子中,因为字典中没有test3,所以会抛出一个KeyError。
在这里插入图片描述

单元测试示例

现在假设我们要为一个图书馆的书籍管理系统进行测试,并检验其安全性。我们实现的其中一个功能如下。它传入一个字典作为参数(实际上是从字典中继承的)。

class book(dict):
    def __init__(self, **kw):
        super().__init__(**kw)
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r'Dict object has no attribute "%s"' % key)
    def __setattr__(self, key, value):
        self[key] = value
    # 按照页数分类
    def get_page(self):
        if self.page <= 60:
            return "短篇"
        elif self.page <= 150:
            return "中篇"
        elif self.page > 150:
            return "长篇"

构造函数中的kw也叫作关键字参数。关键字参数允许我们传入少到零个、多到无穷个含名称的参数。也就是说,使用kw传入的参数会被当作一个字典来处理,参数名会被当作关键字,而参数值会被当作关键字的值。可能读者对于类中的第二个函数感到困惑。在这里插入图片描述
getattr__其实是Python中的一个内置函数,可以用来返回未知属性。当需要调用不存在的属性时,Python尝试调用__getattr(self,‘key’)来获取属性并返回。因为传入了一个字典,所以self[key]其实就是在字典中取值。setattr(self, key, value)也是一个内置函数。当我们尝试给类的实例属性赋值时,就会调用它。下面给出一段示例代码。
在这里插入图片描述
使用这种方法向字典中添加的元素和值(除了字典中最初的元素和值之外)都会被添加到一个叫作__dict__的属性中。另外,这个属性原来并不为空,从末尾位置开始添加。
在这里插入图片描述
我们用单元测试来检查它是否可以正常使用以及其稳定性。isinstance([object],[class])也是一个内置函数。如果该函数的前一个参数(一个实例)是后一个参数(一个类)的实例,那么返回的bool变量值为True;否则,返回的bool变量值为False。因为这里的instance是book的一个实例,而book类继承自dict,所以instance也可以算dict的实例。

from books import book
import unittest

class book_test(unittest.TestCase):
    global instance
    instance = book(name="Harry Potter", page=350)
    def test_init(self):
        self.assertEqual(instance.name, "Harry Potter")
        self.assertEqual(instance.page, 350)
        self.assertTrue(isinstance(instance, dict))
    def test_key_add(self):
        instance.key1 = 'value1'
        self.assertTrue('key1' in instance)
        self.assertEqual(instance.key1, 'value1')
    def test_attr_add(self):
        instance['key2'] = 'value2'
        self.assertTrue('key2' in instance)
        self.assertEqual(instance['key2'], 'value2')
    def test_key_error(self):
        with self.assertTrue(KeyError):
            instance['empty']
    def test_attr_error(self):
        with self.assertRaises(AttributeError):
            instance.empty
def main():
    unittest.main()
    if __name__ == '__main__':
        main()

首先我们从刚才写的实例中导入相应的代码。因为这里将刚才实现的book存放到library_book.py中,并且将books.py和刚才新建的单元测试代码存放在同一个目录下,所以可以直接导入(Python的包其实就是Python的代码文件,不过导入采用的是另一种形式)。
这里,我们在所有函数的外层定义了一个instance变量,并用global声明它是全局变量,在这个类中的所有函数都可以使用instance变量。需要注意的是,实例只需要加载一次,而不是在每个测试的开头都要重复加载。我们一共测试了5个部分,这也是单元测试最常见的需要检查的部分。首先,测试字典是否正确解析。我们采用assertEqual()断言的方式检查实例化对象的值是否是我们想要的值。然后,我们检验两种向字典中添加关键字和值的方式是否能够正常使用。最后,我们检验是否可以正常抛出错误;这种行为经常用于检查是否可以人为(恶意用户)地生成bug。那么这个unittest.main()是什么呢?我们来看一下下面的代码,实际上这个unittest.main()的执行过程和下面的代码是几乎相同的。首先找出所有以test_开头的函数,然后将其添加到TestSuite中,最后使用run() 执行TestSuite中的所有内容。
在这里插入图片描述
map()函数以映射的方式对列表中的每个元素调用book_test(),其中iterable参数可以有多个,但function参数只能传入一个。
在这里插入图片描述
如果读者的生产代码量比较小,很有可能写出的单元测试代码比生产代码本身更加冗杂,这也是很多人并没有感觉到单元测试的重要性并且厌恶写单元测试的原因。如果读者对自己写的代码有清晰的了解,并且确定自己在进行代码维护(或者不需要维护)时可以在短时间内回想起代码的作用,那么单元测试并不是必需的。实际上,当研发一个技术难度比较高的网络项目时,前端和后端的生产代码一般进行分离化处理,以降低“修复了1个bug却出现3个bug”这种情况的概率。

Selenium的单元测试

在网页测试的领域,传统的爬虫技术(如urllib、BS4和requests)一般难以处理Ajax和JavaScript,但实际上,绝大部分前沿的网站都会采用这些传统技术框架来提高安全性或改善用户体验等。
幸运的是,我们有Selenium。其实,Selenium项目本身是用来进行网络测试的,只是后来在网络爬虫中的应用反而越来越多。Selenium项目的测试方法和内置库的unittest语法还有些不太一样。Selenium项目不需要单元测试代码是某些类的函数,并且生产代码和测试代码是可以混在一起。Selenium单元测试更多地和Python本身的断言结合起来,这种断言不需要括号,但要求采用空格分开。如果测试通过,不会有任何提示,而当测试失败的时候会返回提示信息。
在这里插入图片描述
用Selenium进行单元测试更像是闯关,如果不满足某个条件,就不会再向下执行,而不是像unittest那样在所有的测试代码执行完毕之后返回错误的数量和详细信息。Selenium的单元测试也没有提供一个完整的测试框架,仅仅是对网页上元素内容的验证。不过,这对我们来说已经足够了。请看下面一段代码。
在这里插入图片描述
在运行这段代码的时候,如果没有意外,应该可以打开维基百科中一个关于Python的界面,紧接着会关闭这个界面。我们将“Python”换成其他字符,如果在断言检查的时候这些字符不存在,则会直接返回错误,告诉我们在检查这一部分的时候失败了。在这里插入图片描述
实际上,我们在使用Selenium对页面进行操作的时候,比如,获取某个元素的文本,单击某个按钮,就是在对页面执行单元测试。它只需要能够执行一个正常用户所有可能的操作,然后在中间使用assert断言进行阻隔,确保中间某一步不会出错。比如,如果获取到的文本内容为空,那么有以下断言。在这里插入图片描述
然后对中间出现的错误进行统一处理,使用try…except来避免因为某一步的出错而造成全部测试代码的失败的情况。在如下代码中,我们尝试从字典中取出关键字test对应的值,但字典中没有这个关键字。当我们执行代码的时候,虽然出现了错误,却依然可以正常执行。
在这里插入图片描述

美化报告

HTMLTestRunner是一个基于unittest的第三方库,由Wai Yip Tung开发。为什么要使用HTMLTestRunner呢?也许unittest在命令行中输出的测试结果开发者可以看懂,但这样的报告无法直接供相关人员使用,因此,我们需要一种界面更加友好的测试报告来告诉用户测试的详细结果和技术部分。几乎绝大部分的Python第三方库都可以直接使用pip install [package name]来安装.
略…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值