{ "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "# Skip the Docs with Mocks" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ " > #### COhPy - Jan 25, 2016\n", "\n", " > #### Andrew Kubera" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## What are Mocks?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "* Objects that allow any method to be called, or attribute to be retrieved.\n", " * You may set the return value\n", " * Default: all methods return a new mock\n", "* Useful for writing tests\n", " * Simplify 'complex' objects & operations like sockets, HTTP Server Responses, database state" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "* ***Tracks all calls made to it***" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## How to use Mock" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true, "slideshow": { "slide_type": "fragment" } }, "outputs": [], "source": [ "from unittest import mock" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[call.foo()]" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.Mock()\n", "m.foo()\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[call.spam(1, a=5)]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.Mock()\n", "m.spam(1, a=5)\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[call.spam(1), call.spam(2), call.spam(3)]" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.Mock()\n", "m.spam(1)\n", "m.spam(2)\n", "m.spam(3)\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[call.spam(1),\n", " call.spam(2),\n", " call.spam(3),\n", " call.spam(100),\n", " call.spam().next(),\n", " call.spam(200),\n", " call.spam().next()]" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m.spam(100).next()\n", "m.spam(200).next()\n", "m.mock_calls" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Use to Investigate How Libraries Work\n", "---\n", "#### Why?\n", "\n", "* Don't have the docs with you?\n", "* Bad documentation?\n", "* Fun?" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "def someones_function(obj):\n", " ...\n", " obj.open_connection('http://their.secret.url/data')\n", " ...\n", " return obj.read() " ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[call.open_connection('http://their.secret.url/data'), call.read()]" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.Mock()\n", "someones_function(m)\n", "m.mock_calls\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### One more thing..." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "ename": "TypeError", "evalue": "object of type 'Mock' has no len()", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mm\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMock\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmock_calls\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mTypeError\u001b[0m: object of type 'Mock' has no len()" ] } ], "source": [ "m = mock.Mock()\n", "len(m)\n", "m.mock_calls" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Solution: MagicMock" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[call.__len__()]" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "len(m)\n", "m.mock_calls" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "* Most builtin/standard python libraries use *magics*" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Magics may return non-mock objects" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false, "scrolled": true, "slideshow": { "slide_type": "-" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "len: 0\n", "int: 1\n", "float: 1.0\n", "item: \n" ] } ], "source": [ "m = mock.MagicMock()\n", "print('len:', m.__len__())\n", "print('int:', m.__int__())\n", "print('float:', m.__float__())\n", "print('item:', m[22])" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "0" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m.__len__()" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### But not your own magic..." ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false, "slideshow": { "slide_type": "-" } }, "outputs": [ { "ename": "AttributeError", "evalue": "__foo__", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mm\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mmock\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mMagicMock\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m__foo__\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mmock_calls\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;32m/opt/local/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/unittest/mock.py\u001b[0m in \u001b[0;36m__getattr__\u001b[0;34m(self, name)\u001b[0m\n\u001b[1;32m 578\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mAttributeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"Mock object has no attribute %r\"\u001b[0m \u001b[0;34m%\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 579\u001b[0m \u001b[0;32melif\u001b[0m \u001b[0m_is_magic\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 580\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mAttributeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mname\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 581\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0m_mock_unsafe\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 582\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mname\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mstartswith\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'assert'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'assret'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", "\u001b[0;31mAttributeError\u001b[0m: __foo__" ] } ], "source": [ "m = mock.MagicMock()\n", "m.__foo__()\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[call._foo__()]" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "m._foo__()\n", "m.mock_calls" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Now lets play with mocks" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[call.__float__()]" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "\"%f\" % m\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[call.__int__(), call.__str__(), call.__float__()]" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "\"%d %s %E\" % (m, m, m)\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "class Foo:\n", " \n", " def __div__(self, v):\n", " pass\n" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "ename": "TypeError", "evalue": "unsupported operand type(s) for /: 'Foo' and 'int'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mfoo\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mFoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mx\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfoo\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: unsupported operand type(s) for /: 'Foo' and 'int'" ] } ], "source": [ "foo = Foo()\n", "x = foo / 4" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "fragment" } }, "source": [ "### Why Doesn't foo / 4 work??" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[call.__truediv__(2)]" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "m / 2\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[call.__floordiv__(2)]" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "m // 2\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[call.__rtruediv__(2),\n", " call.__floordiv__(2),\n", " call.__mod__(2),\n", " call.__mul__(2),\n", " call.__pow__(2)]" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "2 / m\n", "m // 2\n", "m % 2\n", "m * 2\n", "m ** 2\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4437662744\n", "4437662744\n" ] }, { "data": { "text/plain": [ "[call.__add__(2), call.__iadd__(2)]" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "m.__iadd__.return_value = m\n", "print(id(m))\n", "m + 2\n", "m += 2\n", "print(id(m))\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "l, t, s = mock.MagicMock(), mock.MagicMock(), mock.MagicMock()\n", "list(l)\n", "tuple(t)\n", "set(s);" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false, "slideshow": { "slide_type": "fragment" } }, "outputs": [ { "data": { "text/plain": [ "[call.__iter__(), call.__len__()]" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "l.mock_calls" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[call.__iter__(), call.__len__()]" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "t.mock_calls" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[call.__iter__()]" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "s.mock_calls" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## How can my class be 'range-able'?" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false, "slideshow": { "slide_type": "-" } }, "outputs": [ { "data": { "text/plain": [ "[call.__index__()]" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "range(m)\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[call.__iter__()]" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "[x for x in m]\n", "m.mock_calls" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "[call.__len__(),\n", " call.__iter__(),\n", " call.__len__(),\n", " call.__iter__(),\n", " call.__len__(),\n", " call.__iter__()]" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "m = mock.MagicMock()\n", "np.array(m)\n", "m.mock_calls" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Limitations" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "* Mocks do not overload `__getattr__` and `__repr__`\n", "* `__repr__` is always used the same - no big deal\n", "* Get around attributes\n", " * Store result of dir\n", " * compare with dir after your code " ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "[]" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "m = mock.MagicMock()\n", "np.shape(m)\n", "m.mock_calls" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Default Mock Attributes/Properties Set" ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false, "slideshow": { "slide_type": "-" } }, "outputs": [ { "data": { "text/plain": [ "{'assert_any_call',\n", " 'assert_called_once_with',\n", " 'assert_called_with',\n", " 'assert_has_calls',\n", " 'assert_not_called',\n", " 'attach_mock',\n", " 'call_args',\n", " 'call_args_list',\n", " 'call_count',\n", " 'called',\n", " 'configure_mock',\n", " 'method_calls',\n", " 'mock_add_spec',\n", " 'mock_calls',\n", " 'reset_mock',\n", " 'return_value',\n", " 'side_effect'}" ] }, "execution_count": 30, "metadata": {}, "output_type": "execute_result" } ], "source": [ "m = mock.MagicMock()\n", "default_attrs = set(dir(m))\n", "default_attrs" ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false, "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/plain": [ "{'shape'}" ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "np.shape(m)\n", "set(dir(m)) - default_attrs" ] }, { "cell_type": "markdown", "metadata": { "collapsed": true, "slideshow": { "slide_type": "slide" } }, "source": [ "# End." ] } ], "metadata": { "celltoolbar": "Slideshow", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.1" } }, "nbformat": 4, "nbformat_minor": 0 }