首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python3 + pytest +pytest-模拟:模拟泄漏到其他测试函数中,破坏断言?

Python3 + pytest +pytest-模拟:模拟泄漏到其他测试函数中,破坏断言?
EN

Stack Overflow用户
提问于 2017-04-29 17:46:46
回答 2查看 2.9K关注 0票数 16

注意:我的设置(python版本、模块等)的所有细节都列在问题的底部.

如果这个问题是明目张胆的,我会事先道歉,但我已经和它斗争了好几天了。希望有人能给我一些新的启示。

我正在从unittest -> pytest转换我的个人项目的单元测试。以前我使用内置的unittest.mock模块,但现在我尝试使用pytest-mock插件。

我有一种隐秘的感觉,我的测试正在相互泄漏模拟对象。

这里是为什么:

高级细节:

代码语言:javascript
复制
# Python version
Python 3.5.2

# Pytest version ( and plugins )
pytest==3.0.7
pytest-benchmark==3.1.0a2
pytest-catchlog==1.2.2
pytest-cov==2.4.0
pytest-ipdb==0.1.dev2
pytest-leaks==0.2.2
pytest-mock==1.6.0
pytest-rerunfailures==2.1.0
pytest-sugar==0.8.0
pytest-timeout==1.2.0
python-dateutil==2.6.0
python-dbusmock==0.16.7

当我使用以下命令运行测试时:

py.test --pdb --showlocals -v -R : -k test_subprocess.py

在我们到达test_subprocess_check_command_type之前一切都很好。此时,我得到以下错误:

代码语言:javascript
复制
        # Set mock return types
        # mock_map_type_to_command.return_value = int

        # action
        with pytest.raises(TypeError) as excinfo:
            scarlett_os.subprocess.Subprocess(test_command,
                                              name=test_name,
                                              fork=test_fork,
>                                             run_check_command=True)
E           Failed: DID NOT RAISE <class 'TypeError'>

excinfo    = <[AttributeError("'ExceptionInfo' object has no attribute 'typename'") raised in repr()] ExceptionInfo object at 0x7f8c380f9dc0>
mock_fork  = <Mock name='mock_fork' id='140240122195184'>
mock_logging_debug = <Mock name='mock_logging_debug' id='140240128747640'>
mock_map_type_to_command = <Mock name='mock_map_type_to_command' id='140240122785112'>
mocker     = <pytest_mock.MockFixture object at 0x7f8c329f07a8>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f8c329f0810>
self       = <tests.test_subprocess.TestScarlettSubprocess object at 0x7f8c32aaac20>
test_command = ['who', '-b']
test_fork  = False
test_name  = 'test_who'

tests/test_subprocess.py:267: Failed

 tests/test_subprocess.py::TestScarlettSubprocess.test_subprocess_check_command_type ⨯                                                           100% ██████████

但!

如果我过滤掉除有问题的测试之外的所有其他测试,那么我得到:

通过py.test --pdb --showlocals -v -R : -k test_subprocess_check_command_type

代码语言:javascript
复制
pi@0728af726f1f:~/dev/bossjones-github/scarlett_os$ py.test --pdb --showlocals -v -R : -k test_subprocess_check_command_type
/usr/local/lib/python3.5/site-packages/_pdbpp_path_hack/pdb.py:4: ResourceWarning: unclosed file <_io.TextIOWrapper name='/usr/local/lib/python3.5/site-packages/pdb.py' mode='r' encoding='UTF-8'>
  os.path.dirname(os.path.dirname(__file__)), 'pdb.py')).read(), os.path.join(
Test session starts (platform: linux, Python 3.5.2, pytest 3.0.7, pytest-sugar 0.8.0)
cachedir: .cache
benchmark: 3.1.0a2 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/pi/dev/bossjones-github/scarlett_os, inifile: setup.cfg
plugins: timeout-1.2.0, sugar-0.8.0, rerunfailures-2.1.0, mock-1.6.0, leaks-0.2.2, ipdb-0.1.dev2, cov-2.4.0, catchlog-1.2.2, benchmark-3.1.0a2
timeout: 60.0s method: signal
NOTE: DBUS_SESSION_BUS_ADDRESS environment var not found!
[DBUS_SESSION_BUS_ADDRESS]: unix:path=/tmp/dbus_proxy_outside_socket

 tests/test_subprocess.py::TestScarlettSubprocess.test_subprocess_check_command_type ✓                                                                                                                                                                           100% ██████████

Results (8.39s):
       1 passed
     190 deselected
pi@0728af726f1f:~/dev/bossjones-github/scarlett_os$

我还尝试手动注释以下两个测试,它们允许我再次成功地运行所有测试:

  • test_subprocess_init
  • test_subprocess_map_type_to_command

有人能看出我的设置有什么明显的错误吗?我读过几篇关于“在哪里嘲弄”的博客文章,并且看了几次文档本身,不知道我错过了什么。https://docs.python.org/3/library/unittest.mock.html

我的设置细节

以下是解决这一问题所需的一切。如果我需要提供更多的信息,请告诉我!

还有..。请原谅我的代码看上去有多乱,以及所有的注释块。当我学到新东西的时候,我是个记笔记的人.在不久的将来,我将使所有的东西都变得更仿生、更干净:)

我的代码:

代码语言:javascript
复制
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Scarlett Dbus Service. Implemented via MPRIS D-Bus Interface Specification."""

from __future__ import with_statement, division, absolute_import

import os
import sys
from scarlett_os.exceptions import SubProcessError
from scarlett_os.exceptions import TimeOutError
import logging
from scarlett_os.internal.gi import GObject
from scarlett_os.internal.gi import GLib

logger = logging.getLogger(__name__)


def check_pid(pid):
    """Check For the existence of a unix pid."""
    try:
        os.kill(pid, 0)
    except OSError:
        return False
    else:
        return True


class Subprocess(GObject.GObject):
    """
    GObject API for handling child processes.

    :param command: The command to be run as a subprocess.
    :param fork: If `True` this process will be detached from its parent and
                 run independent. This means that no excited-signal will be emited.

    :type command: `list`
    :type fork: `bool`
    """

    __gtype_name__ = 'Subprocess'
    __gsignals__ = {
        'exited': (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_INT, GObject.TYPE_INT))
    }

    def __init__(self, command, name=None, fork=False, run_check_command=True):
        """Create instance of Subprocess."""

        GObject.GObject.__init__(self)

        self.process = None
        self.pid = None

        if not fork:
            self.stdout = True
            self.stderr = True
        else:
            self.stdout = False
            self.stderr = False

        self.forked = fork

        # Verify that command is properly formatted 
        # and each argument is of type str
        if run_check_command:
            self.check_command_type(command)

        self.command = command
        self.name = name

        logger.debug("command: {}".format(self.command))
        logger.debug("name: {}".format(self.name))
        logger.debug("forked: {}".format(self.forked))
        logger.debug("process: {}".format(self.process))
        logger.debug("pid: {}".format(self.pid))

        if fork:
            self.fork()

    # TODO: Add these arguments so we can toggle stdout
    # def spawn_command(self, standard_input=False, standard_output=False, standard_error=False):
    def spawn_command(self):
        # DO_NOT_REAP_CHILD
        # Don't reap process automatically so it is possible to detect when it is closed.
        return GLib.spawn_async(self.command,
                                flags=GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD
                                )

    def map_type_to_command(self, command):
        """Return: Map after applying type to several objects in an array"""
        # NOTE: In python3, many processes that iterate over iterables return iterators themselves. 
        # In most cases, this ends up saving memory, and should make things go faster.
        # cause of that, we need to call list() over the map object
        return list(map(type, command))

    def check_command_type(self, command):

        types = self.map_type_to_command(command)

        if type(types) is not list:
            raise TypeError("Variable types should return a list in python3. Got: {}".format(types))

        # NOTE: str is a built-in function (actually a class) which converts its argument to a string. 
        # string is a module which provides common string operations.
        # source: http://stackoverflow.com/questions/2026038/relationship-between-string-module-and-str
        for t in types:
            if t is not str:
                raise TypeError("Executables and arguments must be str objects. types: {}".format(t))

        logger.debug("Running Command: %r" % " ".join(command))
        return True

    def run(self):
        """Run the process."""

        # NOTE: DO_NOT_REAP_CHILD: the child will not be automatically reaped;
        # you must use g_child_watch_add yourself (or call waitpid or handle `SIGCHLD` yourself),
        # or the child will become a zombie.
        # source:
        # http://valadoc.org/#!api=glib-2.0/GLib.SpawnFlags.DO_NOT_REAP_CHILD

        # NOTE: SEARCH_PATH: argv[0] need not be an absolute path, it will be looked for in the user's PATH
        # source:
        # http://lazka.github.io/pgi-docs/#GLib-2.0/flags.html#GLib.SpawnFlags.SEARCH_PATH

        self.pid, self.stdin, self.stdout, self.stderr = self.spawn_command()

        logger.debug("command: {}".format(self.command))
        logger.debug("stdin: {}".format(self.stdin))
        logger.debug("stdout: {}".format(self.stdout))
        logger.debug("stderr: {}".format(self.stderr))
        logger.debug("pid: {}".format(self.pid))

        # close file descriptor
        self.pid.close()

        print(self.stderr)

        # NOTE: GLib.PRIORITY_HIGH = -100
        # Use this for high priority event sources.
        # It is not used within GLib or GTK+.
        watch = GLib.child_watch_add(GLib.PRIORITY_HIGH, 
                                     self.pid, 
                                     self.exited_cb)

        return self.pid

    def exited_cb(self, pid, condition):
        if not self.forked:
            self.emit('exited', pid, condition)

    def fork(self):
        """Fork the process."""
        try:
            # first fork
            pid = os.fork()
            if pid > 0:
                logger.debug('pid greater than 0 first time')
                sys.exit(0)
        except OSError as e:
            logger.error('Error forking process first time')
            sys.exit(1)

        # Change the current working directory to path.
        os.chdir("/")

        # Description: setsid() creates a new session if the calling process is not a process group leader. 
        # The calling process is the leader of the new session, 
        # the process group leader of the new process group, 
        # and has no controlling terminal. 
        # The process group ID and session ID of the calling process are set to the PID of the calling process. 
        # The calling process will be the only process in this new process group and in this new session.

        # Return Value: On success, the (new) session ID of the calling process is returned. 
        # On error, (pid_t) -1 is returned, and errno is set to indicate the error.
        os.setsid()

        # Set the current numeric umask and return the previous umask.
        os.umask(0)

        try:
            # second fork
            pid = os.fork()
            if pid > 0:
                logger.debug('pid greater than 0 second time')
                sys.exit(0)
        except OSError as e:
            logger.error('Error forking process second time')
            sys.exit(1)

我的测试:

代码语言:javascript
复制
#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
test_subprocess
----------------------------------
"""

import os
import sys
import pytest

import scarlett_os
# import signal
# import builtins
# import re

class TestScarlettSubprocess(object):
    '''Units tests for Scarlett Subprocess, subclass of GObject.Gobject.'''

    def test_check_pid_os_error(self, mocker):
        # Feels like mocks are leaking into other tests, 
        # stop mock before starting each test function
        mocker.stopall()

        # Setup mock objects
        kill_mock = mocker.MagicMock(name=__name__ + "_kill_mock_OSError")
        kill_mock.side_effect = OSError

        # patch things
        mocker.patch.object(scarlett_os.subprocess.os, 'kill', kill_mock)

        # When OSError occurs, throw False
        assert not scarlett_os.subprocess.check_pid(4353634632623)
        # Verify that os.kill only called once
        assert kill_mock.call_count == 1

    def test_check_pid(self, mocker):
        # Feels like mocks are leaking into other tests, 
        # stop mock before starting each test function
        mocker.stopall()

        # Setup mock objects
        kill_mock = mocker.MagicMock(name=__name__ + "_kill_mock")

        mocker.patch.object(scarlett_os.subprocess.os, 'kill', kill_mock)

        result = scarlett_os.subprocess.check_pid(123)
        assert kill_mock.called
        # NOTE: test against signal 0
        # sending the signal 0 to a given PID just checks if any
        # process with the given PID is running and you have the
        # permission to send a signal to it.
        kill_mock.assert_called_once_with(123, 0)
        assert result is True

    # FIXME: I THINK THIS GUYS IS LEAKING MOCK OBJECTS
    def test_subprocess_init(self, mocker):
        # Feels like mocks are leaking into other tests, 
        # stop mock before starting each test function
        mocker.stopall()

        mock_check_command_type = MagicMock(name="mock_check_command_type")
        mock_check_command_type.return_value = True
        mock_fork = mocker.MagicMock(name="mock_fork")
        mock_logging_debug = mocker.MagicMock(name="mock_logging_debug")

        # mock
        mocker.patch.object(scarlett_os.subprocess.logging.Logger, 'debug', mock_logging_debug)
        mocker.patch.object(scarlett_os.subprocess.Subprocess, 'check_command_type', mock_check_command_type)
        mocker.patch.object(scarlett_os.subprocess.Subprocess, 'fork', mock_fork)

        # NOTE: On purpose this is an invalid cmd. Should be of type array
        test_command = ['who']

        test_name = 'test_who'
        test_fork = False

        s_test = scarlett_os.subprocess.Subprocess(test_command,
                                                   name=test_name,
                                                   fork=test_fork)

        # action
        assert s_test.check_command_type(test_command) is True
        mock_check_command_type.assert_called_with(['who'])
        assert not s_test.process
        assert not s_test.pid
        assert s_test.name == 'test_who'
        assert not s_test.forked
        assert s_test.stdout is True
        assert s_test.stderr is True

        mock_logging_debug.assert_any_call("command: ['who']")
        mock_logging_debug.assert_any_call("name: test_who")
        mock_logging_debug.assert_any_call("forked: False")
        mock_logging_debug.assert_any_call("process: None")
        mock_logging_debug.assert_any_call("pid: None")
        mock_fork.assert_not_called()

    # FIXME: I THINK THIS GUYS IS LEAKING MOCK OBJECTS
    def test_subprocess_map_type_to_command(self, mocker):
        """Using the mock.patch decorator (removes the need to import builtins)"""
        # Feels like mocks are leaking into other tests, 
        # stop mock before starting each test function
        mocker.stopall()

        mock_check_command_type = mocker.MagicMock(name="mock_check_command_type")
        mock_check_command_type.return_value = True
        mock_fork = mocker.MagicMock(name="mock_fork")
        mock_logging_debug = mocker.MagicMock(name="mock_logging_debug")

        # mock
        mocker.patch.object(scarlett_os.subprocess.logging.Logger, 'debug', mock_logging_debug)
        mocker.patch.object(scarlett_os.subprocess.Subprocess, 'check_command_type', mock_check_command_type)
        mocker.patch.object(scarlett_os.subprocess.Subprocess, 'fork', mock_fork)

        # NOTE: On purpose this is an invalid cmd. Should be of type array
        test_command = ["who", "-b"]
        test_name = 'test_who'
        test_fork = False

        # create subprocess object
        s_test = scarlett_os.subprocess.Subprocess(test_command,
                                                   name=test_name,
                                                   fork=test_fork)
        mocker.spy(s_test, 'map_type_to_command')
        assert isinstance(s_test.map_type_to_command(test_command), list)
        assert s_test.map_type_to_command.call_count == 1

        assert s_test.check_command_type(test_command)
        assert s_test.check_command_type(
            test_command) == mock_check_command_type.return_value

    def test_subprocess_check_command_type(self, mocker):
        """Using the mock.patch decorator (removes the need to import builtins)"""
        # Feels like mocks are leaking into other tests, 
        # stop mock before starting each test function
        mocker.stopall()

        test_command = ["who", "-b"]
        test_name = 'test_who'
        test_fork = False

        # mock
        mock_map_type_to_command = mocker.MagicMock(name="mock_map_type_to_command")
        # mock_map_type_to_command.return_value = int
        mock_map_type_to_command.side_effect = [int, [int, int]]
        mock_fork = mocker.MagicMock(name="mock_fork")
        mock_logging_debug = mocker.MagicMock(name="mock_logging_debug")

        mocker.patch.object(scarlett_os.subprocess.logging.Logger, 'debug', mock_logging_debug)
        mocker.patch.object(scarlett_os.subprocess.Subprocess, 'map_type_to_command', mock_map_type_to_command)
        mocker.patch.object(scarlett_os.subprocess.Subprocess, 'fork', mock_fork)


        # action
        with pytest.raises(TypeError) as excinfo:
            scarlett_os.subprocess.Subprocess(test_command,
                                              name=test_name,
                                              fork=test_fork,
                                              run_check_command=True)
        assert str(
            excinfo.value) == "Variable types should return a list in python3. Got: <class 'int'>"

        with pytest.raises(TypeError) as excinfo:
            scarlett_os.subprocess.Subprocess(test_command,
                                              name=test_name,
                                              fork=test_fork,
                                              run_check_command=True)

        assert str(
            excinfo.value) == "Executables and arguments must be str objects. types: <class 'int'>"

我的文件夹结构(注意,我删除了一些东西,因为它太冗长了):

代码语言:javascript
复制
pi@0728af726f1f:~/dev/bossjones-github/scarlett_os$ tree -I *.pyc
.
├── requirements_dev.txt
├── requirements_test_experimental.txt
├── requirements_test.txt
├── requirements.txt
├── scarlett_os
│   ├── automations
│   │   ├── __init__.py
│   │   └── __pycache__
│   ├── commands.py
│   ├── compat.py
│   ├── config.py
│   ├── const.py
│   ├── core.py
│   ├── emitter.py
│   ├── exceptions.py
│   ├── __init__.py
│   ├── internal
│   │   ├── debugger.py
│   │   ├── deps.py
│   │   ├── encoding.py
│   │   ├── formatting.py
│   │   ├── gi.py
│   │   ├── __init__.py
│   │   ├── path.py
│   │   ├── __pycache__
│   │   └── system_utils.py
│   ├── listener.py
│   ├── loader.py
│   ├── logger.py
│   ├── log.py
│   ├── __main__.py
│   ├── mpris.py
│   ├── player.py
│   ├── __pycache__
│   ├── receiver.py
│   ├── speaker.py
│   ├── subprocess.py
│   ├── tasker.py
│   ├── tools
│   │   ├── __init__.py
│   │   ├── package.py
│   │   ├── __pycache__
│   │   └── verify.py
│   └── utility
│       ├── audio.py
│       ├── dbus_runner.py
│       ├── dbus_utils.py
│       ├── distance.py
│       ├── dt.py
│       ├── file.py
│       ├── generators.py
│       ├── gnome.py
│       ├── __init__.py
│       ├── location.py
│       ├── __pycache__
│       ├── temperature.py
│       ├── threadmanager.py
│       ├── thread.py
│       ├── unit_system.py
│       └── yaml.py
├── setup.cfg
├── setup.py
├── tests
│   ├── common_integration.py
│   ├── common.py
│   ├── helpers
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   ├── test_config_validation.py
│   │   ├── test_entity.py
│   │   └── test_init.py
│   ├── __init__.py
│   ├── integration
│   │   ├── baseclass.py
│   │   ├── conftest.py
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   ├── README.md
│   │   ├── stubs.py
│   │   ├── test_integration_end_to_end.py
│   │   ├── test_integration_listener.py
│   │   ├── test_integration_mpris.py
│   │   ├── test_integration_player.py
│   │   ├── test_integration_tasker.py
│   │   ├── test_integration_tasker.py.enable_sound.diff
│   │   └── test_integration_threadmanager.py
│   ├── internal
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   ├── test_deps.py
│   │   ├── test_encoding.py
│   │   └── test_path.py
│   ├── performancetests
│   │   ├── baseclass.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   ├── __pycache__
│   ├── run_all_tests
│   ├── run_dbus_tests.sh
│   ├── test_cli.py
│   ├── test_commands.py
│   ├── testing_config
│   │   └── custom_automations
│   │       ├── light
│   │       │   └── test.py
│   │       └── switch
│   │           └── test.py
│   ├── test_listener.py
│   ├── test_mpris.py
│   ├── test_player.py
│   ├── test_scarlett_os.py
│   ├── test_speaker.py
│   ├── test_subprocess.py
│   ├── test_tasker.py
│   ├── test_threadmanager.py
│   ├── tools_common.py
│   ├── unit_scarlett_os.py
│   └── utility
│       ├── __init__.py
│       ├── __pycache__
│       ├── test_dbus_utils.py
│       ├── test_distance.py
│       ├── test_dt.py
│       ├── test_gnome.py
│       ├── test_init.py
│       ├── test_location.py
│       ├── test_unit_system.py
│       └── test_yaml.py
67 directories, 256 files
pi@0728af726f1f:~/dev/bossjones-github/scarlett_os$

其他细节(在不兼容的情况下扩展pip冻结):

代码语言:javascript
复制
# Python version
Python 3.5.2

# Pytest version ( and plugins )
pytest==3.0.7
pytest-benchmark==3.1.0a2
pytest-catchlog==1.2.2
pytest-cov==2.4.0
pytest-ipdb==0.1.dev2
pytest-leaks==0.2.2
pytest-mock==1.6.0
pytest-rerunfailures==2.1.0
pytest-sugar==0.8.0
pytest-timeout==1.2.0
python-dateutil==2.6.0
python-dbusmock==0.16.7


# Pip Freeze ( Just in case )
alabaster==0.7.10
appdirs==1.4.3
argh==0.26.2
asn1crypto==0.22.0
astroid==1.5.2
Babel==2.4.0
bleach==2.0.0
bumpversion==0.5.3
cffi==1.10.0
click==6.7
click-plugins==1.0.3
colorama==0.3.7
colorlog==2.10.0
coverage==4.3.4
coveralls==1.1
cryptography==1.8.1
Cython==0.25.2
decorator==4.0.11
docopt==0.6.2
docutils==0.13.1
ecdsa==0.13
entrypoints==0.2.2
Fabric3==1.12.post1
fancycompleter==0.7
fields==5.0.0
flake8==3.3.0
flake8-docstrings==1.0.3
flake8-polyfill==1.0.1
freezegun==0.3.8
gnureadline==6.3.3
graphviz==0.6
html5lib==0.999999999
hunter==1.4.1
idna==2.5
imagesize==0.7.1
ipdb==0.10.2
ipykernel==4.6.1
ipython==6.0.0
ipython-genutils==0.2.0
ipywidgets==6.0.0
isort==4.2.5
jedi==0.10.2
Jinja2==2.9.6
jsonschema==2.6.0
jupyter==1.0.0
jupyter-client==5.0.1
jupyter-console==5.1.0
jupyter-core==4.3.0
lazy-object-proxy==1.2.2
MarkupSafe==1.0
mccabe==0.6.1
mistune==0.7.4
mock==2.0.0
mock-open==1.3.1
mypy-lang==0.4.6
nbconvert==5.1.1
nbformat==4.3.0
notebook==5.0.0
objgraph==3.1.0
ordereddict==1.1
packaging==16.8
pandocfilters==1.4.1
paramiko==1.18.2
pathtools==0.1.2
pbr==1.10.0
pdbpp==0.8.3
pexpect==4.2.1
pickleshare==0.7.4
pluggy==0.4.0
plumbum==1.6.3
prompt-toolkit==1.0.14
psutil==5.2.2
ptyprocess==0.5.1
py==1.4.33
py-cpuinfo==3.2.0
pyasn1==0.2.3
pycodestyle==2.3.1
pycparser==2.17
pycrypto==2.6.1
pydbus==0.6.0
pydocstyle==2.0.0
pyflakes==1.5.0
pygal==2.3.1
pygaljs==1.0.1
Pygments==2.2.0
pygobject==3.22.0
pylint==1.7.1
pyparsing==2.2.0
pystuck==0.8.5
pytest==3.0.7
pytest-benchmark==3.1.0a2
pytest-catchlog==1.2.2
pytest-cov==2.4.0
pytest-ipdb==0.1.dev2
pytest-leaks==0.2.2
pytest-mock==1.6.0
pytest-rerunfailures==2.1.0
pytest-sugar==0.8.0
pytest-timeout==1.2.0
python-dateutil==2.6.0
python-dbusmock==0.16.7
pytz==2017.2
PyYAML==3.12
pyzmq==16.0.2
qtconsole==4.3.0
requests==2.13.0
requests-mock==1.3.0
rpyc==3.3.0
-e git+git@github.com:bossjones/scarlett_os.git@c14ffcde608da12f5c2d4d9b81a63c7e618b3eed#egg=scarlett_os
simplegeneric==0.8.1
six==1.10.0
snowballstemmer==1.2.1
Sphinx==1.5.5
stevedore==1.18.0
termcolor==1.1.0
terminado==0.6
testpath==0.3
tornado==4.5.1
tox==2.7.0
traitlets==4.3.2
typing==3.6.1
virtualenv==15.0.3
virtualenv-clone==0.2.6
virtualenvwrapper==4.7.2
voluptuous==0.9.3
watchdog==0.8.3
wcwidth==0.1.7
webencodings==0.5.1
widgetsnbextension==2.0.0
wmctrl==0.3
wrapt==1.10.10
xdot==0.7

编辑:(还有一个细节,为什么我不只是补丁上下文管理器或修饰器?)?

关于他们的设计选择,pytest-mock有一个相当不错的章节,以及为什么他们决定放弃嵌套的with语句和堆积在一起的装饰器。链接是here,但让我在这里提到几个,以防万一:

代码语言:javascript
复制
- excessive nesting of with statements breaking the flow of test
- receiving the mocks as parameters doesn't mix nicely with pytest's approach of naming fixtures as parameters, or pytest.mark.parametrize;

因此,如果使用这个插件可以使我的代码变得更简洁,我想让它成为现实。如果那是不可能的,那也许我需要重新考虑一下。

EN

回答 2

Stack Overflow用户

发布于 2017-05-09 12:06:12

您得到的错误是测试中的代码命中了AttributeError而不是TypeError

细节是,假设某个对象有一个.typename成员,但它没有。

我想一旦你解开了这个谜语,剩下的就会很好了。

我看到有人打开了https://github.com/pytest-dev/pytest-mock/issues/84 (您?),让我们等待pytest开发人员来分析它,以防两个插件之间存在不兼容。

票数 5
EN

Stack Overflow用户

发布于 2017-04-29 18:31:07

为什么不使用函数修饰器或上下文管理器运行您的模拟,以确保它们被关闭?例如,在test_subprocess_map_type_to_command中,没有执行所有这些操作来模拟scarlett_os.subprocess.Subprocess.check_command_type

代码语言:javascript
复制
mock_check_command_type = mocker.MagicMock(name="mock_check_command_type")
mock_check_command_type.return_value = True
mocker.patch.object(scarlett_os.subprocess.Subprocess, 'check_command_type', mock_check_command_type)

为什么不直接使用上下文管理器并做:

代码语言:javascript
复制
with mock.patch.object(
    scarlett_os.subprocess.Subprocess,
    'check_command_type',
    return_value=True):

它会更简洁,并将确保您的模拟不会泄漏。

更好的是,如果您的模拟应用于整个函数(我认为其中一些应用了),您可以在函数顶部使用一个装饰器:

代码语言:javascript
复制
@mock.patch('scarlett_os.subprocess.Subprocess.check_command_type', 
            return_value=True)
票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/43698734

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档