Python 基础学习18:测试

测试是编程工作的一部分,要调试就必须运行程序。
Python 不需要编译,所以测试就是运行程序。

先测试再编码

为程序的各个部分进行测似(单元测试)是非常重要的,必须时刻牢记:“测试一点点,再编写一点点代码”的理念。
测试在前,编码在后——测试驱动编程。

准确的需求说明

开发软件时,必须指导软件要解决的问题——要实现的目标。
通过编写需求说明来阐明程序的目标,需求说明也就是描述程序必须满足何种需求的文档,后续就可以方便的核实需求是否得到满足。

在 Python 中,可以将测试程序当作需求说明,先编写测试,再编写让测试通过的程序。

例如以下测试程序示例:

from area import rect_area

height = 3
width = 4

correct_answer = 12

answer = rect_area(height, width)

if correct_answer == answer:
    print('Test Passed')
else:
    print('Test Failed')

随后通过编写满足需求的 rect_area 函数来实现需求。

做好应对变化的准备

必须做好修改代码的心理准备,而不是固守既有代码,但修改会有风险。
如果程序设计良好(使用了合理的抽象和封装),修改带来的影响将是局部的,只会影响很小一段代码。

测试四部曲

测试驱动开发过程的各个阶段:

  1. 确定需要实现的功能
  2. 编写实现功能的框架代码,让程序能够运行
  3. 编写让测试刚好能通过的代码
  4. 改进(重构)代码以全面而准确的实现所需功能

测试工具

有多种测试模块可帮助自动测试过程:

  • unittest:一个通用的测试框架
  • doctest:一个更简单的模块,为检查文档而设计,非常适合用于编写单元测试

doctest

基于文档实现测试的示例 my_math.py

def square(x):
    '''
    计算平方并返回结果
    >>> square(2)
    4
    >>> square(3)
    9
    '''
    return x * x

if __name__ == '__main__':
    import doctest, my_math
    doctest.testmod(my_math)

运行 python my_math.py -v 返回的内容:

Trying:
    square(2)
Expecting:
    4
ok
Trying:
    square(3)
Expecting:
    9
ok
1 items had no tests:
    my_math
1 items passed all tests:
   2 tests in my_math.square
2 tests in 2 items.
2 passed and 0 failed.
Test passed.

unittest

unittest 基于流行的 Java 测试框架 Junit 它更加灵活和强大。

使用模块 unitest 中的 TestCase 类编写一个测试:

import unittest, my_math

class ProductTestCase(unittest.TestCase):

    def test_integers(self):
        for x in range(-10, 10):
            for y in range(-10, 10):
                p = my_math.product(x,y)
                self.assertEqual(p,x * y, 'Integer multiplication failed')

    def test_floats(self):
        for x in range(-10, 10):
            for y in range(-10, 10):
                x = x / 10
                y = y / 10
                p = my_math.product(x, y)
                self.assertEqual(p, x * y, 'Float multiplication failed')

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

运行结果:

Ran 2 tests in 0.000s

OK

pytest

pytest 库是一组工具,可以帮助轻松的编写测试,而且能持续支持随项目增大而变得复杂的测试。

安装 pytest

Python 默认不包含 pytest ,需要通过第三方包进行安装:

pip3 install pytest

编写需要测试的代码

name_function.py 中编写 get_formatted_name 方法:

def get_formatted_name(first, last):
    """生成格式规范的名字

    Args:
        first (string): 名字
        last (string): 姓
    """
    full_name = f"{first} {last}"
    return full_name.title()

编写一个使用该方法的程序 names.py :

from name_function import get_formatted_name

print("Enter 'q' at any time to quit.")
while True:
    first = input("\nPlease give me a first name: ")
    if first == 'q': break
    last = input("\nPlease give me a last name: ")
    if last == 'q': break

    formatted_name = get_formatted_name(first, last)
    print(f"\t Neatly formatted name: {formatted_name}.")

编写测试

pytest 需要编写以 test_ 开头的文件,当 pytest 运行时,它会查找以 test_ 开头的文件,并运行其中的测试,且文件中的所需运行的函数也需定义以 test_ 开头。

test_name_function.py 示例:

from name_function import get_formatted_name

def test_first_last_name():
    formatted_name = get_formatted_name('janis', 'joplin')
    assert formatted_name == 'Janis Joplin'

运行 pytest 命令进行测试:

(env) [hcai@P1-Gen4 Chapter_16]$ pytest
============== test session starts ===============
platform linux -- Python 3.12.1, pytest-7.4.4, pluggy-1.3.0
rootdir: /home/hcai/pythonProject/Chapter_16
collected 3 items                                

test_my_math.py ..                         [ 66%]
test_name_function.py .                    [100%]

=============== 3 passed in 0.01s ================

后续可以在该文件中添加新的测试函数,或在项目中添加新的测试文件。

超越单元测试

源代码检查是一种发现代码中常见错误或问题的方式。
性能分析指的是了解程序运行速度到底有多快。
满足 “使其管用,使其更好,使其更快” —— 古老规则。

使用 PyLint 检查源代码

安装 pylint

pip3 install pylint

使用 subprocess 调用外部检查器:

import unittest, my_math

from subprocess import Popen, PIPE



class ProductTestCase(unittest.TestCase):

def test_with_Pylint(self):

cmd = 'pylint', '-rn', 'my_math'

pylint = Popen(cmd, stdout=PIPE, stderr=PIPE)

self.assertEqual(pylint.stdout.read(), '')

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

也可以直接对文件进行检查:

pylint my_math.py

执行后显示的结果:

Your code has been rated at 6.25/10 (previous run: 2.86/10, +3.39)

性能分析

在编程中,不成熟的优化是万恶之源——C.A.R.Hoare.

标准库中包含一个性能分析模块 profile ,还有一个速度更快的版本 cProfile ,这个性能分析模块使用起来简单,只需要调用 run 方法并提供一个字符串参数。

>>> import cProfile
>>> from my_math import product
>>> cProfile.run('product(1,2)')
         4 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 my_math.py:6(product)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

将输出各个函数和方法被调用多少次以及执行花费的时间。
如果通过第二个参数向 run 提供一个文件名,分析结果将保存在该文件中,随后可以通过 pstats 模块来研究分析结果了。

>>> cProfile.run('product(1,2)', 'my_path.profile')
>>> import pstats
>>> p = pstats.Stats('my_path.profile')

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部