在上一篇文章中,我们确实使用假设创建了一些检验来为我们生成输入日期。在那篇文章中,有一个验证类来验证密码。我在每个测试中都会检查API调用的回复。使用python进行API response验证并不困难。我解释一下。

假设我们有一个返回一些用户数据的API。例如,我可以使用ID调用函数getPerson并检索以下数据:

{
  "name": "testuser",
  "age": 34,
  "interest": "testing"
}

response是带有某些字段的json文件。名称至少应包含8个字符,并且不能超过12个字符。年龄是整数。系统中的用户必须年满21岁,因此18岁是无效的。interest的值是以下值之一:“testing”, “programming”, “agile”.

现在,我可以使用以下python代码检查所有值:

class ReplyValidator(object):
    def check_name(self, name):
        if len(name) < 8:
            return False
        elif len(name) > 12:
            return False
        return True

    def check_age(self, age):
        if age < 21:
            return False
        return True
    
    def check_interest(self, interest):
        if interest not in  ["testing", "programming", "agile"]:
            return False
        return True

class Reply():
    def __init__(self, data):
        self.name = data["name"]
        self.age = data["age"]
        self.interest = data["interest"]
    
    @property
    def is_valid(self):
        return all([
            ReplyValidator().check_name(self.name), 
            ReplyValidator().check_street(self.street),
            ReplyValidator().check_age(self.age),
            ReplyValidator().check_interest(self.interest)
            ])

我也创建了一些单元测试来测试我们的验证类。我仍然是一名测试人员,所以我也测试自己的代码。

单元测试我自己的代码

from line import Reply

def test_a_valid_reply():
    reply_data = dict(name="testuser",
                    street="testing street",
                    age=34,
                    interest="testing")

    reply = Reply(reply_data)
    assert reply.is_valid

def test_an_invalid_name():
    reply_data = dict(name="test",
                    street="testing street",
                    age=34,
                    interest="testing")

    reply = Reply(reply_data)
    assert not reply.is_valid

def test_an_invalid_age():
    reply_data = dict(name="test",
                    street="testing street",
                    age=10,
                    interest="testing")

    reply = Reply(reply_data)
    assert reply.is_valid

在python中使用抽象方法 代码很简单。但是,如果有额外的验证,则必须创建验证并添加额外的检查。您不必编写大量代码来检查它会不好吗?可以使用描述符技术编写验证类。

from abc import ABC, abstractmethod

class BaseValidator(ABC):
    @abstractmethod
    def validate(self, value):
        pass

    def __init__(self):
        self.value = None

    def __get__(self, obj, objtype):
        return self.value
    
    def __set__(self,obj, value):
        self.validate(value)
        self.value = value

首先,我们创建一个继承自ABC类的BaseValidator类。因此,我可以创建一个抽象方法。从此类继承的所有类都必须实现验证功能。

描述符可以绑定变量,例如python中的@property装饰器。我们可以创建一些数据类来保存值并检查该值是否有效。描述符的设置器首先调用一个函数来验证输入。BaseValidator的所有子类都必须实现此功能。现在界面已更改。如果验证失败,则会引发异常。

class OneOf(BaseValidator):
    def __init__(self, values=list()):
        self.values = values

    def validate(self, value):
        if not isinstance(value, str):
            raise TypeError(f'{value} should be of type str')
        if not(value in self.values):
            raise ValueError(f'{value} should be one of {self.values}')

class String(BaseValidator):
    def __init__(self, min_len = 8, max_len=12):
        self.min_len = min_len
        self.max_len = max_len

    def validate(self, value):
        if not isinstance(value, str):
            raise TypeError(f'{value} should be of type str')
        if len(value) < self.min_len:
            raise ValueError(f'length of {value} should be at least {self.min_len}')
        if len(value) > self.max_len:
            raise ValueError(f'length of {value} should be not bigger than {self.max_len}')

class Integer(BaseValidator):
    def __init__(self, minimum=21):
        self.minimum = minimum
    
    def validate(self, value):
        if not isinstance(value, int):
            raise TypeError(f'{value} should be of type int')
        if value < self.minimum:
            raise ValueError(f'{value} should be at least {self.minimum}')

我创建了3个示例类。String,Integer和OneOf类。String类具有最小和最大长度。验证器功能检查新值是否在最小和最大长度之间。

Integer类具有一个最小参数。验证功能检查新值是否大于此最小参数。

OneOf类很特殊。它接收可能的(字符串)值列表。验证功能检查新值是否为可能的值之一。

使用验证程序进行API回复验证 现有代码现在应该更改。让我们以最小的变化尝试一下。

class ReplyValidator(object):
    name = String()
    age = Integer()
    interest = OneOf(["testing", "programming", "agile"])

class Reply():
    def __init__(self, data):
        self.name = data["name"]
        self.age = data["age"]
        self.interest = data["interest"]
    
    @property
    def is_valid(self):
        validator = ReplyValidator()
        validator.name = self.name
        validator.age = self.age
        validator.interest = self.interest
        return True

两个测试用例需要更新。输入无效时,代码将引发异常。

def test_an_invalid_name():
    reply_data = dict(name="test",
                    street="testing street",
                    age=34,
                    interest="testing")

    reply = Reply(reply_data)
    with pytest.raises(ValueError):
        not reply.is_valid

def test_an_invalid_age():
    reply_data = dict(name="testuser",
                    street="testing street",
                    age=10,
                    interest="testing")

    reply = Reply(reply_data)
    with pytest.raises(ValueError):
        reply.is_valid

代码再次更改。不再需要is_valid方法。“框架”现在可以验证所有数据。如果验证失败,则会引发错误。因此我们的代码如下所示:

class ReplyValidator(object):
    name = String()
    age = Integer()
    interest = OneOf(["testing", "programming", "agile"])

class Reply():
    def __init__(self, data):
        self.validator = ReplyValidator()
        self.validator.name = data["name"]
        self.validator.age = data["age"]
        self.validator.interest = data["interest"]

该代码无需额外检查即可验证数据。现在可以使用Reply类验证API的response。将真实的API答复作为输入参数。使用python进行API response验证并不困难。这正是我要进行API测试所需要的