07-Pytest白盒测试

7.1 白盒测试

7.1.1 定义

    白盒测试(White Box Testing)又被称之为透明盒测试(Glass Box Testing)、结构测试(Structural Testing),是软件测试中的一种质量保证手段。主要是通过测试待测程序的内部结构和设计,找出潜在的逻辑错误和遗漏等,从而做到提前预防Bug产生。因此,在白盒测试中,对测试人员的技能有明显提高,如下所示:

  • 需要测试人员深入了解待测程序的内部逻辑结构,对所有逻辑路径进行测试验证。因此需要测试人员具备良好的业务能力
  • 需要测试人员具备一定的程序设计能力,能够快速理解代码之间的逻辑结构及其相互关系,例如数据流、控制流、数据结构、程序结构等

    以上图为例,输入一张图片,经过程序扫描处理后,输出一张同样的图片。如果是黑盒测试,我们只需要校对输出和输入的图片一样就可以了,如果是白盒测试,我们就需要了解,程序是如何扫描输入的图片,如何存储为文件,输出时如何解析数据等等。这个时候就需要测试人员去深入了解待测程序内部的逻辑是怎么一步一步去处理的。

7.1.2 实施步骤

    白盒测试的一般实施步骤如下所示:

  • 1.理解需求和设计

  根据需求文档,了解软件的预期行为

  • 2.提取测试需求

  根据对需求文档的理解,提取相应的测试需求

  • 3.制定测试计划和测试策略

  根据前面的步骤,制定详细的测试计划和测试策略,确定相应的测试工作任务和测试范围

  • 4.编写测试用例

  根据对需求的理解,进行测试用例设计并转换为测试用例

  • 5.用例评审

  在用例编写完成,召集相关人员进行用例评审,达到理解一致

  • 6.执行用例

  通过执行用例,并记录测试结果

  • 7、编写测试报告

  根据测试结果生成测试报告

7.1.3 测试分类

    白盒测试总体上可分为静态分析动态分析两大类。

  • 静态分析:不需要运行程序,就可以执行的测试,例如:代码静态检查、代码静态扫描等
  • 动态分析:需要运行程序,才可以执行的测试,例如:单元测试、集成测试、代码覆盖率检查等

7.1.4 测试方法

    主要测试方法如下所示:

7.1.4.1 静态分析

    静态分析主要关注代码是否符合已经制定的编码规范、从而发现潜在的Bug或漏洞等。更多强调的是开发人员的参与,或者使用对应的代码扫描工具来完成。

1.代码静态检查

    代码静态检查又可以分为以下几类:

  • 代码走查

    代码走查是一种传统的检查方法,由开发人员检查自己编写的代码或交叉检查他人编写的代码。一般是在程序编译构建完成之后,对源代码进行分析、检查并补充相关的设计文档,目的是通过走查发现程序中的错误。

  • 代码审查

    由若干开发人员和测试人员组成一个审核小组,通过阅读代码、讨论,对源代码进行静态分析的过程。如果要进行代码审查,一般分为三步:

    • 1.审核小组负责人提前把代码设计文档、控制流程图、源代码及相应的规范等分发给小组成员
    • 2.小组成员在阅读以上材料之后,在召开的审查会议上,小组成员提出问题、展开讨论等,审查错误是否存在
    • 3.根据代码审查结果,创建或更新常见错误清单,供后续的开发、审查参考

2.代码静态扫描

    代码静态扫描一般是通过使用外部工具,对源码进行扫描,从而发现代码不符合设定的规范、潜在的Bug或漏洞的一种方法,常见的静态扫描工具如SonarQube等。

不同的编程语言代码静态扫描工具会有所不同,可以自行在网上搜索相关的资料

7.1.4.2 动态分析

    白盒测试的动态分析一般是测试人员参与的环节。主要使用代码覆盖率来进行衡量。主要的测试方法如下所示:

    测试代码的目录结果如下所示:

Content-08
  |——src       # 用于存放源码
  |    |——————| __init__.py
  |    |——————| 
  |——test     # 用于存放测试代码 
  |    |——————| __init__.py
  |    |——————| testSentenceCoverage.py

1.语句覆盖

    语句覆盖主要是指每条语句至少执行一次。示例代码如下所示:

# say_hello.py
def SayHello(name:str):
    return f"Hello {name}"
# testSentenceCoverage.py
from src.say_hello import SayHello

def test_say_hello():
    assert SayHello("Surpass") == "Hello Surpass"

    使用插件pytest-cov 运行结果如下所示:

$ pytest --cov=src
=========================== test session starts ============================
platform win32 -- Python 3.12.8, pytest-8.3.5, pluggy-1.5.0
rootdir: C:\Users\Surpass\Documents\PyCharmProjects\Pytest\03-codes\Content-08
configfile: pytest.ini
plugins: allure-pytest-2.13.5, anyio-4.7.0, cov-6.2.1
collected 1 item                                                            

test\testSentenceCoverage.py .                                        [100%]

============================== tests coverage ============================== 
_____________ coverage: platform win32, python 3.12.8-final-0 ______________ 

Name               Stmts   Miss  Cover
--------------------------------------
src\__init__.py        0      0   100%
src\say_hello.py       2      0   100%
--------------------------------------
TOTAL                  2      0   100%
============================ 1 passed in 0.06s ============================= 

2.判定覆盖

    确保代码中的每一个判定分支结果为真和假至少执行一次。示例代码如下所示:

# is_odd_number.py
from typing import Optional

def isOddNumber(n:int)->Optional[bool]:
    if not isinstance(n, int):
        return None
    return True if n % 2 == 1 else False
# testJugeCoverage.py
from src.is_odd_number import isOddNumber

def test_is_odd_number():
    assert isOddNumber(1)
    assert not isOddNumber(2)
    assert isOddNumber(3)
    # assert not isOddNumber("abc")

    使用插件pytest-cov 运行结果如下所示:

$ pytest --cov=src/
=========================== test session starts ============================
platform win32 -- Python 3.12.8, pytest-8.3.5, pluggy-1.5.0
rootdir: C:\Users\Surpass\Documents\PyCharmProjects\Pytest\03-codes\Content-08
configfile: pytest.ini
plugins: allure-pytest-2.13.5, anyio-4.7.0, cov-6.2.1, html-4.1.1, metadata-3.1.1
collected 2 items                                                           

test\testJugeCoverage.py .                                            [ 50%] 
test\testSentenceCoverage.py .                                        [100%]

============================== tests coverage ============================== 
_____________ coverage: platform win32, python 3.12.8-final-0 ______________ 

Name                   Stmts   Miss  Cover
------------------------------------------
src\__init__.py            0      0   100%
src\is_odd_number.py       5      1    80%
src\say_hello.py           2      0   100%
------------------------------------------
TOTAL                      7      1    86%
============================ 2 passed in 0.08s ============================= 

这里is_odd_number.py中的判定结果总数为5,是因为pytest-cov在计算是语句+判定覆盖两个结合在一起总数为5
其中语句覆盖语句数量为1:from typing import Optional
其他判定为True和False各2个,所以总数为5

    针对这种情况,我们使用插件pytest-html生成HTML报告中,查看更加详细的情况

 pytest --cov=src/ --cov-report=html

3.条件覆盖

    确保判定中的每个条件至少有一次取值为真,一次为假的情况。示例如下所示:

# condition.py
def scoreRank(score):
    if score > 0 and score < 60:
        return "不及格"
    elif score >= 60 and score < 80:
        return "良好"
    elif score >= 80 and score <= 100:
        return "优秀"
    else:
        return "错误"
from src.condition import scoreRank

def test_score_rank():

    assert scoreRank(50) == "不及格"
    assert scoreRank(90) == "优秀"
$ pytest --cov=src/
=========================== test session starts ============================
platform win32 -- Python 3.12.8, pytest-8.3.5, pluggy-1.5.0
rootdir: C:\Users\Surpass\Documents\PyCharmProjects\Pytest\03-codes\Content-08
configfile: pytest.ini
plugins: allure-pytest-2.13.5, anyio-4.7.0, cov-6.2.1, html-4.1.1, metadata-3.1.1
collected 3 items                                                           

test\testJugeCoverage.py .                                            [ 33%] 
test\testScoreRank.py .                                               [ 66%] 
test\testSentenceCoverage.py .                                        [100%]

============================== tests coverage ============================== 
_____________ coverage: platform win32, python 3.12.8-final-0 ______________ 

Name                   Stmts   Miss  Cover
------------------------------------------
src\__init__.py            0      0   100%
src\condition.py           8      2    75%
src\is_odd_number.py       5      1    80%
src\say_hello.py           2      0   100%
------------------------------------------
TOTAL                     15      3    80%
============================ 3 passed in 0.08s =============================

4.判定条件覆盖

    这是一种判定和条件覆盖的结合体。使得待测程序中的每个判定结果为真和假至少执行一次,每个逻辑条件的结果为真和假也至少执行一次,即同时满足100%的判定覆盖和100%的条件覆盖。示例代码可以直接使用前面的条件覆盖代码。

5.条件组合覆盖

    确保待测程序中每个判定中的条件结果的所有可能组合至少执行一次。示例代码可以直接使用前面的条件覆盖代码。

6.路径覆盖

    确保测试用例能使待测程序中的所有可能路径至少执行一次测试用例,是最强的覆盖准则。

from typing import List

def getMinNum(nums: List[int]) -> int:
    if not isinstance(nums, list):
        return None
    return min(nums)

def getMaxNum(nums: List[int]) -> int:
    if not isinstance(nums, list):
        return None
    return max(nums)

def getAvgNum(nums: List[int]) -> int:
    if not isinstance(nums, list):
        return None
    return sum(nums) / len(nums)

def getResult(nums: List[int]) -> str:
    if not isinstance(nums, list):
        return None
    return [getMaxNum(nums), getMinNum(nums),getAvgNum(nums)]
from src.all_path import getResult

def test_get_result():
    assert getResult([1,2,3]) == [3,2,1]
    assert getResult([item for item in range(1,101)]) == [100,50,1]

$ pytest --cov=src/
=========================== test session starts ============================
platform win32 -- Python 3.12.8, pytest-8.3.5, pluggy-1.5.0
rootdir: C:\Users\Surpass\Documents\PyCharmProjects\TestNote\test-note\Pytest\03-codes\Content-08
configfile: pytest.ini
plugins: allure-pytest-2.13.5, anyio-4.7.0, cov-6.2.1, html-4.1.1, metadata-3.1.1
collected 4 items                                                           

test\testAllPath.py F                                                 [ 25%]
test\testJugeCoverage.py .                                            [ 50%] 
test\testScoreRank.py .                                               [ 75%] 
test\testSentenceCoverage.py .                                        [100%]

================================= FAILURES ================================= 
_____________________________ test_get_result ______________________________ 

    def test_get_result():
>       assert getResult([1,2,3]) == [3,2,1]
E       assert [3, 1, 2.0] == [3, 2, 1]
E
E         At index 1 diff: 1 != 2
E         Use -v to get more diff

test\testAllPath.py:11: AssertionError
============================== tests coverage ============================== 
_____________ coverage: platform win32, python 3.12.8-final-0 ______________ 

Name                   Stmts   Miss  Cover
------------------------------------------
src\__init__.py            0      0   100%
src\all_path.py           17      4    76%
src\condition.py           8      2    75%
src\is_odd_number.py       5      1    80%
src\say_hello.py           2      0   100%
------------------------------------------
TOTAL                     32      7    78%
========================= short test summary info ========================== 
FAILED test/testAllPath.py::test_get_result - assert [3, 1, 2.0] == [3, 2, 1]
======================= 1 failed, 3 passed in 0.31s ======================== 

7.动态覆盖方法总结

测试方法 总结
语句覆盖 无法发现运算中的逻辑关系结果
判定覆盖 若程序中的判定是多个条件联合构成时,则未必能发现每个条件的错误
条件覆盖 未必能覆盖全部分支

7.1.5 优缺点总结

    白盒测试能深入了解代码内部的逻辑结构,做得好的情况下,能提前预防产生Bug,但也存在成本较高、人员能力要求高等缺点,总结如下所示:

优点 缺点
1.能够检测代码中每个分支和路径 1.投入成本较高,人源能力要求高
2.能够提前发现隐藏在代码中的错误 2.若想覆盖代码中所有路径难度太大
3.对代码层面的测试较为彻底 3.不能替代集成测试

本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

posted @ 2025-06-18 13:43  Surpassme  阅读(80)  评论(0)    收藏  举报