软件可测试性及常用Python测试技巧小结

问题

  • 什么是代码的可测试性

  • 如何写出可测试的代码?

  • 有哪些常见的不好测试的代码?

写出可测试的代码

  • 依赖注入

    将对象的创建反转给上层逻辑,在外部创建好对象后再注入到下层代码中。

  • mock

    若代码中依赖了外部系统或者不可控组件,比如,需要依赖数据库、网络通信、文件系统等,可以通过二次封装将被测代码与外部系统解依赖,提高可测试性.

    比如用户类包含过期日期属性,假定我们需要对用户登录场景进行测试,其中一个判断逻辑是:如果用户过期,则返回登录失败。假如说_expire_data属性是经过封装的,未暴露对外接口进行修改,那么当测试过期用户登录时,可以对用户过期判定逻辑进行二次封装。

    class User:
        id: int
        name: str
        _password: str
        _expire_date: datetime.datetime
    
        def is_expired()->bool:
           pass
    
    def user_is_expired(user: User):
       return user.is_expired()
    

    测试时可以对user_is_expired的返回值进行mock

    def user_is_expired(user: User):
       return true
    

可能的问题

  • 对外部服务的依赖

  • 网络通信耗时

  • 不可控因素

反模式

  • 未决行为

    代码输出是随机的,比如跟时间、随机数相关的代码。

  • 滥用全局变量

  • 滥用静态方法

  • 复杂继承

  • 高耦合代码

如何度量软件架构质量属性?

软件可测试性

软件测试的不同方面

  • 功能

  • 性能

常用的软件测试策略

  • 存根

单元测试

测试一段代码:

# solution.py
from algorithms.base import BaseSolution
from utils import timer


class Solution(BaseSolution):
    @timer
    def run(self, nums1: list, m: int, nums2: list, n: int) -> list:
        nums1[m:] = nums2
        nums1.sort()
        return nums1

class Solution1(BaseSolution):
    @timer
    def run(self, nums1: list, m: int, nums2: list, n: int) -> list:
        array = []
        s1_index, s2_index = 0, 0
        while s1_index<m or s2_index<n:
            if s1_index == m:
                array.append(nums2[s2_index])
                s2_index += 1
            elif s2_index == n:
                array.append(nums1[s1_index])
                s1_index += 1
            elif nums1[s1_index]<nums2[s2_index]:
                array.append(nums1[s1_index])
                s1_index += 1
            else:
                array.append(nums2[s2_index])
                s2_index += 1
        return array
solutions = [
    Solution,
    Solution1
]

通过单元测试框架针对所测模块执行测试用例

# test.py
import unittest
from algorithms.leetcode.alg88 import solution, case
from copy import deepcopy
class MyTestCase(unittest.TestCase):
    def test_algorithms(self):
        _solution = solution.solutions
        for solution_i in _solution:
            print("===", solution_i.__name__)
            for _case in case.cases:
                case_copy = deepcopy(_case)
                print(case_copy)
                result = solution_i().run(*case_copy['input'])
                print(result)
                self.assertEqual(result, case_copy['output'])

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

代码覆盖率

内联测试

文档内联测试(doctest):无需开发或维护单独的测试套件,只需要在函数、类或者模块中组合代码测试即可。示例:

def is_anagram(word: str, other: str)->bool:
    """判断两个单词是否互为易位词
     >>> is_anagram("stop", "pots")
     True
     >>> is_anagram("asda", "sa")
     False
     >>> is_anagram("stop", "otps")
     True
    """
    if len(word) != len(other):
        return False
    return sorted(list(word))==sorted(list(other))

doc test

测试自动化

测试驱动开发(TDD)

参考资料

CoolCats
CoolCats
理学学士

我的研究兴趣是时空数据分析、知识图谱、自然语言处理与服务端开发