Change in vdsm[master]: tests: Add simple mocking library

nsoffer at redhat.com nsoffer at redhat.com
Tue Nov 12 09:12:02 UTC 2013


Nir Soffer has uploaded a new change for review.

Change subject: tests: Add simple mocking library
......................................................................

tests: Add simple mocking library

New mock module provides a MockCallable, useful for mocking functions
and other callables required for testing stuff that needs special
environment for running.

MockCallable is using the record and play aprroach; After a mock is
created, you tell it what calls to expect. If needed, you may specify
the result of each call. The result may any return value or rasing an
exception.

When the mock is called by the code under test, it will raise a
MockError as soon as it is called with unexpected arguments or keyword
arguments, or calls are invoked in different order.

When tests are done, you should invoke verify() to make sure that all
expected calls were invoked. If some expected calls were called yet,
this will raise a MockError.

See mockTests for example usage.

Change-Id: Ia5d874f553b6a983652ed745d7d8554716e7a15e
Signed-off-by: Nir Soffer <nsoffer at redhat.com>
---
M tests/Makefile.am
A tests/mock.py
A tests/mockTests.py
3 files changed, 189 insertions(+), 0 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/55/21155/1

diff --git a/tests/Makefile.am b/tests/Makefile.am
index 439aee4..acffb41 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -47,6 +47,7 @@
 	md_utils_tests.py \
 	miscTests.py \
 	mkimageTests.py \
+	mockTests.py \
 	monkeypatchTests.py \
 	mountTests.py \
 	netconfTests.py \
@@ -105,6 +106,7 @@
 	$(test_modules) \
 	apiData.py \
 	hookValidation.py \
+	mock.py \
 	monkeypatch.py \
 	testrunner.py \
 	testValidation.py \
diff --git a/tests/mock.py b/tests/mock.py
new file mode 100644
index 0000000..29f31c7
--- /dev/null
+++ b/tests/mock.py
@@ -0,0 +1,93 @@
+#
+# Copyright 2013 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301  USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+
+class MockError(Exception):
+    pass
+
+
+class _Call(object):
+
+    def __init__(self, args, kw, result=None):
+        self.args = args
+        self.kw = kw
+        self.result = result
+
+    def __eq__(self, other):
+        return self.args == other.args and self.kw == other.kw
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __str__(self):
+        return "Call(args=%r kw=%r)" % (self.args, self.kw)
+
+
+class MockCallable(object):
+
+    def __init__(self):
+        self.expectations = []
+
+    def expect(self, *args, **kw):
+        """
+        Records an expected call with args and kw.
+
+        Returns object so you can set the record the result in the same call:
+
+            mock.expect("foo").result(-1)
+        """
+        call = _Call(args, kw)
+        self.expectations.append(call)
+        return self
+
+    def result(self, value):
+        """
+        Sets result for last recorded expectation.
+
+        If value is an Exception, this call will result in raising it.
+        """
+        if not self.expectations:
+            raise MockError("Not expecting anything")
+        self.expectations[-1].result = value
+
+    def verify(self):
+        """
+        Verify that all expectations were met.
+
+        If expecting more calls, raise a MockError with the details of the next
+        one.
+        """
+        if self.expectations:
+            raise MockError("Expecting %s" % self.expectations[-1])
+
+    def __call__(self, *args, **kw):
+        """
+        Verifies a call and return expected result.
+        """
+        actual = _Call(args, kw)
+        if not self.expectations:
+            raise MockError("Unexpected %s" % actual)
+        expected = self.expectations.pop(0)
+        if expected != actual:
+            raise MockError("Exptected %s but got %s" % (expected, actual))
+        if isinstance(expected.result, Exception):
+            raise expected.result
+        return expected.result
diff --git a/tests/mockTests.py b/tests/mockTests.py
new file mode 100644
index 0000000..eebd65a
--- /dev/null
+++ b/tests/mockTests.py
@@ -0,0 +1,94 @@
+#
+# Copyright 2013 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301  USA
+#
+# Refer to the README and COPYING files for full details of the license
+#
+
+import mock
+from testrunner import VdsmTestCase as TestCaseBase
+
+
+class MockCallableTests(TestCaseBase):
+
+    def testNothingExpected(self):
+        m = mock.MockCallable()
+        m.verify()
+
+    def testSomeExpectationNotMet(self):
+        m = mock.MockCallable()
+        m.expect("foo")
+        m.expect("bar")
+        m("foo")
+        self.assertRaises(mock.MockError, m.verify)
+
+    def testAllExpectationsMet(self):
+        m = mock.MockCallable()
+        m.expect("foo")
+        m.expect("bar")
+        m("foo")
+        m("bar")
+        m.verify()
+
+    def testCallOrder(self):
+        m = mock.MockCallable()
+        m.expect("foo")
+        m.expect("bar")
+        self.assertRaises(mock.MockError, m, "bar")
+
+    def testMissingArg(self):
+        m = mock.MockCallable()
+        m.expect("foo", "bar", "baz")
+        self.assertRaises(mock.MockError, m, "foo", "bar")
+
+    def testArgsOrderDiffer(self):
+        m = mock.MockCallable()
+        m.expect("foo", "bar", "baz")
+        self.assertRaises(mock.MockError, m, "foo", "baz", "bar")
+
+    def testExpectedKeywordArgs(self):
+        m = mock.MockCallable()
+        m.expect(foo=1, bar=2)
+        m(foo=1, bar=2)
+
+    def testMissingKeywordArg(self):
+        m = mock.MockCallable()
+        m.expect(foo=1, bar=2)
+        self.assertRaises(mock.MockError, m, foo=1)
+
+    def testKeywordArgValueDiffer(self):
+        m = mock.MockCallable()
+        m.expect(foo=42)
+        self.assertRaises(mock.MockError, m, foo=7)
+
+    def testNoResult(self):
+        m = mock.MockCallable()
+        m.expect("foo")
+        self.assertEquals(None, m("foo"))
+
+    def testReturnsResult(self):
+        m = mock.MockCallable()
+        m.expect("foo").result("bar")
+        self.assertEquals("bar", m("foo"))
+
+    def testRaises(self):
+        class WAT(Exception):
+            pass
+        m = mock.MockCallable()
+        m.expect("foo").result(WAT())
+        self.assertRaises(WAT, m, "foo")
+        m.verify()


-- 
To view, visit http://gerrit.ovirt.org/21155
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: Ia5d874f553b6a983652ed745d7d8554716e7a15e
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer at redhat.com>


More information about the vdsm-patches mailing list