{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Your first test\n",
    "\n",
    "Welcome to your first tutorial on unit testing with Python's built-in `unittest` module! In this tutorial, you'll write your very first test and see it run successfully.\n",
    "\n",
    "**Time commitment**: 15–20 minutes\n",
    "\n",
    "## Before you start\n",
    "\n",
    "This tutorial assumes you:\n",
    "- Can run Python code in Jupyter notebooks or Google Colab\n",
    "- Understand basic Python syntax (variables, functions, return statements)\n",
    "- Have written at least a few simple Python functions before\n",
    "\n",
    "**If you're completely new to Python**, we recommend completing a basic Python introduction first.\n",
    "\n",
    "**If some concepts seem unfamiliar**, don't worry - you'll pick them up as we go. Focus on following the patterns shown in the examples.\n",
    "\n",
    "## Learning objectives\n",
    "\n",
    "By the end of this tutorial, you will be able to:\n",
    "\n",
    "- Understand what unit testing is and why it matters\n",
    "- Write a simple function and a test for it\n",
    "- Use the `assertEqual()` assertion\n",
    "- Run tests using Python's unittest framework\n",
    "- Interpret test output (successes and failures)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## What is unit testing?\n",
    "\n",
    "Unit testing is a software development practice where you write small, automated tests to verify that individual \"units\" of code (typically functions or methods) work correctly. Think of it as a safety net that catches bugs before they reach production.\n",
    "\n",
    "### Why should you test?\n",
    "\n",
    "- **Confidence**: Know your code works as expected\n",
    "- **Documentation**: Tests describe how your code should behave\n",
    "- **Safety**: Change code without breaking existing functionality\n",
    "- **Early bug detection**: Catch problems before users do\n",
    "\n",
    "### Unit testing in professional software development\n",
    "\n",
    "In professional settings, tests help you:\n",
    "- Verify your code works correctly before deployment\n",
    "- Catch bugs early when they're cheap to fix\n",
    "- Make changes confidently without breaking existing functionality\n",
    "- Document how your code should behave for other developers\n",
    "\n",
    "In 1996, the Ariane 5 rocket exploded 37 seconds after launch due to a [software error](https://doi.org/10.1145/251880.251992) that could have been caught by tests. The project cost £6 billion.\n",
    "\n",
    "Good tests are like insurance - you hope you never need them, but you're grateful when you do.\n",
    "\n",
    "Now let's write our first test!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 1: Writing a simple function\n",
    "\n",
    "Before we can test anything, we need some code to test. Let's start with a simple function that adds two numbers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(a, b):\n",
    "    \"\"\"Add two numbers and return the result.\"\"\"\n",
    "    return a + b"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's try it out to make sure it works:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Try the function\n",
    "result = add(2, 3)\n",
    "print(f\"2 + 3 = {result}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Great! But what if we change the function later? How do we know we didn't break it? This is where tests come in."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 2: Writing your first test\n",
    "\n",
    "To write a test with `unittest`, we need to:\n",
    "\n",
    "1. Import the `unittest` module\n",
    "2. Create a test class that inherits from `unittest.TestCase`\n",
    "3. Write test methods (their names must start with `test_`)\n",
    "4. Use assertions to check expected behaviour\n",
    "\n",
    "Let's create a test for our `add()` function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import unittest\n",
    "\n",
    "class TestAddFunction(unittest.TestCase):\n",
    "    \"\"\"Tests for the add() function.\"\"\"\n",
    "    \n",
    "    def test_add_positive_numbers(self):\n",
    "        \"\"\"Test that adding two positive numbers works correctly.\"\"\"\n",
    "        result = add(2, 3)\n",
    "        self.assertEqual(result, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Understanding the test structure\n",
    "\n",
    "**Don't worry if this looks complex!** You don't need to fully understand Python classes yet to write tests. For now, treat this as a **recipe** you follow:\n",
    "\n",
    "1. `import unittest` - brings in the testing tools\n",
    "2. `class TestAddFunction(unittest.TestCase):` - creates a container for your tests\n",
    "3. `def test_add_positive_numbers(self):` - defines a test (must start with `test_`)\n",
    "4. `self.assertEqual(...)` - checks if something is correct\n",
    "\n",
    "Think of `self` as a way to say \"use the testing tools\".\n",
    "\n",
    "**Want to understand what `self`, classes, and `TestCase` really mean?** See [Understanding Test Structure](https://agilearn.co.uk/guides/unit-testing/concepts/understanding-test-structure) for a deeper explanation. For now, focus on the **pattern**."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 3: Running the test\n",
    "\n",
    "### Running tests in Jupyter notebooks\n",
    "\n",
    "In Jupyter, we need special arguments to run unittest:\n",
    "\n",
    "```python\n",
    "unittest.main(argv=[''], verbosity=2, exit=False)\n",
    "```\n",
    "\n",
    "**What do these arguments mean?**\n",
    "- `argv=['']`: Prevents Jupyter from interfering with command-line arguments\n",
    "- `verbosity=2`: Shows detailed test output\n",
    "- `exit=False`: Prevents the notebook from shutting down after tests run\n",
    "\n",
    "**For more details** on running tests in Jupyter, including troubleshooting tips, see [How to Run Tests in Jupyter](https://agilearn.co.uk/guides/unit-testing/recipes/run-tests-in-jupyter).\n",
    "\n",
    "Let's run our test:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run the test (in Jupyter, we need these special arguments)\n",
    "unittest.main(argv=[''], verbosity=2, exit=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Understanding the output\n",
    "\n",
    "When you run the test, you'll see output like:\n",
    "\n",
    "```\n",
    "test_add_positive_numbers (__main__.TestAddFunction)\n",
    "Test that adding two positive numbers works correctly. ... ok\n",
    "\n",
    "----------------------------------------------------------------------\n",
    "Ran 1 test in 0.001s\n",
    "\n",
    "OK\n",
    "```\n",
    "\n",
    "- The test name and docstring are displayed\n",
    "- \"ok\" means the test passed\n",
    "- We can see how many tests ran and how long they took\n",
    "- \"OK\" at the end means all tests passed"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Step 4: What happens when a test fails?\n",
    "\n",
    "Let's deliberately write a failing test to see what happens. We'll test a buggy function:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def buggy_add(a, b):\n",
    "    \"\"\"A buggy version of add that always adds one extra.\"\"\"\n",
    "    return a + b + 1  # Bug: adds an extra 1!\n",
    "\n",
    "class TestBuggyAdd(unittest.TestCase):\n",
    "    \"\"\"This test will fail!\"\"\"\n",
    "    \n",
    "    def test_buggy_add(self):\n",
    "        \"\"\"This test demonstrates a failure.\"\"\"\n",
    "        result = buggy_add(2, 3)\n",
    "        self.assertEqual(result, 5)  # We expect 5, but we'll get 6!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run the failing test\n",
    "unittest.main(argv=[''], verbosity=2, exit=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Understanding failure output\n",
    "\n",
    "When a test fails, you'll see:\n",
    "\n",
    "```\n",
    "FAIL: test_buggy_add (__main__.TestBuggyAdd)\n",
    "This test demonstrates a failure.\n",
    "----------------------------------------------------------------------\n",
    "Traceback (most recent call last):\n",
    "  ...\n",
    "AssertionError: 6 != 5\n",
    "```\n",
    "\n",
    "- \"FAIL\" indicates a failed assertion\n",
    "- The traceback shows where the failure occurred\n",
    "- `AssertionError: 6 != 5` tells you what went wrong\n",
    "\n",
    "This is incredibly useful for debugging!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quick reference\n",
    "\n",
    "Here are the essentials to remember:\n",
    "\n",
    "### Basic test structure\n",
    "```python\n",
    "import unittest\n",
    "\n",
    "class TestMyFunction(unittest.TestCase):\n",
    "    def test_specific_scenario(self):\n",
    "        \"\"\"Clear description of what this test checks.\"\"\"\n",
    "        result = my_function(input_value)\n",
    "        self.assertEqual(result, expected_output)\n",
    "\n",
    "# For Jupyter notebooks\n",
    "unittest.main(argv=[''], verbosity=2, exit=False)\n",
    "```\n",
    "\n",
    "### Golden rules\n",
    "1. **Test names must start with `test_`**\n",
    "2. **Use `self.` before assertions**\n",
    "3. **One test, one behaviour**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Key takeaways\n",
    "\n",
    "Congratulations! You've written and run your first test. Let's recap:\n",
    "\n",
    "1. **Tests are classes** that inherit from `unittest.TestCase`\n",
    "2. **Test methods** must start with `test_` so unittest can find them\n",
    "3. **Assertions** (like `assertEqual()`) check expected behaviour\n",
    "4. **Tests can pass or fail** - failures help you find bugs\n",
    "5. **Use `unittest.main(argv=[''], verbosity=2, exit=False)`** to run tests in Jupyter\n",
    "\n",
    "## Next steps\n",
    "\n",
    "Now that you've written your first test, continue to the next tutorial:\n",
    "\n",
    "- **[Testing Thoroughly](https://agilearn.co.uk/guides/unit-testing/learn/02-testing-thoroughly)** - Learn to test multiple scenarios, understand edge cases, and build comprehensive test suites\n",
    "\n",
    "### Supporting resources\n",
    "- [Understanding Test Structure](https://agilearn.co.uk/guides/unit-testing/concepts/understanding-test-structure) - Deep dive into classes, `self`, and `TestCase`\n",
    "- [How to Run Tests in Jupyter](https://agilearn.co.uk/guides/unit-testing/recipes/run-tests-in-jupyter) - Complete guide with troubleshooting\n",
    "\n",
    "**Remember**: Testing is a skill that improves with practice. Every test you write makes you a better developer.\n",
    "\n",
    "Happy testing!"
   ]
  }
 ],
 "metadata": {
  "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.12.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}