Python中如何编写单元测试?

我现在还是手工测. 因为

1.  没人带单元测试
2.  单元测试无法自动化,不能自动建表,而且搞模拟数据很耗时间
3. 写起来麻烦,一个 function 如果有 100 行左右的代码,测试代码估计要 1K 行,
而且如果逻辑改了的话。测试代码要改 N 久


Python中如何编写单元测试?
12 回复

django 有 tests ,来拥抱 django 把。


在Python里写单元测试,最常用的就是内置的 unittest 模块。我给你一个最直接的例子,看完就明白了。

假设你有个要测试的函数,放在 calculator.py 里:

# calculator.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

然后,创建一个单独的测试文件,比如 test_calculator.py。关键是要继承 unittest.TestCase 类,并且测试方法的名字要以 test_ 开头。

# test_calculator.py
import unittest
from calculator import add, subtract

class TestCalculator(unittest.TestCase):

    def test_add(self):
        # 测试正常情况
        self.assertEqual(add(2, 3), 5)
        # 测试负数
        self.assertEqual(add(-1, 1), 0)
        # 测试浮点数(注意精度)
        self.assertAlmostEqual(add(0.1, 0.2), 0.3)

    def test_subtract(self):
        self.assertEqual(subtract(10, 4), 6)
        self.assertEqual(subtract(0, 5), -5)

# 这行允许你直接运行这个脚本
if __name__ == '__main__':
    unittest.main()

怎么运行? 在命令行里,直接运行测试文件:

python -m unittest test_calculator.py

或者,如果你在文件末尾加了 unittest.main(),直接运行 python test_calculator.py 也行。

运行后,你会看到类似这样的输出,. 表示测试通过:

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

核心就三点:

  1. 导入 unittest
  2. 创建测试类,继承 unittest.TestCase
  3. 写测试方法,用 self.assertXxx()(比如 assertEqual, assertTrue)来验证结果。

一句话建议:unittest 模块,为每个功能点创建以 test_ 开头的测试方法。

数据库建好,放 docker 里

我们的解决方案是两个数据库:

- 正常开发库
- 测试库

跑单元测试之前把开发库的表结构拷贝到测试库, 然后自动扫描测试包下的 SQL 文件并在测试数据库执行(测试数据初始化).

缺点就是每次运行测试之前会有比较长的数据库初始化时间, 但可以接受.

你没发现看起来是单元测试的问题,换个角度其实是被测代码的问题么?

你可以参考一下, GitHub 上的开源项目,很少是不带单元测试的。
另外如果你觉得单元测试很难写,有可能是你的设计有问题,模块间的耦合太重。

另外单元测试写到什么程度本来就没有一个很明确的界定。
对我来说,一些核心模块会写单元测试,业务模块大多不写。
核心模块不会频繁改动,且被广泛引用,出问题影响大。
业务模块影响范围小,而且需求变动很可能导致所有测试用例都得重写,测试用例的性价比太低。
(注: REST 接口都会写测试用例。 REST 自动化测试比手动测试还更简单)

  • -,基本没写过!

代码最好分层, models 、 service 、 controller 三层,分层测试。如果是 python 的话推荐用 pytest ,测试数据库用 sqlite 就够了。
每次 push 之后触发持续集成然后跑单元测试!都是自动化的。

web ,因为前后端分离,所以可以直接请求 API ,一个是看返回状态,一个是看返回的数据结构,数据内容是数据库的问题所以不在考虑范围

从来不写,

def testXXX
#TODO
pass

回到顶部