This section contains a few recipes to help you adding new tests to the existing code base.
Warning
Most of the tests you will see in qiBuild source code were written a long time ago and do not follow these guidelines.
qiBuild code base is somewhat hard to test for several reasons.
It’s a build framework, so there are lots of code which interact with the operating system.
More specifically
Here are a few guide lines which should help you overcome these issues and writing new tests.
Do NOT use unittest to write new tests.
Use py.test instead
For instance
# In mypackage/mymodule.py
def frobnicate(bar=True):
pass
# in mypackage/test/test_mymodule.py
from mypackage.mymodule import frobnicate
def test_frobnicate():
res = frobnicate(bar=True)
assert res is False
res = frobnicate(bar=False)
assert res is True
And then you can quick run the frobnicate tests with
$ py.test2 mypackage/test/test_mymodule.py
# or:
$ py.test2 -k frobnicate
Basically, inside the code of an action, you should just:
Generally speaking, the following code is hard to test:
class Foo:
def __init__(self):
# Reading some config files from the filesystem
self.config = read_config()
def do_something(self):
if self.config.foo_bar:
do_foo_bar()
class MyClass():
def __init__(self):
self.foo = Foo()
def frobnicate(self):
res = self.foo.do_something()
# Do something with res
If you want to test MyClass.frobnicate, you have to create the resources used by the Foo class.
By a simple refactoring, you can make the situation much easier for you
class MyClass():
def __init__(self, foo=None)
if foo is None:
self.foo = Foo()
else:
self.foo = foo
Then in your test, you can do something like:
class FakeFoo:
def __init__(self, res):
self.res = res
def do_something():
return res
def test_frobnicate():
fake_foo = FakeFoo(False)
my_class = MyClass(foo=fake_foo)
# Do some test with my_class.frobnicate()
See also
Most of qibuild source code use exception as a way to display error messages to the end users.
# In the code that is used by every action:
try:
module.do()
except Exception as e:
ui.error(str(e))
So it’s important to check the correctness of the error message.
This is how to do it:
import pytest
# pylint: disable-msg=E1101
with pytest.raises(Exception) as e:
do_something_that_should_raise()
assert "Bad input" in e.value.message
Notes:
See also
If you have some code looking like:
def read_config(fp):
""" Parse the config file from the file-like object
"""
You can just use StringIO
from StringIO import StringIO
def test_parse_config():
config_fp = StringIO("\n")
config = read_config(config_fp)
# Do something with config
It also works for writing instead of reading, obviously.
Most of the stdlib of Python accepts both file paths and file-like objects.
In this case you should use the built-in tmpdir from py.test
def test_foo(tmpdir)
work = tmpdir.mkdir("work")
dot_di = tmpdir.mkdir(".qi")
qibuild_xml = dot_qi.join("qibuild.xml")
qibuild_xml.write("....")
worktree = qisys.worktree.open(work.strpath)
Note that tmpdir is a py.._path.local.LocaPath instance (from the pylib project by the same author of pytest)
This is why you have all these beautiful methods available.
tmpdir is a magic function argument that py.test provides.
You are sure that this directory is created empty, is writeable, and will be removed at the end of the test.
See also
Here we introduce an other library called mock.
The idea is that we will dynamically replace a function by an other. (This is also called monkey-patching)
There are some tools in py.test for monkey patching, but the mock project contains much more features.
See also
Here’s how to use it in py.test:
import mock
def test_foo():
with mock.patch('module.fun') as m:
m.return_value = True
# From now on module.fun is replaced by a
# function that always return True
# do something that uses module.fun
# You can also write checks using m.called_args
# here.
Some classes are available for you to be used as mock.
(It’s good idea to re-use the same mock for all the tests)
So, here’s how you can write code that uses qibuild.interact
# in foo.py
import qibuild.interact
def foo():
bar = qibuild.interact.ask_yes_no("bar ?")
spam = qibuild.interact.ask_string("please enter spam value")
import mock
from qibuild.test.interact import FakeInteract
def test_foo():
fake_interact = FakeInteract([False, "eggs"])
with mock.patch('qibuild.interact', fake_interact):
# Do something that uses qibuild.interact.
# Everything will happen as is ask_yes_no returned
# False and ask_string returned "eggs"
Note that you must built the FakeInteract object with the returned value of the various qibuild.interact.ask_ functions.
If you do not want to use a list, you can use a dictionary instead, the keys should match parts of the questions that are asked.
def test_foo():
fake_interact = FakeInteract({"bar" : False, "spam" : "egges"})
There are times where you really need a ‘real’ worktree and some real source code.