如何为 GitPython 克隆/拉取函数编写单元测试?

问题描述 投票:0回答:2

我有一个 python 项目,它使用 GitPython 对远程 Git 存储库执行克隆和拉取功能。

举个简单的例子:

import git
from git import Git
from git import Repo


def clone_and_checkout(full_dir, git_url, repo_ver):

    repo = Repo.clone_from(
        url=git_url,
        to_path=full_dir
    )

    # Trigger re-create if repository is bare
    if repo.bare:
        raise git.exc.InvalidGitRepositoryError

    # Set origin and pull
    origin = repo.remotes.origin
    origin.pull()

    # Check out desired version of repository
    g = Git(full_dir)
    g.checkout(repo_ver)

我希望能够为此功能编写单元测试,但显然这需要接触当前的外部系统。

我很好奇是否有人有模拟这种外部交互的经验,就像使用 Mock 模拟 HTTP 调用一样。我希望能够以一种可以在测试时模拟的方式执行这些任务,而无需调用实际的 Git 远程。

我应该如何为此编写测试?

编辑:为了更清楚地了解我的要求,我应该提到我是 Mock 的新手,并且正在努力理解如何 Mock 这些类的实例而不是类本身。我的问题应该表述得更好 - 类似于“如何使用 Mock 来设置实例特定的属性,例如裸露?”

我已经了解了很多关于 Mock 的知识,并且已经弄清楚如何做到这一点,所以我将为我自己的问题提供答案。

python git unit-testing gitpython
2个回答
6
投票

这似乎是对 Mock 理解不完全,或者使用 Patch 方法的常见结果。

要做的第一件事是阅读模拟文档中的“修补位置”部分。有了这些信息,您应该能够使用 patch 函数来模拟上述函数中使用的 GitPython 对象。这些装饰器将出现在您的单元测试函数上方。

@mock.patch('gitter.Repo')
@mock.patch('gitter.Git')

为了为这些模拟对象之一的实例提供返回值,您可以使用 PropertyMock。这是利用此功能的单元测试的完整示例:

import gitter  # file containing our clone function
import mock
import unittest


class test_gitter(unittest.TestCase):

    @mock.patch('gitter.Repo')
    @mock.patch('gitter.Git')
    def runTest(self, mock_git, mock_repo):

        # Set the "bare" attribute of the Repo instance to be False
        p = mock.PropertyMock(return_value=False)
        type(mock_repo.clone_from.return_value).bare = p

        gitter.clone_and_checkout(
            '/tmp/docker',
            '[email protected]:docker/docker.git',
            'master'
        )
        mock_git.checkout.called_once_with('master')

0
投票

帮助来自
git
协议知识

其实,你不需要嘲笑任何东西。您可以测试所有功能,即本地 git 操作,例如:添加、提交、签出、rebase 或cherry-pick 以及远程操作,例如 fetch、push 或 pull,而无需设置 git 服务器。

本地和远程存储库使用这些协议之一交换数据:

  • local protocol
    (不使用网络,在unix中表示为
    file://
  • http(s)://
    协议
  • ssh://
    协议
  • git://
    协议

更多相关信息请参见 git 协议解释

创建git操作测试环境

当您指定本地文件系统中其他存储库的路径时,正在使用

local protocol
。因此,为了在干净且隔离的环境中执行测试,您唯一要做的就是安排两个存储库。惯例是将“遥控器”设置为
bare repository
。 然后另一个应该通过到第一个的路径设置上游,瞧! 从现在起,您就拥有了功能齐全的测试设置。对 Linus Torvalds 表示感谢。

使用服务器进行假 git 存储库的 Pytest 实现

import datetime
from pathlib import Path

import pytest
from git import Actor, Remote, Repo


@pytest.fixture
def fake_repo(tmp_path) -> "Helper":
    return Helper(tmp_path)


class Helper:
    """The main purpose of defining it as a class is to gather all the variables
    under one namespace, so that we don't need to define 6 separate pytest fixtures.

    You don't need git server to test pull/push operations. Since git offers
    "local protocol" - plain bare repository in your filesystem is fully
    compatible with http(s), ssh and git protocol (Neglecting authentication functionality).
    """

    def __init__(self, tmp_path_fixture: Path):
        self.local_repo_path: Path = tmp_path_fixture / "local-repo"
        remote_repo_path: Path = tmp_path_fixture / "remote-repo"
        remote_repo_path.mkdir()

        self.remote_repo: Repo = Repo.init(str(remote_repo_path), bare=True)

        self.repo: Repo = Repo.init(str(self.local_repo_path))
        self.remote_obj: Remote = self.repo.create_remote("origin", str(remote_repo_path))

        # do initial commit on origin
        commit_date = self.tz_datetime(2023, 10, 1, 11, 12, 13)
        self.repo.index.commit("Initial commit", author_date=commit_date, commit_date=commit_date)
        self.remote_obj.push("master")

    def local_graph(self) -> str:
        return self.repo.git.log("--graph --decorate --pretty=oneline --abbrev-commit".split())

    @classmethod
    def tz_datetime(cls, *args, **kwargs):
        tz_info = datetime.datetime.utcnow().astimezone().tzinfo
        return datetime.datetime(*args, **kwargs, tzinfo=tz_info)

    def do_commit(self, *files_to_add, msg: str = "Sample commit message.", author: str = "author") -> None:
        author = Actor(author, f"{author}@example.com")
        # constant date helps to make git hashes reproducible, since the date affects commit sha value
        date = self.tz_datetime(2023, 10, 4, 15, 45, 13)

        self.repo.index.add([str(file_) for file_ in files_to_add])
        self.repo.index.commit(msg, author=author, committer=author, author_date=date, commit_date=date)


def test_preparing_repo(fake_repo):
    file_1 = fake_repo.local_repo_path / "file_1.txt"

    file_1.write_text("Initial file contents")
    fake_repo.do_commit(file_1, msg="First commit.")

    fake_repo.repo.git.checkout("-b", "new_branch")
    file_1.write_text("Changed file contents")
    fake_repo.do_commit(file_1, msg="Second commit.")

    fake_repo.repo.git.checkout("-b", "other_branch")
    file_1.write_text("Another change")
    fake_repo.do_commit(file_1, msg="Change on other_branch.")

    assert (
        fake_repo.repo.git.branch("-a")
        == """\
  master
  new_branch
* other_branch
  remotes/origin/master"""
    )

    assert (
        fake_repo.local_graph()
        == """\
* 1743bd6 (HEAD -> other_branch) Change on other_branch.
* 2696781 (new_branch) Second commit.
* 5ea439d (master) First commit.
* 04fc02f (origin/master) Initial commit"""
    )


© www.soinside.com 2019 - 2024. All rights reserved.