Add description and examples for nose
authorJon Speicher <jon.speicher@gmail.com>
Sat, 27 Jul 2013 12:39:09 +0000 (08:39 -0400)
committerW. Trevor King <wking@tremily.us>
Sat, 9 Nov 2013 18:27:50 +0000 (10:27 -0800)
python/sw_engineering/SoftwareEngineering.ipynb

index b2c22e549eda04489962e29fe4784f7c596c6d3a..cd45d07e78f2eda94ffb9f8346fb3d049fbf8d61 100644 (file)
        "output_type": "stream",
        "stream": "stdout",
        "text": [
-        "Overwriting sightings.py\n"
+        "Writing sightings.py\n"
        ]
       }
      ],
        "output_type": "stream",
        "stream": "stdout",
        "text": [
-        "Overwriting test_sightings.py\n"
+        "Writing test_sightings.py\n"
        ]
       }
      ],
      "language": "python",
      "metadata": {},
      "outputs": [],
-     "prompt_number": 17
+     "prompt_number": 11
     },
     {
      "cell_type": "markdown",
        "output_type": "pyerr",
        "traceback": [
         "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mAssertionError\u001b[0m                            Traceback (most recent call last)",
-        "\u001b[0;32m<ipython-input-18-2d797333ff4c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      5\u001b[0m     \u001b[0;32massert\u001b[0m \u001b[0;36m4\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0madd_two_plus_two\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2 + 2 didn't equal 4\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
-        "\u001b[0;32m<ipython-input-18-2d797333ff4c>\u001b[0m in \u001b[0;36mtest_add_two_plus_two_equals_four\u001b[0;34m()\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtest_add_two_plus_two_equals_four\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;32m----> 5\u001b[0;31m     \u001b[0;32massert\u001b[0m \u001b[0;36m4\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0madd_two_plus_two\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2 + 2 didn't equal 4\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      7\u001b[0m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+        "\u001b[0;32m<ipython-input-12-2d797333ff4c>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m      5\u001b[0m     \u001b[0;32massert\u001b[0m \u001b[0;36m4\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0madd_two_plus_two\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2 + 2 didn't equal 4\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 7\u001b[0;31m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+        "\u001b[0;32m<ipython-input-12-2d797333ff4c>\u001b[0m in \u001b[0;36mtest_add_two_plus_two_equals_four\u001b[0;34m()\u001b[0m\n\u001b[1;32m      3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mtest_add_two_plus_two_equals_four\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;32m----> 5\u001b[0;31m     \u001b[0;32massert\u001b[0m \u001b[0;36m4\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0madd_two_plus_two\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"2 + 2 didn't equal 4\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m      6\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m      7\u001b[0m \u001b[0mtest_add_two_plus_two_equals_four\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
         "\u001b[0;31mAssertionError\u001b[0m: 2 + 2 didn't equal 4"
        ]
       }
      ],
-     "prompt_number": 18
+     "prompt_number": 12
     },
     {
      "cell_type": "markdown",
       "As you can see, the failed line is highlighted and the text supplied to the `assert` statement is printed. In addition, IPython provides what is known as a *traceback*. The traceback shows you which function called the function that called the function that called assert, ad infinitum. This can be a very helpful debugging aid."
      ]
     },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "***\n",
+      "# Nose\n",
+      "***\n",
+      "\n",
+      "Running one test by hand is fine, but imagine if we had a large, complex system and many, many test cases per function. How would we run all of our tests? Running them by hand in an IPython interpreter or notebook would defeat the purpose of automation. We could create a \"test runner\" file that called each of our test functions in sequence:\n",
+      "\n",
+      "    import test_sightings\n",
+      "\n",
+      "    test_read_sightings_from_file_when_file_does_not_exist()\n",
+      "    test_read_sightings_from_file_when_file_is_empty()\n",
+      "    test_read_sightings_from_file_when_file_has_blank_line()\n",
+      "    ...\n",
+      "    test_count_wolverines_with_no_wolverines()\n",
+      "    test_count_wolverines_with_one_wolverine()\n",
+      "    ...\n",
+      "\n",
+      "You can imagine this file growing very large. You can also be sure that at some point, some developer is going to write a test but forget to add it to the \"runner\", thereby running the risk of missing a bug. Fortunately, there is a very popular package that works with Python called `nose`. `nose` \"sniffs out your tests\" and runs them for you. `nose` is installed by default with several Python distributions, and its primary interface is a command-line runner script called `nosetests`."
+     ]
+    },
+    {
+     "cell_type": "code",
+     "collapsed": false,
+     "input": [
+      "!nosetests"
+     ],
+     "language": "python",
+     "metadata": {},
+     "outputs": [
+      {
+       "output_type": "stream",
+       "stream": "stdout",
+       "text": [
+        ".........\r\n",
+        "----------------------------------------------------------------------\r\n",
+        "Ran 9 tests in 0.003s\r\n",
+        "\r\n",
+        "OK\r\n"
+       ]
+      }
+     ],
+     "prompt_number": 15
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "There is even a plugin for the IPython notebook that runs `nose` and produces colored cells and tracebacks within the notebooks themselves. The plugin is included in the directory containing this notebook."
+     ]
+    },
     {
      "cell_type": "code",
      "collapsed": false,
      "language": "python",
      "metadata": {},
      "outputs": [],
-     "prompt_number": 12
+     "prompt_number": 16
     },
     {
      "cell_type": "code",
      "outputs": [
       {
        "html": [
-        "<div id=\"ipython_nose_0807c237e17a408b8f8b351c1716ab5b\"></div>"
+        "<div id=\"ipython_nose_a8474679bbd64af09651e2ad71d36bcc\"></div>"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_0807c237e17a408b8f8b351c1716ab5b = $(\"#ipython_nose_0807c237e17a408b8f8b351c1716ab5b\");"
+        "document.ipython_nose_a8474679bbd64af09651e2ad71d36bcc = $(\"#ipython_nose_a8474679bbd64af09651e2ad71d36bcc\");"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "document.ipython_nose_0807c237e17a408b8f8b351c1716ab5b.append($(\"<span>.</span>\"));"
+        "document.ipython_nose_a8474679bbd64af09651e2ad71d36bcc.append($(\"<span>.</span>\"));"
        ],
        "output_type": "display_data"
       },
       {
        "javascript": [
-        "delete document.ipython_nose_0807c237e17a408b8f8b351c1716ab5b;"
+        "document.ipython_nose_a8474679bbd64af09651e2ad71d36bcc.append($(\"<span>F</span>\"));"
+       ],
+       "output_type": "display_data"
+      },
+      {
+       "javascript": [
+        "delete document.ipython_nose_a8474679bbd64af09651e2ad71d36bcc;"
        ],
        "output_type": "display_data"
       },
         "    </script>\n",
         "    \n",
         "    <div class=\"noseresults\">\n",
-        "      <div class=\"nosebar fail leftmost\" style=\"width: 0%\">\n",
+        "      <div class=\"nosebar fail leftmost\" style=\"width: 50%\">\n",
         "          &nbsp;\n",
         "      </div>\n",
         "      <div class=\"nosebar skip\" style=\"width: 0%\">\n",
         "          &nbsp;\n",
         "      </div>\n",
-        "      <div class=\"nosebar pass rightmost\" style=\"width: 100%\">\n",
+        "      <div class=\"nosebar pass rightmost\" style=\"width: 50%\">\n",
         "          &nbsp;\n",
         "      </div>\n",
-        "      1/1 tests passed\n",
+        "      1/2 tests passed; 1 failed\n",
+        "    </div>\n",
+        "    \n",
+        "    <div class=\"nosefailure\">\n",
+        "        <div class=\"nosefailbanner\">\n",
+        "          failed: <span class=\"nosefailedfunc\">__main__.test_add_two_plus_two_equals_four</span>\n",
+        "            [<a class=\"nosefailtoggle\" href=\"#\">toggle traceback</a>]\n",
+        "        </div>\n",
+        "        <pre class=\"nosetraceback\">Traceback (most recent call last):\n",
+        "  File \"/Users/Jon/Applications/anaconda/lib/python2.7/unittest/case.py\", line 331, in run\n",
+        "    testMethod()\n",
+        "  File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
+        "    self.test(*self.arg)\n",
+        "  File \"&lt;<a href=\"#ipython-input-12-2d797333ff4c\">ipython-input-12-2d797333ff4c</a><script>\n",
+        "            $(\"div.prompt.input_prompt:contains([12])\")\n",
+        "                .attr(\"id\", \"ipython-input-12-2d797333ff4c\");\n",
+        "            </script>&gt;\", line 5, in test_add_two_plus_two_equals_four\n",
+        "    assert 4 == add_two_plus_two(), \"2 + 2 didn't equal 4\"\n",
+        "AssertionError: 2 + 2 didn't equal 4\n",
+        "</pre>\n",
         "    </div>\n",
         "    "
        ],
        "output_type": "pyout",
-       "prompt_number": 13,
+       "prompt_number": 17,
        "text": [
-        "1/1 tests passed\n"
+        "1/2 tests passed; 1 failed\n",
+        "========\n",
+        "__main__.test_add_two_plus_two_equals_four\n",
+        "========\n",
+        "Traceback (most recent call last):\n",
+        "  File \"/Users/Jon/Applications/anaconda/lib/python2.7/unittest/case.py\", line 331, in run\n",
+        "    testMethod()\n",
+        "  File \"/Users/Jon/Applications/anaconda/lib/python2.7/site-packages/nose/case.py\", line 197, in runTest\n",
+        "    self.test(*self.arg)\n",
+        "  File \"<ipython-input-12-2d797333ff4c>\", line 5, in test_add_two_plus_two_equals_four\n",
+        "    assert 4 == add_two_plus_two(), \"2 + 2 didn't equal 4\"\n",
+        "AssertionError: 2 + 2 didn't equal 4\n",
+        "\n"
        ]
       }
      ],
-     "prompt_number": 13
+     "prompt_number": 17
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "Using `nose` allows you to run all of your tests with one command. It eliminates the need to remember to add tests to a dedicated runner script, and it will clearly highlight which test has failed and point you to a traceback."
+     ]
+    },
+    {
+     "cell_type": "markdown",
+     "metadata": {},
+     "source": [
+      "**Note:** `nose` works by adhering to some reasonably sane naming conventions. If you prefix the names of all files containing tests with `test_`, and prefix the names of all functions containing tests with `test_`, `nose` will generally find your tests. A good rule of thumb is to place tests for a module named `my_module.py` in a file named `test_my_module.py`, and to name tests using plain English-y descriptions of what the test verifies, such as `test_that_two_plus_two_equals_four`."
+     ]
     },
     {
      "cell_type": "code",