pytest:测试类上的标记会覆盖测试函数上的相同标记

时间:2016-02-09 19:04:38

标签: automated-tests pytest

我使用pytest.mark给我的测试kwargs。但是,如果我在类和类中的测试中使用相同的标记,则当两个使用相同的kwargs时,类的标记将覆盖函数上的标记。

import pytest

animal = pytest.mark.animal


@animal(species='croc')  # Mark the class with a kwarg
class TestClass(object):

    @animal(species='hippo')  # Mark the function with new kwarg
    def test_function(self):
        pass


@pytest.fixture(autouse=True)  # Use a fixture to inspect my function
def animal_inspector(request):
    print request.function.animal.kwargs  # Show how the function object got marked


# prints {'species': 'croc'} but the function was marked with 'hippo'

我的河马在哪里,我怎么能让他回来?

2 个答案:

答案 0 :(得分:0)

不幸的是有pytest bugs related to this,我猜你正在碰到其中一个。{}我找到的那些与你不在那里做的子类有关。

答案 1 :(得分:0)

所以我一直在挖掘pytest代码并弄清楚为什么会这样。函数上的标记在导入时应用于函数,但类和模块级别标记不会在函数级别应用,直到测试集合为止。函数标记首先发生,并将它们的kwargs添加到函数中。然后类标记覆盖任何相同的kwargs,模块标记进一步覆盖任何匹配的kwargs。

我的解决方案是简单地创建我自己的修改过的MarkDecorator,它会在将kwargs添加到标记之前对其进行过滤。基本上,无论什么样的kwarg值首先被设置(似乎总是由函数装饰器设置)将始终是标记上的值。理想情况下,我认为应该在MarkInfo类中添加此功能,但由于我的代码没有创建实例,因此我使用 创建实例:MarkDecorator。请注意,我只更改源代码中的两行(关于keys_to_add的位)。

from _pytest.mark import istestfunc, MarkInfo
import inspect


class TestMarker(object):  # Modified MarkDecorator class
    def __init__(self, name, args=None, kwargs=None):
        self.name = name
        self.args = args or ()
        self.kwargs = kwargs or {}

    @property
    def markname(self):
        return self.name # for backward-compat (2.4.1 had this attr)

    def __repr__(self):
        d = self.__dict__.copy()
        name = d.pop('name')
        return "<MarkDecorator %r %r>" % (name, d)

    def __call__(self, *args, **kwargs):
        """ if passed a single callable argument: decorate it with mark info.
            otherwise add *args/**kwargs in-place to mark information. """
        if args and not kwargs:
            func = args[0]
            is_class = inspect.isclass(func)
            if len(args) == 1 and (istestfunc(func) or is_class):
                if is_class:
                    if hasattr(func, 'pytestmark'):
                        mark_list = func.pytestmark
                        if not isinstance(mark_list, list):
                            mark_list = [mark_list]
                        mark_list = mark_list + [self]
                        func.pytestmark = mark_list
                    else:
                        func.pytestmark = [self]
                else:
                    holder = getattr(func, self.name, None)
                    if holder is None:
                        holder = MarkInfo(
                            self.name, self.args, self.kwargs
                        )
                        setattr(func, self.name, holder)
                    else:
                        # Don't set kwargs that already exist on the mark
                        keys_to_add = {key: value for key, value in self.kwargs.items() if key not in holder.kwargs}
                        holder.add(self.args, keys_to_add)
                return func
        kw = self.kwargs.copy()
        kw.update(kwargs)
        args = self.args + args
        return self.__class__(self.name, args=args, kwargs=kw)


# Create my Mark instance. Note my modified mark class must be imported to be used
animal = TestMarker(name='animal')

# Apply it to class and function
@animal(species='croc')  # Mark the class with a kwarg
class TestClass(object):

    @animal(species='hippo')  # Mark the function with new kwarg
    def test_function(self):
        pass

# Now prints {'species': 'hippo'}  Yay!