{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Testing best practices\n",
    "\n",
    "Welcome to the final tutorial in the Getting Started series! You've learned to write tests and test thoroughly. Now you'll learn best practices, common mistakes to avoid, and develop good testing habits.\n",
    "\n",
    "**Time commitment**: 15–20 minutes\n",
    "\n",
    "**Prerequisites**: Complete [Your First Test](https://agilearn.co.uk/guides/unit-testing/learn/01-your-first-test) and [Testing Thoroughly](https://agilearn.co.uk/guides/unit-testing/learn/02-testing-thoroughly) before starting this tutorial.\n",
    "\n",
    "## Learning objectives\n",
    "\n",
    "By the end of this tutorial, you will be able to:\n",
    "\n",
    "- Use a variety of common unittest assertions\n",
    "- Recognise and avoid common testing mistakes\n",
    "- Follow test naming conventions\n",
    "- Write clear, maintainable tests\n",
    "- Apply what you've learned in a practical exercise"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Common assertions\n",
    "\n",
    "You've already seen `assertEqual()`, `assertAlmostEqual()`, and `assertIsNone()`. Let's explore more useful assertions:\n",
    "\n",
    "| Assertion | Checks | Example |\n",
    "|-----------|--------|----------|\n",
    "| `assertEqual(a, b)` | `a == b` | `self.assertEqual(add(2, 3), 5)` |\n",
    "| `assertNotEqual(a, b)` | `a != b` | `self.assertNotEqual(add(2, 3), 6)` |\n",
    "| `assertTrue(x)` | `bool(x) is True` | `self.assertTrue(result > 0)` |\n",
    "| `assertFalse(x)` | `bool(x) is False` | `self.assertFalse(result < 0)` |\n",
    "| `assertIn(a, b)` | `a in b` | `self.assertIn(3, [1, 2, 3])` |\n",
    "| `assertIsNone(x)` | `x is None` | `self.assertIsNone(error_result)` |\n",
    "| `assertIsInstance(a, b)` | `isinstance(a, b)` | `self.assertIsInstance(result, int)` |\n",
    "| `assertAlmostEqual(a, b)` | `round(a-b, 7) == 0` | `self.assertAlmostEqual(0.1 + 0.2, 0.3)` |\n",
    "\n",
    "Let's see a few more in action:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import unittest\n",
    "\n",
    "def add(a, b):\n",
    "    \"\"\"Add two numbers and return the result.\"\"\"\n",
    "    return a + b\n",
    "\n",
    "class TestCommonAssertions(unittest.TestCase):\n",
    "    \"\"\"Demonstrates common assertion methods.\"\"\"\n",
    "    \n",
    "    def test_assertTrue_example(self):\n",
    "        \"\"\"Test that a condition is true.\"\"\"\n",
    "        result = add(2, 3)\n",
    "        self.assertTrue(result > 0)\n",
    "    \n",
    "    def test_assertIn_example(self):\n",
    "        \"\"\"Test that an item is in a collection.\"\"\"\n",
    "        numbers = [1, 2, 3, 4, 5]\n",
    "        self.assertIn(3, numbers)\n",
    "    \n",
    "    def test_assertIsInstance_example(self):\n",
    "        \"\"\"Test the type of an object.\"\"\"\n",
    "        result = add(2, 3)\n",
    "        self.assertIsInstance(result, int)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run the assertion examples\n",
    "unittest.main(argv=[''], verbosity=2, exit=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Choosing the right assertion\n",
    "\n",
    "Use the most specific assertion available:\n",
    "\n",
    "- ✅ `self.assertIsNone(result)` - clear intent\n",
    "- ❌ `self.assertEqual(result, None)` - less clear\n",
    "- ❌ `self.assertTrue(result is None)` - least clear\n",
    "\n",
    "More specific assertions provide better error messages when tests fail!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Common mistakes to avoid\n",
    "\n",
    "Watch out for these frequent errors as you start testing:\n",
    "\n",
    "### 1. Forgetting the `test_` prefix\n",
    "```python\n",
    "def check_addition(self):  # ❌ unittest won't find this\n",
    "def test_addition(self):   # ✅ Will run automatically\n",
    "```\n",
    "**Why it matters**: unittest only runs methods starting with `test_`. Without it, your test exists but never executes.\n",
    "\n",
    "### 2. Forgetting `self` before assertions\n",
    "```python\n",
    "assertEqual(result, 5)       # ❌ NameError: assertEqual is not defined\n",
    "self.assertEqual(result, 5)  # ✅ Correct\n",
    "```\n",
    "**Why it matters**: Assertions are methods of the TestCase class, so you need `self.` to access them.\n",
    "\n",
    "### 3. Comparing floats with `assertEqual`\n",
    "```python\n",
    "# ❌ May fail due to floating-point precision\n",
    "self.assertEqual(0.1 + 0.2, 0.3)  # Actually gives 0.30000000000000004\n",
    "\n",
    "# ✅ Use assertAlmostEqual for floats\n",
    "self.assertAlmostEqual(0.1 + 0.2, 0.3, places=7)\n",
    "```\n",
    "**Why it matters**: Computers store decimal numbers imperfectly. Use `assertAlmostEqual` when working with floats."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Let's see these mistakes in action\n",
    "\n",
    "Here's code demonstrating these mistakes (intentionally broken):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class TestCommonMistakes(unittest.TestCase):\n",
    "    \"\"\"Demonstrates common mistakes.\"\"\"\n",
    "    \n",
    "    # Mistake 1: Missing 'test_' prefix - this won't run!\n",
    "    def check_addition(self):\n",
    "        \"\"\"This test will be silently ignored.\"\"\"\n",
    "        self.assertEqual(add(2, 3), 5)\n",
    "    \n",
    "    def test_addition_correct(self):\n",
    "        \"\"\"This test WILL run because it starts with 'test_'.\"\"\"\n",
    "        self.assertEqual(add(2, 3), 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run the tests - notice only one test runs!\n",
    "unittest.main(argv=[''], verbosity=2, exit=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice only `test_addition_correct` ran! The `check_addition` method was silently ignored.\n",
    "\n",
    "**For a complete list of common testing mistakes and how to fix them**, see [Avoid Common Testing Mistakes](https://agilearn.co.uk/guides/unit-testing/recipes/avoid-common-testing-mistakes)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test naming best practices\n",
    "\n",
    "Good test names clearly describe what they're testing. Follow this pattern:\n",
    "\n",
    "**Structure**: `test_[function]_[scenario]_[expected_outcome]`\n",
    "\n",
    "**Examples:**\n",
    "- ✅ `test_add_positive_numbers` - clear and specific\n",
    "- ✅ `test_divide_by_zero_raises_error` - describes the behaviour\n",
    "- ✅ `test_calculate_discount_with_invalid_input_returns_none` - very explicit\n",
    "- ❌ `test1` - no information about what's being tested\n",
    "- ❌ `test_function` - too vague\n",
    "\n",
    "### Why good names matter\n",
    "\n",
    "When a test fails, you'll see:\n",
    "```\n",
    "FAIL: test_calculate_discount_with_invalid_input_returns_none\n",
    "```\n",
    "\n",
    "vs.\n",
    "\n",
    "```\n",
    "FAIL: test1\n",
    "```\n",
    "\n",
    "Which one tells you what went wrong?\n",
    "\n",
    "**For comprehensive naming conventions and patterns**, see [Test Naming Conventions](https://agilearn.co.uk/guides/unit-testing/reference/test-naming-conventions)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Writing clear tests\n",
    "\n",
    "Follow these principles for maintainable tests:\n",
    "\n",
    "### 1. One test, one behaviour\n",
    "\n",
    "Each test should check one specific behaviour:\n",
    "\n",
    "```python\n",
    "# ❌ Testing multiple behaviours\n",
    "def test_calculator(self):\n",
    "    self.assertEqual(add(2, 3), 5)\n",
    "    self.assertEqual(subtract(5, 3), 2)\n",
    "    self.assertEqual(multiply(2, 3), 6)\n",
    "\n",
    "# ✅ Separate tests for each behaviour\n",
    "def test_add_positive_numbers(self):\n",
    "    self.assertEqual(add(2, 3), 5)\n",
    "\n",
    "def test_subtract_positive_numbers(self):\n",
    "    self.assertEqual(subtract(5, 3), 2)\n",
    "```\n",
    "\n",
    "**Why?** When a test fails, you immediately know which behaviour broke.\n",
    "\n",
    "### 2. Use descriptive docstrings\n",
    "\n",
    "```python\n",
    "def test_calculate_discount_invalid_negative_price(self):\n",
    "    \"\"\"Test that negative price returns None.\"\"\"\n",
    "    self.assertIsNone(calculate_discount(-10, 20))\n",
    "```\n",
    "\n",
    "The docstring appears in test output, helping you understand what broke.\n",
    "\n",
    "### 3. Keep tests simple\n",
    "\n",
    "Tests should be easy to read and understand:\n",
    "\n",
    "```python\n",
    "# ✅ Clear and simple\n",
    "def test_add_positive_numbers(self):\n",
    "    result = add(2, 3)\n",
    "    self.assertEqual(result, 5)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exercise: Your turn!\n",
    "\n",
    "Now it's time to practise what you've learned. Here's a simple function that needs tests:\n",
    "\n",
    "```python\n",
    "def multiply(a, b):\n",
    "    \"\"\"Multiply two numbers and return the result.\"\"\"\n",
    "    return a * b\n",
    "```\n",
    "\n",
    "**Your task**: Complete the test class below with at least three test methods:\n",
    "1. Test multiplying positive numbers\n",
    "2. Test multiplying by zero\n",
    "3. Test multiplying negative numbers\n",
    "\n",
    "**Bonus challenges:**\n",
    "- Test the identity property (multiplying by 1)\n",
    "- Test with floating-point numbers\n",
    "\n",
    "Try it yourself in the cell below before looking at the solution!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def multiply(a, b):\n",
    "    \"\"\"Multiply two numbers and return the result.\"\"\"\n",
    "    return a * b\n",
    "\n",
    "# Write your test class here\n",
    "class TestMultiplyFunction(unittest.TestCase):\n",
    "    \"\"\"Your tests for the multiply() function.\"\"\"\n",
    "    \n",
    "    def test_multiply_positive_numbers(self):\n",
    "        \"\"\"Test multiplying two positive numbers.\"\"\"\n",
    "        # TODO: Test that multiply(3, 4) equals 12\n",
    "        # TODO: Test that multiply(7, 5) equals 35\n",
    "        pass\n",
    "    \n",
    "    def test_multiply_by_zero(self):\n",
    "        \"\"\"Test multiplying any number by zero.\"\"\"\n",
    "        # TODO: Test that multiply(5, 0) equals 0\n",
    "        # TODO: Test that multiply(0, 5) also equals 0\n",
    "        # TODO: What about multiply(0, 0)?\n",
    "        pass\n",
    "    \n",
    "    def test_multiply_negative_numbers(self):\n",
    "        \"\"\"Test multiplying negative numbers.\"\"\"\n",
    "        # TODO: What should multiply(-3, 4) equal?\n",
    "        # TODO: What about multiply(3, -4)?\n",
    "        # TODO: What about multiply(-3, -4)?\n",
    "        pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Solution\n",
    "\n",
    "Here's one possible solution (try yours first!):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class TestMultiplyFunctionSolution(unittest.TestCase):\n",
    "    \"\"\"Solution: Tests for the multiply() function.\"\"\"\n",
    "    \n",
    "    def test_multiply_positive_numbers(self):\n",
    "        \"\"\"Test multiplying two positive numbers.\"\"\"\n",
    "        self.assertEqual(multiply(3, 4), 12)\n",
    "        self.assertEqual(multiply(7, 5), 35)\n",
    "    \n",
    "    def test_multiply_by_zero(self):\n",
    "        \"\"\"Test multiplying any number by zero.\"\"\"\n",
    "        self.assertEqual(multiply(5, 0), 0)\n",
    "        self.assertEqual(multiply(0, 5), 0)\n",
    "        self.assertEqual(multiply(0, 0), 0)\n",
    "    \n",
    "    def test_multiply_negative_numbers(self):\n",
    "        \"\"\"Test multiplying negative numbers.\"\"\"\n",
    "        self.assertEqual(multiply(-3, 4), -12)\n",
    "        self.assertEqual(multiply(3, -4), -12)\n",
    "        self.assertEqual(multiply(-3, -4), 12)\n",
    "    \n",
    "    def test_multiply_by_one(self):\n",
    "        \"\"\"Bonus: Test multiplying by one (identity property).\"\"\"\n",
    "        self.assertEqual(multiply(5, 1), 5)\n",
    "        self.assertEqual(multiply(1, 5), 5)\n",
    "    \n",
    "    def test_multiply_floats(self):\n",
    "        \"\"\"Bonus: Test multiplying floating-point numbers.\"\"\"\n",
    "        self.assertAlmostEqual(multiply(2.5, 4.0), 10.0, places=1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Run the solution tests\n",
    "unittest.main(argv=[''], verbosity=2, exit=False)"
   ]
  },
  {
   "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",
    "### Most common assertions\n",
    "- `assertEqual(a, b)` - checks a == b\n",
    "- `assertTrue(x)` - checks bool(x) is True\n",
    "- `assertIsNone(x)` - checks x is None\n",
    "- `assertAlmostEqual(a, b)` - checks a ≈ b (for floats)\n",
    "- `assertIn(a, b)` - checks a in b\n",
    "\n",
    "### Golden rules\n",
    "1. **Test names must start with `test_`**\n",
    "2. **Use `self.` before assertions**\n",
    "3. **One test, one behaviour**\n",
    "4. **Use descriptive names and docstrings**\n",
    "5. **Keep tests simple and readable**\n",
    "\n",
    "**For a complete quick reference guide**, including all assertions, test fixtures, and patterns, see [unittest Quick Reference](https://agilearn.co.uk/guides/unit-testing/reference/unittest-quick-reference)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Key takeaways\n",
    "\n",
    "Congratulations! You've completed the Getting Started series. Let's recap everything you've learned:\n",
    "\n",
    "### From Tutorial 1: Your first test\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",
    "\n",
    "### From Tutorial 2: Testing thoroughly\n",
    "4. **Test multiple scenarios** - normal cases, edge cases, and error conditions\n",
    "5. **Use appropriate assertions** for different types of checks\n",
    "\n",
    "### From this tutorial\n",
    "6. **Follow naming conventions** for clear, maintainable tests\n",
    "7. **Avoid common mistakes** like forgetting `test_` or `self.`\n",
    "8. **One test should check one behaviour** for easier debugging\n",
    "9. **Use specific assertions** for better error messages\n",
    "10. **Keep tests simple and readable**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Next steps\n",
    "\n",
    "Now that you understand the fundamentals, continue building your testing skills:\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",
    "- [Avoid Common Testing Mistakes](https://agilearn.co.uk/guides/unit-testing/recipes/avoid-common-testing-mistakes) - 10 common mistakes and how to fix them\n",
    "- [Test Naming Conventions](https://agilearn.co.uk/guides/unit-testing/reference/test-naming-conventions) - Comprehensive naming patterns\n",
    "- [unittest Quick Reference](https://agilearn.co.uk/guides/unit-testing/reference/unittest-quick-reference) - Complete assertion guide\n",
    "\n",
    "### Practice and learn more\n",
    "1. **Test your own code**: Take any Python function you've written and create 3-5 tests for it\n",
    "2. **Explore the unittest documentation**: [Python unittest documentation](https://docs.python.org/3/library/unittest.html)\n",
    "3. **Continue with more tutorials**: Coming soon - test fixtures, test organisation, and mocking\n",
    "\n",
    "### Challenge yourself\n",
    "\n",
    "Try writing tests for these common programming tasks:\n",
    "- A function that checks if a string is a palindrome\n",
    "- A function that finds the maximum value in a list\n",
    "- A function that validates an email address\n",
    "- A function that calculates the factorial of a number\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
}