|
2659 | 2659 | "By using mocks in these ways, we can create more reliable and controlled unit tests for our data projects, ensuring that our code behaves correctly under various conditions."
|
2660 | 2660 | ]
|
2661 | 2661 | },
|
| 2662 | + { |
| 2663 | + "cell_type": "markdown", |
| 2664 | + "id": "3e516385", |
| 2665 | + "metadata": {}, |
| 2666 | + "source": [ |
| 2667 | + "### pytest-mock vs unittest.mock: Simplifying Mocking in Python Tests" |
| 2668 | + ] |
| 2669 | + }, |
| 2670 | + { |
| 2671 | + "cell_type": "code", |
| 2672 | + "execution_count": null, |
| 2673 | + "id": "d6041e6a", |
| 2674 | + "metadata": { |
| 2675 | + "tags": [ |
| 2676 | + "hide-cell" |
| 2677 | + ] |
| 2678 | + }, |
| 2679 | + "outputs": [], |
| 2680 | + "source": [ |
| 2681 | + "!pip install pytest-mock" |
| 2682 | + ] |
| 2683 | + }, |
| 2684 | + { |
| 2685 | + "cell_type": "markdown", |
| 2686 | + "id": "350f5aa4", |
| 2687 | + "metadata": {}, |
| 2688 | + "source": [ |
| 2689 | + "Traditional mocking with unittest.mock often requires repetitive setup and teardown code, which can make test code harder to read and maintain. \n", |
| 2690 | + "\n", |
| 2691 | + "pytest-mock addresses this issue by leveraging pytest's fixture system, simplifying the mocking process and reducing boilerplate code.\n", |
| 2692 | + "\n", |
| 2693 | + "Consider the following example that demonstrates the difference between unittest.mock and pytest-mock.\n", |
| 2694 | + "\n", |
| 2695 | + "Using unittest.mock:" |
| 2696 | + ] |
| 2697 | + }, |
| 2698 | + { |
| 2699 | + "cell_type": "code", |
| 2700 | + "execution_count": 5, |
| 2701 | + "id": "ed814822", |
| 2702 | + "metadata": {}, |
| 2703 | + "outputs": [ |
| 2704 | + { |
| 2705 | + "name": "stdout", |
| 2706 | + "output_type": "stream", |
| 2707 | + "text": [ |
| 2708 | + "Overwriting test_rm_file.py\n" |
| 2709 | + ] |
| 2710 | + } |
| 2711 | + ], |
| 2712 | + "source": [ |
| 2713 | + "%%writefile test_rm_file.py\n", |
| 2714 | + "from unittest.mock import patch\n", |
| 2715 | + "import os\n", |
| 2716 | + "\n", |
| 2717 | + "\n", |
| 2718 | + "def rm_file(filename):\n", |
| 2719 | + " os.remove(filename)\n", |
| 2720 | + "\n", |
| 2721 | + "\n", |
| 2722 | + "def test_with_unittest_mock():\n", |
| 2723 | + " with patch(\"os.remove\") as mock_remove:\n", |
| 2724 | + " rm_file(\"file\")\n", |
| 2725 | + " mock_remove.assert_called_once_with(\"file\")" |
| 2726 | + ] |
| 2727 | + }, |
| 2728 | + { |
| 2729 | + "cell_type": "code", |
| 2730 | + "execution_count": 4, |
| 2731 | + "id": "2e9d8b1d", |
| 2732 | + "metadata": {}, |
| 2733 | + "outputs": [ |
| 2734 | + { |
| 2735 | + "name": "stdout", |
| 2736 | + "output_type": "stream", |
| 2737 | + "text": [ |
| 2738 | + "\u001b[1m============================= test session starts ==============================\u001b[0m\n", |
| 2739 | + "platform darwin -- Python 3.11.2, pytest-7.4.3, pluggy-1.3.0\n", |
| 2740 | + "rootdir: /Users/khuyentran/book/Efficient_Python_tricks_and_tools_for_data_scientists/Chapter5\n", |
| 2741 | + "plugins: dvc-3.28.0, hydra-core-1.3.2, typeguard-4.1.5, mock-3.14.0, anyio-4.2.0, hypothesis-6.88.4\n", |
| 2742 | + "collected 1 item \u001b[0m\n", |
| 2743 | + "\n", |
| 2744 | + "test_rm_file.py \u001b[32m.\u001b[0m\u001b[32m [100%]\u001b[0m\n", |
| 2745 | + "\n", |
| 2746 | + "\u001b[32m============================== \u001b[32m\u001b[1m1 passed\u001b[0m\u001b[32m in 0.01s\u001b[0m\u001b[32m ===============================\u001b[0m\n", |
| 2747 | + "\u001b[0m" |
| 2748 | + ] |
| 2749 | + } |
| 2750 | + ], |
| 2751 | + "source": [ |
| 2752 | + "!pytest test_rm_file.py" |
| 2753 | + ] |
| 2754 | + }, |
| 2755 | + { |
| 2756 | + "cell_type": "markdown", |
| 2757 | + "id": "0607964b", |
| 2758 | + "metadata": {}, |
| 2759 | + "source": [ |
| 2760 | + "Using pytest-mock:" |
| 2761 | + ] |
| 2762 | + }, |
| 2763 | + { |
| 2764 | + "cell_type": "code", |
| 2765 | + "execution_count": 3, |
| 2766 | + "id": "2e772780", |
| 2767 | + "metadata": {}, |
| 2768 | + "outputs": [ |
| 2769 | + { |
| 2770 | + "name": "stdout", |
| 2771 | + "output_type": "stream", |
| 2772 | + "text": [ |
| 2773 | + "Writing test_rm_file.py\n" |
| 2774 | + ] |
| 2775 | + } |
| 2776 | + ], |
| 2777 | + "source": [ |
| 2778 | + "%%writefile test_rm_file.py\n", |
| 2779 | + "import os\n", |
| 2780 | + "\n", |
| 2781 | + "\n", |
| 2782 | + "def rm_file(filename):\n", |
| 2783 | + " os.remove(filename)\n", |
| 2784 | + "\n", |
| 2785 | + "\n", |
| 2786 | + "def test_unix_fs(mocker):\n", |
| 2787 | + " mocker.patch(\"os.remove\")\n", |
| 2788 | + " rm_file(\"file\")\n", |
| 2789 | + " os.remove.assert_called_once_with(\"file\")" |
| 2790 | + ] |
| 2791 | + }, |
| 2792 | + { |
| 2793 | + "cell_type": "code", |
| 2794 | + "execution_count": 8, |
| 2795 | + "id": "f74a6fca", |
| 2796 | + "metadata": {}, |
| 2797 | + "outputs": [ |
| 2798 | + { |
| 2799 | + "name": "stdout", |
| 2800 | + "output_type": "stream", |
| 2801 | + "text": [ |
| 2802 | + "\u001b[1m============================= test session starts ==============================\u001b[0m\n", |
| 2803 | + "platform darwin -- Python 3.11.2, pytest-7.4.3, pluggy-1.3.0\n", |
| 2804 | + "rootdir: /Users/khuyentran/book/Efficient_Python_tricks_and_tools_for_data_scientists/Chapter5\n", |
| 2805 | + "plugins: dvc-3.28.0, hydra-core-1.3.2, typeguard-4.1.5, mock-3.14.0, anyio-4.2.0, hypothesis-6.88.4\n", |
| 2806 | + "collected 1 item \u001b[0m\n", |
| 2807 | + "\n", |
| 2808 | + "test_rm_file.py \u001b[32m.\u001b[0m\u001b[32m [100%]\u001b[0m\n", |
| 2809 | + "\n", |
| 2810 | + "\u001b[32m============================== \u001b[32m\u001b[1m1 passed\u001b[0m\u001b[32m in 0.01s\u001b[0m\u001b[32m ===============================\u001b[0m\n", |
| 2811 | + "\u001b[0m" |
| 2812 | + ] |
| 2813 | + } |
| 2814 | + ], |
| 2815 | + "source": [ |
| 2816 | + "!pytest test_rm_file.py" |
| 2817 | + ] |
| 2818 | + }, |
| 2819 | + { |
| 2820 | + "cell_type": "markdown", |
| 2821 | + "id": "4f873409", |
| 2822 | + "metadata": {}, |
| 2823 | + "source": [ |
| 2824 | + "Key differences:\n", |
| 2825 | + "\n", |
| 2826 | + "1. Setup: pytest-mock uses the `mocker` fixture, automatically provided by pytest, eliminating the need to import patching utilities.\n", |
| 2827 | + "\n", |
| 2828 | + "2. Patching: With pytest-mock, you simply call `mocker.patch('os.remove')`, whereas unittest.mock requires a context manager or decorator.\n", |
| 2829 | + "\n", |
| 2830 | + "3. Cleanup: pytest-mock automatically undoes mocking after the test, while unittest.mock relies on the context manager for cleanup.\n", |
| 2831 | + "\n", |
| 2832 | + "4. Accessing mocks: pytest-mock allows direct access to the patched function (e.g., `os.remove.assert_called_once_with()`), while unittest.mock requires accessing the mock through a variable (e.g., `mock_remove.assert_called_once_with()`)." |
| 2833 | + ] |
| 2834 | + }, |
| 2835 | + { |
| 2836 | + "cell_type": "markdown", |
| 2837 | + "id": "2ca3e0b7", |
| 2838 | + "metadata": {}, |
| 2839 | + "source": [ |
| 2840 | + "[Link to pytest-mock](https://bit.ly/4dBDAOE)." |
| 2841 | + ] |
| 2842 | + }, |
2662 | 2843 | {
|
2663 | 2844 | "attachments": {},
|
2664 | 2845 | "cell_type": "markdown",
|
|
4728 | 4909 | "name": "python",
|
4729 | 4910 | "nbconvert_exporter": "python",
|
4730 | 4911 | "pygments_lexer": "ipython3",
|
4731 |
| - "version": "3.11.6" |
| 4912 | + "version": "3.11.2" |
4732 | 4913 | },
|
4733 | 4914 | "toc": {
|
4734 | 4915 | "base_numbering": 1,
|
|
0 commit comments