测试开发基础知识与unittest使用

test develop · 03-27 · 40 人浏览

测试开发基础知识与unittest使用

测试流程

  1. 分析需求
  2. 制定测试计划
  3. 设计测试用例

    1. 测试方向
    2. 具体设计

      1. 黑白盒测试
      2. 手工和自动测试
      3. 冒烟测试: 测试前先验证基本功能是否能实现,是否具有可测性
  4. 执行测试: 搭建环境、冒烟测试、正式测试
  5. 测试评估

测试方向

  • 功能测试: 各个功能是否正常,输入是否有限制
  • 易用性测试: 是否有各种提示
  • 界面测试: ui界面是否有错,风格是否统一
  • 性能测试: 加载时间,高并发下性能,弱网环境下,耗电量
  • 安全测试: 敏感内容处理,多用户单设备,单用户多设备
  • 兼容性测试: 设备、语言、系统等

黑盒白盒

  1. 白盒测试(White Box Testing)

    • 定义:白盒测试是一种测试方法,它基于对软件内部结构、设计和代码的理解来进行测试。
    • 特点:测试人员需要访问源代码和系统内部结构,以设计测试用例和确定测试覆盖范围。
    • 目的:主要测试逻辑路径、控制流、数据流和代码覆盖率,以发现潜在的错误和漏洞。
  2. 黑盒测试(Black Box Testing)

    • 定义:黑盒测试是一种测试方法,它不考虑程序的内部逻辑和结构,而是通过测试软件的功能和输入/输出来评估系统的正确性。
    • 特点:测试人员只需关注软件的外部行为,无需了解其内部实现。
    • 目的:测试系统是否符合规格说明书和用户需求,发现功能性、性能和安全方面的问题。

python assert

assert即断言,用于测试代码的正确性,并在条件不满足时引发异常或输出错误消息。

# 测试用例
def test_something():
    result = one_class.one_function("some_stuff")
    # 断言结果是否等于期望值
    assert result == "right_answer", "message showing it's not valid"
assert expression [, message]

即,对assert后的表达式进行判断,如果为False则引发 AssertionError 异常,使用可选的参数[, message]输出自定义异常信息

值得注意的是,如果表达式为True,程序什么也不会做并会继续执行,而若为False,抛出异常后会中断测试,如果使用unittestassertEqual方法则可以在抛出异常且不中断测试的情况下继续

单元测试

使用python中的unittest包来实现自动化的单元测试

其有四个模块

  • TestLoader
  • TestCase
  • TestSuite
  • TestRunner
  • TestResult

一个TestCase的实例就是一个测试用例。什么是测试用例呢?就是一个完整的测试流程,包括测试前准备环境的搭建(setUp),执行测试代码(run),以及测试后环境的还原(tearDown)。其中setUPteaerDown这种对一个测试用例环境的搭建和销毁,是一个fixture

而多个测试用例集合在一起,就是TestSuite,而且TestSuite也可以嵌套TestSuite

TestLoader是用来加载TestCaseTestSuite中的

TestRunner是来执行测试用例的
测试的结果会保存到TextTestResult实例中,包括运行了多少测试用例,成功了多少,失败了多少等信息。

断言方法

上文提到过,使用assert时,如果抛出异常,测试会直接中断,为了实现连续的单元测试,unittest提供了一系列断言方法

        self.assertEqual(result, 3 , "Should be 3")
        assert result == 3, "Result should be 3"

从逻辑上来说,这两种写法是一致的,unittest中提供的其他方法如下:

  • assertEqual(a, b):检查ab是否相等。
  • assertTrue(x):检查x是否为True。
  • assertFalse(x):检查x是否为False。
  • assertIs(a, b):检查ab是否是同一个对象。
  • assertIsNot(a, b):检查ab是否不是同一个对象。
  • assertIn(a, b):检查a是否在b中。
  • assertNotIn(a, b):检查a是否不在b中。

上述断言方法都有其他参数可以传入,比如常用的[, message]

默认情况

unittest给了一些适合编写简单测试的默认规则

  1. 创建一个测试用的类,传参unittest.TestCase以继承
  2. 定义数个以test_开头的方法,会被自动识别
  3. 在主函数中使用unittest.main()自动运行全部的testcase

运行unittest.main()时可以加入参数verbosity可选值0、1、2,决定了测试输出的详细度(递增)

例:

import unittest
class CalculateSth:
    def __init__(self,a,b):
        self.a = a
        self.b = b
    def add(self):
        return self.a+self.b

class TestCalculate(unittest.TestCase):
    def test_two_positive_numbers(self):
        cal = CalculateSth(1,2)
        result = cal.add()
        self.assertEqual(result, 3 , "Should be 3")

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

组织TestSuite

上述运行有一个弊端:无法决定测试样例运行的顺序。要按照一定顺序执行TestCase,需要将用例组织进一个TestSuite

import unittest
import SomeTestFunc

if __name__ == '__main__':
    suite = unittest.TestSuite()

    tests = [SomeTestFunc("test_one"), SomeTestFunc("test_two"), SomeTestFunc("test_three")]
    suite.addTests(tests)

    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

将结果输出到文件中

    with open('UnittestTextReport.txt', 'a') as f:
        runner = unittest.TextTestRunner(stream=f, verbosity=2)
        runner.run(suite)

将结果输出为HTML页面

可以使用第三方库HTMLTestRunner,这样在使用TestRunner时可以使用类似的方法输出到一个html文件中

    with open('HTMLReport.html', 'w') as f:
        runner = HTMLTestRunner(stream=f,
                                title='title',
                                description='generated by HTMLTestRunner.',
                                verbosity=2
                                )
        runner.run(suite)
 ======================or==============================
    report_path = "./report.html"
    report = HTMLTestReport(report_path,
                            title='title',
                            description='generated by HTMLTestRunner.',
                            verbosity=2)
 
    # 执行测试套件
    report.run(suite)

Test Fixture

考虑到测试时有时需要在测试前进行一些准备(比如连接数据库等等)并在测试后进行一些善后操作(比如断开连接)TestCase提供了两个方法,我们可以在测试类中重写这两个方法

    def setUp(self):
        print "do something before a case."

    def tearDown(self):
        print "do something after a case."

这样,在每次执行一个case前后,我们都会进行一系列固定的操作

但是如果我们仅需要在执行所有的case前后进行一些固定的操作,我们需要重写两个另外的方法类

    @classmethod
    def setUpClass(cls):
        print "do something before all cases."

    @classmethod
    def tearDownClass(cls):
        print "do something after all cases"

跳过某些case

使用@unittest.skip()装饰器修饰特定的case以跳过,其仍会被计数在结果中,但并不会被执行

skip装饰器一共有三个

unittest.skip(reason)

unittest.skipIf(condition, reason)

unittest.skipUnless(condition, reason)

其中skip无条件跳过,skipIf当condition为True时跳过,skipUnless当condition为False时跳过,reason可以是一个字符串,为你跳过的理由

python test develop
Theme Jasmine by Kent Liao