python测试开发教程1简介

软件开发更类似于手工艺,而不是工业。本章主要内容如下

  • 软件测试和质量控制
  • 自动测试和测试套件
  • 测试驱动的开发
  • 集成和功能测试
  • 测试金字塔和奖杯

软件测试和质量控制

软件开发过程在很大程度上受到了制造业标准的启发,在早期,测试和质量控制被引入到产品开发周期中。软件公司经常有质量保证团队,专注于建立流程以保证软件的稳健性和跟踪结果。

质量控制过程通常通过测试计划的执行来实现这种信心。它通常是一个检查表,由专门的团队在生产的各个阶段进行检查,以确保软件的行为符合预期。需求和测试计划、测试用例、测试报告等关联,形成一个闭环。

自动测试

测试用例太多,很多时候需要对回归和手工不能实现的部分进行自动化。
自动化本身也是一种开发,需要对测试用例有一定的组织,为此产生了测试套件。

import unittest


class MyTestCase(unittest.TestCase):
    def test_one(self):
        pass

    def notatest(self):
        pass


class MySecondTestCase(unittest.TestCase):
    def test_two(self):
        pass

    def test_two_part2(self):
        pass


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

执行示例:

$ python 01_automatictests.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK
$ python 01_automatictests.py -v
test_two (__main__.MySecondTestCase) ... ok
test_two_part2 (__main__.MySecondTestCase) ... ok
test_one (__main__.MyTestCase) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

还可以分布在多个文件,比如

├──02_tests │
├── tests_div │

├─── __init__.py │ │

└── tests_div.py │ │
└── tests_sum.py 

大多数测试是按照 "安排,行动,断言(Arrange, Act, Assert) "的模式来写的。

测试驱动的开发

测试可以做的不仅仅是验证我们的代码,测试在设计代码本身中是必不可少的。在实现代码之前编写测试,迫使我们对需求进行推理。

import unittest

class AdditionTestCase(unittest.TestCase):
    def test_main(self):
        result = addition(3, 2)
        assert result == 5

    def test_threeargs(self):
        result = addition(3, 2, 1)
        assert result == 6

    def test_noargs(self):
        result = addition()
        assert result == 0


def addition(*args):
    total = 0
    for a in args:
        total += a
    return total


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

集成和功能测试

集成测试是指不同架构组件的测试,比如:

import unittest

class Authentication:
    USERS = [{"username": "user1",
              "password": "pwd1"}]

    def login(self, username, password):
        u = self.fetch_user(username)
        if not u or u["password"] != password:
            return None
        return u

    def fetch_user(self, username):
        for u in self.USERS:
            if u["username"] == username:
                return u
        else:
            return None


class Authorization:
    PERMISSIONS = [{"user": "user1",
                    "permissions": {"create", "edit", "delete"}}]

    def can(self, user, action):
        for u in self.PERMISSIONS:
            if u["user"] == user["username"]:
                return action in u["permissions"]
        else:
            return False


class TestAuthentication(unittest.TestCase):
    def test_login(self):
        auth = Authentication()
        auth.USERS = [{"username": "testuser", "password": "testpass"}]

        resp = auth.login("testuser", "testpass")
        
        assert resp == {"username": "testuser", "password": "testpass"}

    def test_failed_login(self):
        auth = Authentication()

        resp = auth.login("usernotexisting", "")

        assert resp is None

    def test_wrong_password(self):
        auth = Authentication()
        auth.USERS = [{"username": "testuser", "password": "testpass"}]

        resp = auth.login("testuser", "wrongpass")

        assert resp == None

    def test_fetch_user(self):
        auth = Authentication()
        auth.USERS = [{"username": "testuser", "password": "testpass"}]

        user = auth.fetch_user("testuser")

        assert user == {"username": "testuser", "password": "testpass"}

    def test_fetch_user_not_existing(self):
        auth = Authentication()
                              
        resp = auth.fetch_user("usernotexisting")
              
        assert resp is None


class TestAuthorization(unittest.TestCase):
    def test_can(self):
        authz = Authorization()
        authz.PERMISSIONS = [{"user": "testuser", "permissions": {"create"}}]

        resp = authz.can({"username": "testuser"}, "create")

        assert resp is True

    def test_not_found(self):
        authz = Authorization()

        resp = authz.can({"username": "usernotexisting"}, "create")

        assert resp is False

    def test_unathorized(self):
        authz = Authorization()
        authz.PERMISSIONS = [{"user": "testuser", "permissions": {"create"}}]
                      
        resp = authz.can({"username": "testuser"}, "delete")
                      
        assert resp is False


class TestAuthorizeAuthenticatedUser(unittest.TestCase):
    def test_auth(self):
        auth = Authentication()
        authz = Authorization()              
        auth.USERS = [{"username": "testuser", "password": "testpass"}]
        authz.PERMISSIONS = [{"user": "testuser", "permissions": {"create"}}]      

        u = auth.login("testuser", "testpass")
        resp = authz.can(u, "create")

        assert resp is True


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

功能测试倾向于验证我们是否向用户展示了我们真正想要的功能,多为黑盒测试。
端到端(E2E)测试是一种特殊的功能测试,涉及组件的垂直整合。
系统测试与功能测试本身非常相似,但不是测试单一功能,而是通常测试用户在系统中的整个流程。
验收测试是一种功能测试,多由用户进行。
单元和集成测试的目的是测试实现,而功能测试的目的是测试行为。

参考资料

测试分布模型

  • 金字塔
  • 奖杯

推荐阅读更多精彩内容