# 64. Python's unittest Framework

**Python's `unittest` Framework**

The `unittest` framework is a built-in module in Python used to create unit tests to verify the correctness of your code. Unit tests are an essential part of the development process as they ensure that individual components of your code work as expected. Below are key features and example snippets for using the `unittest` module.

***

**1. Basic Structure of a Unit Test**

A unit test typically consists of a test class that inherits from `unittest.TestCase`, and test methods that verify the functionality of a specific piece of code.

```python
import unittest

class MyTestCase(unittest.TestCase):
    def test_addition(self):
        result = 1 + 1
        self.assertEqual(result, 2)  # Verify the result is 2

if __name__ == '__main__':
    unittest.main()
```

Here, `assertEqual` is used to check if the result of `1 + 1` is equal to `2`.

***

**2. Common Assertions**

* **`assertEqual(a, b)`**: Check if `a` and `b` are equal.
* **`assertNotEqual(a, b)`**: Check if `a` and `b` are not equal.
* **`assertTrue(x)`**: Check if `x` is `True`.
* **`assertFalse(x)`**: Check if `x` is `False`.
* **`assertIsNone(x)`**: Check if `x` is `None`.
* **`assertIsNotNone(x)`**: Check if `x` is not `None`.
* **`assertIn(a, b)`**: Check if `a` is in `b`.
* **`assertNotIn(a, b)`**: Check if `a` is not in `b`.

```python
class TestAssertions(unittest.TestCase):
    def test_assertions(self):
        self.assertEqual(2 + 2, 4)
        self.assertNotEqual(2 + 2, 5)
        self.assertTrue(1 == 1)
        self.assertFalse(1 == 0)
        self.assertIsNone(None)
        self.assertIsNotNone(1)
        self.assertIn(2, [1, 2, 3])
        self.assertNotIn(4, [1, 2, 3])

if __name__ == '__main__':
    unittest.main()
```

***

**3. Test Setup and Teardown**

Sometimes, you need to set up preconditions or clean up after tests. You can use `setUp()` and `tearDown()` methods for this purpose.

* **`setUp()`**: Called before every test method.
* **`tearDown()`**: Called after every test method.

```python
class MyTestCase(unittest.TestCase):
    def setUp(self):
        self.test_data = [1, 2, 3]
        print("Setting up data.")

    def tearDown(self):
        print("Tearing down data.")
        self.test_data = None

    def test_addition(self):
        result = sum(self.test_data)
        self.assertEqual(result, 6)

if __name__ == '__main__':
    unittest.main()
```

***

**4. Skipping Tests**

You can skip tests conditionally using decorators like `@unittest.skip()` or `@unittest.skipIf()`.

* **`@unittest.skip(reason)`**: Skip a test unconditionally.
* **`@unittest.skipIf(condition, reason)`**: Skip a test if the condition is true.
* **`@unittest.skipUnless(condition, reason)`**: Skip a test unless the condition is true.

```python
class MyTestCase(unittest.TestCase):
    @unittest.skip("Skipping this test unconditionally")
    def test_skipped(self):
        self.fail("This test is skipped.")

    @unittest.skipIf(True, "Condition met, skipping")
    def test_skip_if(self):
        self.fail("This test is skipped.")

if __name__ == '__main__':
    unittest.main()
```

***

**5. Test Fixtures**

Test fixtures provide a way to organize setup and teardown code for multiple tests, using `setUp()` and `tearDown()`, or class-level setup and teardown with `setUpClass()` and `tearDownClass()`.

* **`setUpClass()`**: Called once before all tests in the class.
* **`tearDownClass()`**: Called once after all tests in the class.

```python
class MyTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print("Setting up class-level fixtures.")

    @classmethod
    def tearDownClass(cls):
        print("Tearing down class-level fixtures.")

    def test_method1(self):
        print("Test method 1")

    def test_method2(self):
        print("Test method 2")

if __name__ == '__main__':
    unittest.main()
```

***

**6. Test Suites**

A test suite is a collection of test cases. You can create a suite using `unittest.TestLoader` to load test cases from a class or module and then run them together.

```python
def my_test_suite():
    suite = unittest.TestSuite()
    suite.addTest(MyTestCase('test_addition'))
    suite.addTest(MyTestCase('test_assertions'))
    return suite

if __name__ == '__main__':
    runner = unittest.TextTestRunner()
    runner.run(my_test_suite())
```

***

**7. Mocking with `unittest.mock`**

The `unittest.mock` module allows you to replace parts of your system under test with mock objects. This is useful for testing code that interacts with external systems, like databases or APIs.

```python
from unittest import mock

class MyTestCase(unittest.TestCase):
    @mock.patch('builtins.print')
    def test_mocking(self, mock_print):
        mock_print("Hello")
        mock_print.assert_called_with("Hello")

if __name__ == '__main__':
    unittest.main()
```

***

**8. Handling Expected Exceptions**

You can use the `assertRaises()` method to check that an exception is raised under certain conditions.

```python
class MyTestCase(unittest.TestCase):
    def test_divide_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            1 / 0

if __name__ == '__main__':
    unittest.main()
```

***

**9. Running Tests from Command Line**

You can run the tests from the command line by calling `python -m unittest`. This will discover and run all test cases in the module or directory.

```bash
python -m unittest my_test_module.py
```

***

**10. Organizing Tests into Modules**

It’s a good practice to organize tests into separate modules for different functionalities. You can organize tests by creating different test files and folders.

```
/tests
    /test_feature1.py
    /test_feature2.py
    /test_helpers.py
```

Each file can contain multiple test cases or test suites.

***

**Summary of Key Features:**

* **Assertions**: Verify correctness using assertions like `assertEqual()`, `assertTrue()`, `assertRaises()`, etc.
* **Setup and Teardown**: Use `setUp()`, `tearDown()`, `setUpClass()`, and `tearDownClass()` for preparation and cleanup.
* **Skipping Tests**: Skip tests using `@unittest.skip` and conditional decorators.
* **Mocking**: Use `unittest.mock` to simulate external systems.
* **Running Tests**: Run tests via `unittest.main()` or the command line.
