如何在 python 行为步骤数据 table 中指定列的类型?

How can I specify the type of a column in a python behave step data table?

假设我有一个定义如下的步骤:

 Then I would expect to see the following distribution for Ford
      | engine | doors | color |
      | 2.1L   | 4     | red   |

我有读取 table 并按如下方式执行断言的步骤实现:

@then('I would expect to see the following distribution for {car_type}')
def step(context, car_type):
    car = find_car_method(car_type)
    for row in context.table:
        for heading in row.headings:
            assertEqual(getattr(car, heading), 
                        row[heading], 
                        "%s does not match. " % heading + \
                        "Found %s" % getattr(car, heading))

(我这样做是因为这种方法允许添加更多字段,但保持它足够通用以用于检查汽车属性的许多用途)。

当我的汽车对象有 4 扇门(作为 int)时,它不匹配,因为数据 table 要求有“4”扇门(作为 unicode str)。

我可以实现此方法来检查列的名称并针对不同的字段进行不同的处理,但是添加新字段时维护变得更加困难,因为还有一个地方可以添加它。我宁愿在步骤数据 table 中指定它。类似于:

 Then I would expect to see the following distribution for Ford
      | engine | doors:int | color |
      | 2.1L   | 4         | red   |

有没有类似的东西可以用来实现这个(因为这不起作用)?

请注意,在某些情况下,我需要根据遇到同样问题的数据 table 创建。在这种情况下,尝试使用 'car' 对象的类型来确定 None 类型是没有用的。

谢谢,

拜尔

经过一番挖掘,我找不到任何东西,所以决定实施我自己的解决方案。我将其张贴在这里,因为它可能会在将来对某人有所帮助。

我创建了一个辅助方法:

def convert_to_type(full_field_name, value):
    """ Converts the value from a behave table into its correct type based on the name
        of the column (header).  If it is wrapped in a convert method, then use it to
        determine the value type the column should contain.

        Returns: a tuple with the newly converted value and the name of the field (without the
                 convertion method specified).  E.g. int(size) will return size as the new field
                 name and the value will be converted to an int and returned.
    """
    field_name = full_field_name.strip()
    matchers = [(re.compile('int\((.*)\)'), lambda val: int(val)),
                (re.compile('float\((.*)\)'), lambda val: float(val)),
                (re.compile('date\((.*)\)'), lambda val: datetime.datetime.strptime(val, '%Y-%m-%d'))]
    for (matcher, func) in matchers:
        matched = matcher.match(field_name)
        if matched:
            return (func(value), matched.group(1))
    return (value, full_field_name)

然后我可以按场景设置如下:

Then I would expect to see the following distribution for Ford
  | engine | int(doors) | color |
  | 2.1L   | 4          | red   |

然后我一步步改成如下:

@then('I would expect to see the following distribution for {car_type}')
def step(context, car_type):
    car = find_car_method(car_type)
    for row in context.table:
        for heading in row.headings:
            (value, field_name) = convert_to_type(heading, row[heading])
            assertEqual(getattr(car, field_name), 
                        value, 
                        "%s does not match. " % field_name + \
                        "Found %s" % getattr(car, field_name))

应该将 'matchers' 移动到模块级别,因为它们不必在每次调用方法时都重新创建。也很容易扩展它以获得更多转换方法(例如 liter() 和 cc() 用于解析引擎大小,同时也转换为标准单位)。