DevOps: 测试驱动开发 [TDD]

171 阅读1分钟

软件测试级别

  1. Unit tests: 目的是验证软件的每个单元是否按设计运行
  2. Integration tests: 目的是暴露集成单元之间交互中的故障
  3. System tests: 目的是评估系统是否符合指定要求
  4. Acceptance test: 目的是评估系统是否符合业务要求,以及是否可接受交付

什么是 BDD & TDD

行为驱动开发 (BDD)

  • 从外到内地描述系统的行为
  • 用于集成/验收测试 Integration & Acceptance tests

测试驱动开发(TDD)

  • 由内而外测试系统功能
  • 用于单元测试 Unit test

BDD 是构建正确的事物,而 TDD 是保证正确的构建

TDD 工作流

image.png

Python 测试工具

  • PyUnit:这就是我们将使用的。它是像 JUnit 这样的标准单元测试模块
  • Green: 类似 nose 的测试运行器,可遵循红/绿/重构(我们也将使用它)

断言 Assertions

Example:

sum = add(2, 3)
assert(sum == 5)
from math import pi
def area_of_circle(radius):
    """ Calculates the area given a non-negative number """
    if type(radius) not in [int, float]:
        raise TypeError("The radius must be a number")
    if radius < 0:
        raise ValueError("The radius cannot be negative")
    return pi * (radius**2)

什么是测试覆盖率?

  • 测试覆盖率衡量所有测试期间执行的代码行的百分比
  • 高测试覆盖率使人确信已编写的代码已被测试用例覆盖

如何提高测试覆盖率呢?

  • 必须包含“happy”路径和“sad”路径才能获得完整的测试覆盖率
  • 哪怕覆盖率为100%,也不能保证没有bug产生

For example:

class TestCircleArea(TestCase):
    # Happy path
    def test_area(self):
        """ Test areas when radius is >= 0 """
        self.assertAlmostEqual(area_of_circle(1), pi)
        self.assertAlmostEqual(area_of_circle(0), 0)
        self.assertAlmostEqual(area_of_circle(2.1), pi * 2.1**2)
    
    # Sad path
    def test_values(self):
        """ Test that ValueError is raised for bad values """
        self.assertRaises(ValueError, area_of_circle, -3)
        def test_types(self):
        """ Test that TypeError is raised with bad types """
        self.assertRaises(TypeError, area_of_circle, True)
        self.assertRaises(TypeError, area_of_circle, "radius")

Factories and Fakes

  • 有时您需要假数据来进行测试
  • 有时您会希望整个类都是假数据

Account Model:

db = SQLAlchemy()
class Account(db.Model):
    """
    Class that represents an Account
    """
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(128))
    email = db.Column(db.String(64))
    phone_number = db.Column(db.String(32), nullable=True)
    active = db.Column(db.Boolean(), nullable=False, default=True)
    date_joined = db.Column(db.DateTime, nullable=False)

Factory:

import factory
from datetime import datetime
from models import Account
class AccountFactory(factory.Factory):
    """ Creates fake Accounts """
    class Meta:
        model = Account
    
    id = factory.Sequence(lambda n: n)
    name = factory.Faker("name")
    email = factory.Faker("email")
    phone_number = factory.Faker("phone_number")
    active = factory.FuzzyChoice(choices=[True, False])
    date_joined = factory.FuzzyDate(datetime.date(2008, 1, 1)

Using Account Factory:

def test_update_account(self):
    """ It should Update an account """
    account = AccountFactory()
    account.create()
    # Fetch it back
    account = Account.find(account.id)
    account.email = "XYZZY@plugh.com" # Change it
    account.update()
    # Fetch it back again
    account = Account.find(account.id)
    self.assertEqual(account.email, "XYZZY@plugh.com")