подстановка mock и side_effect — сохранение доступа к исходному классу и его атрибутам

#python #mocking

#python #издевательство

Вопрос:

Я хочу создать метод mock _subprocess для конкретного экземпляра класса. В частности, когда задача запускается pip freeze как команда (в этом случае она taskname замораживается).

 class Command(object):
    def __init__(self, mgr, taskname, config):
        self.mgr = mgr
        self.taskname = taskname

        self.config = config
        self.append = self.config.get("append", False)
        self.stderr = ""

    def _subprocess(self, cmd, fnp_o, self_=None):
        try:
            mode = "a" if self.append else "w"

            fnp_stderr = self.mgr._get_fnp("log")
            with open(fnp_stderr, "a") as ferr:

                ferr.write("cmd: %snstderr begin:n" % (cmd))

                with open(fnp_o, mode) as fo:
                    proc = subprocess.check_call(
                        cmd.split(),
                        stdout=fo,
                        stderr=ferr,
                        cwd=self.mgr.workdir,
                        encoding="utf-8",
                    )
                ferr.write("stderr endnn")

        except (Exception,) as e:
            if cpdb(): pdb.set_trace()
            raise        
  

Это метод тестирования:

 def fake_subprocess(self, cmd, fnp_o, self_):
    try:
        raise NotImplementedError("fake_subprocess(%s)" % (locals()))
    except (Exception,) as e:
        pdb.set_trace()
        raise

def test_001_scan(self):
    try:

        with patch.object(Command, '_subprocess', side_effect = self.fake_subprocess) as mock_method:

            options = self.get_options()
            self.mgr = Main(options)
            self.mgr.process()

    except (Exception,) as e:
        pdb.set_trace()
        raise
  

Моя проблема двоякая.

Во-первых, self in fake_subprocess ссылается на UnitTest объект, а не на Command объект. Мое использование self_ параметра позволяет обойти это.

Во-вторых, в большинстве случаев, за исключением pip freeze того, что я хочу запустить исходный подпроцесс, а не поддельный.

Теперь я, вероятно, смогу справиться с этим, сохранив дополнительную ссылку на Command._subprocess и используя self_

Но есть ли более элегантный способ? Очень наивно, когда дело доходит до unittest.Mock .

Ответ №1:

Это то, что в итоге сработало для меня:

тестовая сторона

 def fake_subprocess(self, cmd, fnp_o, self_):
    try:
        if self_.taskname != "freeze":
            return self_._subprocess_actual(cmd, fnp_o, self_)

        with open(fnp_o, self_.mode) as fo:
            fo.write(self.fake_subprocess_payload["freeze"])

    except (Exception,) as e:
        raise

def test_001_scan(self):
    try:

        with patch.object(
            Command, "_subprocess", side_effect=self.fake_subprocess
        ) as mock_method:

            options = self.get_options()
            self.mgr = Main(options)
            self.mgr.process()

    except (Exception,) as e:
        raise
  

фактическая сторона кода

 class Command(object):

    def _subprocess(self, cmd, fnp_o, self_=None):
        try:

            fnp_stderr = self.mgr._get_fnp("log")
            with open(fnp_stderr, "a") as ferr:

                ferr.write("cmd: %snstderr begin:n" % (cmd))

                with open(fnp_o, self.mode) as fo:
                    proc = subprocess.check_call(
                        cmd.split(), stdout=fo, stderr=ferr, cwd=self.mgr.workdir
                    )
                ferr.write("stderr endnn")

        except (Exception,) as e:
            if cpdb():
                pdb.set_trace()
            raise

    _subprocess_actual = _subprocess

    def run(self):
        try:
            t_cmd = self.config["cmdline"]  # .replace(r"\","\")
            t_fnp = os.path.join(self.mgr.workdir, self.config["filename"])

            fnp_log = "subprocess.log"

            cmd = sub_template(t_cmd, self, self.mgr.vars)

            fnp_o = sub_template(t_fnp, self, self.mgr.vars)
            self._subprocess(cmd=cmd, fnp_o=fnp_o, self_=self)

        except (Exception,) as e:
            if cpdb():
                pdb.set_trace()
            raise