General mechanisms used by git-ubuntu tests
We ship the test suite and this is available from the snap as git-ubuntu.self-test. This is done by CI prior to publication of the snap.
You can use git-ubuntu.self-test from the snap to test a local development tree with dependencies from the snap. TBC: how.
Many tests are parameterized using pytest’s parametrize mechanism.
General approach to testing
We try to test what is most likely to fail.
For example, testing that we call an API in the expected way when the code that does it is straightforward is of limited value if the risk is that we’ve misunderstood the API and are calling it wrong.
Most code should come with tests. Some old code doesn’t have tests; we are trying to improve this incrementally by writing tests in areas of code that we touch. Don’t feel that you have to go down the rabbit hole. Landing a simple fix doesn’t require that you raise the quality of everything you touch, but some incremental improvement is appreciated such that it’s possible to say that the quality is higher than it was previously.
git-ubuntu operations often operate on inputs that are git repositories or source packages, and then outputs the same. These inputs and outputs are fundamentally data structures just as a simple list is a data structure. We might test a function that operates on a list by providing sample lists as inputs and making assertions about the output. Similarly, most git-ubuntu tests provide sample repositories and source packages as inputs, and make assertions about output repositories.
Providing an input repository or input source package is a tedious and
repetitive task, so it is automated. Input source repositories can be specified
as data structures by using the
gitubuntu.repo_builder module allows git repositories to
be specified as data structures. Where an input git repository needs a source
package in a tree object,
gitubuntu.repo_builder.SourceTree can be
used to construct it.
Please avoid checking in already constructed git repositories or source trees as test inputs. These will inevitably contains binary blobs and/or boilerplate. These are opaque and hide the important part of data structure that actually matter to the tests, making such tests harder to work with. Instead, the “builder” style described above eliminates binary blobs and, with default arguments, the boilerplate. Where edge case inputs are required that are not already supported by the builder modules, the builder modules themselves should be enhanced instead. And if the way the builder modules are called become obtuse, then they should be refactored together with the tests.
Commonly used fixtures
git-ubuntu tests make extensive use of pytest’s fixture functionality.
You should make yourself familiar with the pytest built-in tmpdir fixture. Note
that it supplies a
py.path object, not a string. In some cases, this
needs explicit conversion with
git-ubuntu uses the pygit2 library extensively. However
git-ubuntu repositories have a particular structure for which we have many
helper methods so we wrap the
pygit2.Repository in a
GitUbuntuRepository. The underlying
pygit2.Repository object is available via the
gitubuntu.git_repository.GitUbuntuRepository.raw_repo attribute on a
gitubuntu.test_fixtures.repo() fixture provides a
gitubuntu.git_repository.GitUbuntuRepository instance in a temporary
directory. Alternatively you can use the lower level
gitubuntu.test_fixtures.pygit2_repo() fixture to get an unwrapped
pygit2.Repository instance in a temporary directory.
gitubuntu.repo_builder operates on
directly, so tests can either use a
fixture directly, or they can use a
fixture and access its
gitubuntu.git_repository.GitUbuntuRepository.raw_repo attribute to use
Let’s say you want to test a function that looks up the package version string from debian/changelog in a git tree object:
# repo is our test fixture. When pytest runs this test, it will supply
# it with an empty GitUbuntuRepository instance in a temporary
# First we describe a git repository in a data structure and ask it to
# write itself into repo.raw_repo.
# We give the commit a name here, purely for the internal
# reference that comes later
# The Placeholder() instance is used to internally reference the
# commit we created earlier
# In the line above, raw_repo is the underlying pygit2.Repository
# Now our git repository contains a commit that contains a source
# package tree and a tag that refers to the commit.
# We can fetch the tree object using normal pygit2 operations
tree = (
# Now we can test the our code in the normal way.
# repo_builder.SourceTree() above defaults to a version of '1-1' and we
# didn't override it, so that's the version string that our
# function-under-test should return.
actual_result = repo.get_changelog_versions_from_treeish(str(tree.id))
assert actual_result == '1-1'