|
1151 | 1151 | {
|
1152 | 1152 | "cell_type": "code",
|
1153 | 1153 | "execution_count": 1,
|
| 1154 | + "id": "52c638a0", |
1154 | 1155 | "metadata": {},
|
1155 | 1156 | "outputs": [
|
1156 | 1157 | {
|
|
1305 | 1306 | },
|
1306 | 1307 | {
|
1307 | 1308 | "cell_type": "markdown",
|
| 1309 | + "id": "781ff6d9", |
1308 | 1310 | "metadata": {},
|
1309 | 1311 | "source": [
|
1310 | 1312 | "### Test for Specific Exceptions in Unit Testing"
|
1311 | 1313 | ]
|
1312 | 1314 | },
|
1313 | 1315 | {
|
1314 | 1316 | "cell_type": "markdown",
|
| 1317 | + "id": "8c087d03", |
1315 | 1318 | "metadata": {},
|
1316 | 1319 | "source": [
|
1317 | 1320 | "To test for a specific exception in unit testing, use `pytest.raises`.\n",
|
|
1322 | 1325 | {
|
1323 | 1326 | "cell_type": "code",
|
1324 | 1327 | "execution_count": 7,
|
| 1328 | + "id": "24775262", |
1325 | 1329 | "metadata": {},
|
1326 | 1330 | "outputs": [
|
1327 | 1331 | {
|
|
1353 | 1357 | },
|
1354 | 1358 | {
|
1355 | 1359 | "cell_type": "markdown",
|
| 1360 | + "id": "e2de71c1", |
1356 | 1361 | "metadata": {},
|
1357 | 1362 | "source": [
|
1358 | 1363 | "```bash\n",
|
|
1363 | 1368 | {
|
1364 | 1369 | "cell_type": "code",
|
1365 | 1370 | "execution_count": 8,
|
| 1371 | + "id": "2898168f", |
1366 | 1372 | "metadata": {
|
1367 | 1373 | "tags": [
|
1368 | 1374 | "remove-input"
|
|
2399 | 2405 | "\n",
|
2400 | 2406 | "A mock object can control the behavior of a real object in a testing environment by simulating responses from external services.\n",
|
2401 | 2407 | "\n",
|
2402 |
| - "The following code uses a mock object to test the `get_data` function's behavior when calling an API that may either succeed or fail." |
| 2408 | + "Here are two common use cases with examples:" |
2403 | 2409 | ]
|
2404 | 2410 | },
|
2405 | 2411 | {
|
2406 |
| - "attachments": {}, |
2407 | 2412 | "cell_type": "markdown",
|
2408 |
| - "id": "aa19ebb6", |
| 2413 | + "id": "fa134f51", |
2409 | 2414 | "metadata": {},
|
2410 | 2415 | "source": [
|
2411 |
| - "```python\n", |
| 2416 | + "1. **Mocking Time-Dependent Functions**\n", |
| 2417 | + "\n", |
| 2418 | + "When testing functions that depend on the current time or date, you can mock the time to ensure consistent results.\n", |
| 2419 | + "\n", |
| 2420 | + "Example: Testing a function that returns data for the last week" |
| 2421 | + ] |
| 2422 | + }, |
| 2423 | + { |
| 2424 | + "cell_type": "code", |
| 2425 | + "execution_count": 43, |
| 2426 | + "id": "25d20b5a", |
| 2427 | + "metadata": {}, |
| 2428 | + "outputs": [ |
| 2429 | + { |
| 2430 | + "name": "stdout", |
| 2431 | + "output_type": "stream", |
| 2432 | + "text": [ |
| 2433 | + "Overwriting main.py\n" |
| 2434 | + ] |
| 2435 | + } |
| 2436 | + ], |
| 2437 | + "source": [ |
| 2438 | + "%%writefile main.py\n", |
| 2439 | + "from datetime import datetime, timedelta\n", |
| 2440 | + "\n", |
| 2441 | + "\n", |
| 2442 | + "def get_data_for_last_week():\n", |
| 2443 | + " end_date = datetime.now().date()\n", |
| 2444 | + " start_date = end_date - timedelta(days=7)\n", |
| 2445 | + " return {\n", |
| 2446 | + " \"start_date\": start_date.strftime(\"%Y-%m-%d\"),\n", |
| 2447 | + " \"end_date\": end_date.strftime(\"%Y-%m-%d\"),\n", |
| 2448 | + " }" |
| 2449 | + ] |
| 2450 | + }, |
| 2451 | + { |
| 2452 | + "cell_type": "markdown", |
| 2453 | + "id": "e66a0f82", |
| 2454 | + "metadata": {}, |
| 2455 | + "source": [ |
| 2456 | + "Now, let's create a test for this function using mock:\n" |
| 2457 | + ] |
| 2458 | + }, |
| 2459 | + { |
| 2460 | + "cell_type": "code", |
| 2461 | + "execution_count": 52, |
| 2462 | + "id": "7754b84b", |
| 2463 | + "metadata": {}, |
| 2464 | + "outputs": [ |
| 2465 | + { |
| 2466 | + "name": "stdout", |
| 2467 | + "output_type": "stream", |
| 2468 | + "text": [ |
| 2469 | + "Overwriting test_main.py\n" |
| 2470 | + ] |
| 2471 | + } |
| 2472 | + ], |
| 2473 | + "source": [ |
| 2474 | + "%%writefile test_main.py\n", |
| 2475 | + "from datetime import datetime\n", |
2412 | 2476 | "from unittest.mock import patch\n",
|
| 2477 | + "from main import get_data_for_last_week\n", |
| 2478 | + "\n", |
| 2479 | + "\n", |
| 2480 | + "@patch(\"main.datetime\")\n", |
| 2481 | + "def test_get_data_for_last_week(mock_datetime):\n", |
| 2482 | + " # Set a fixed date for the test\n", |
| 2483 | + " mock_datetime.now.return_value = datetime(2024, 8, 5)\n", |
| 2484 | + "\n", |
| 2485 | + " # Call the function\n", |
| 2486 | + " result = get_data_for_last_week()\n", |
| 2487 | + "\n", |
| 2488 | + " # Assert the results\n", |
| 2489 | + " assert result[\"start_date\"] == \"2024-07-29\"\n", |
| 2490 | + " assert result[\"end_date\"] == \"2024-08-05\"\n", |
| 2491 | + "\n", |
| 2492 | + " # Verify that datetime.now() was called\n", |
| 2493 | + " mock_datetime.now.assert_called_once()" |
| 2494 | + ] |
| 2495 | + }, |
| 2496 | + { |
| 2497 | + "cell_type": "markdown", |
| 2498 | + "id": "8124b826", |
| 2499 | + "metadata": {}, |
| 2500 | + "source": [ |
| 2501 | + "This test mocks the `datetime.now()` method to return a fixed date, allowing for predictable and consistent test results." |
| 2502 | + ] |
| 2503 | + }, |
| 2504 | + { |
| 2505 | + "cell_type": "code", |
| 2506 | + "execution_count": 53, |
| 2507 | + "id": "a6c35293", |
| 2508 | + "metadata": { |
| 2509 | + "tags": [ |
| 2510 | + "hide-cell" |
| 2511 | + ] |
| 2512 | + }, |
| 2513 | + "outputs": [ |
| 2514 | + { |
| 2515 | + "name": "stdout", |
| 2516 | + "output_type": "stream", |
| 2517 | + "text": [ |
| 2518 | + "\u001b[1m============================= test session starts ==============================\u001b[0m\n", |
| 2519 | + "platform darwin -- Python 3.11.2, pytest-7.4.3, pluggy-1.3.0 -- /Users/khuyentran/.pyenv/versions/3.11.2/bin/python3\n", |
| 2520 | + "cachedir: .pytest_cache\n", |
| 2521 | + "hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase(PosixPath('/Users/khuyentran/book/Efficient_Python_tricks_and_tools_for_data_scientists/Chapter5/.hypothesis/examples'))\n", |
| 2522 | + "rootdir: /Users/khuyentran/book/Efficient_Python_tricks_and_tools_for_data_scientists/Chapter5\n", |
| 2523 | + "plugins: dvc-3.28.0, hydra-core-1.3.2, typeguard-4.1.5, anyio-4.2.0, hypothesis-6.88.4\n", |
| 2524 | + "collected 1 item \u001b[0m\n", |
| 2525 | + "\n", |
| 2526 | + "test_main.py::test_get_data_for_last_week \u001b[32mPASSED\u001b[0m\n", |
| 2527 | + "\n", |
| 2528 | + "\u001b[32m============================== \u001b[32m\u001b[1m1 passed\u001b[0m\u001b[32m in 0.09s\u001b[0m\u001b[32m ===============================\u001b[0m\n", |
| 2529 | + "\u001b[0m" |
| 2530 | + ] |
| 2531 | + } |
| 2532 | + ], |
| 2533 | + "source": [ |
| 2534 | + "!pytest -sv test_main.py" |
| 2535 | + ] |
| 2536 | + }, |
| 2537 | + { |
| 2538 | + "cell_type": "markdown", |
| 2539 | + "id": "161d68ef-2f73-43ab-98cf-8268bab793a0", |
| 2540 | + "metadata": {}, |
| 2541 | + "source": [ |
| 2542 | + "2. **Mocking API calls**\n", |
| 2543 | + "\n", |
| 2544 | + "When testing code that makes external API calls, mocking helps avoid actual network requests during testing.\n", |
| 2545 | + "\n", |
| 2546 | + "Example: Testing a function that makes an API call" |
| 2547 | + ] |
| 2548 | + }, |
| 2549 | + { |
| 2550 | + "cell_type": "code", |
| 2551 | + "execution_count": 35, |
| 2552 | + "id": "c0a3fb32", |
| 2553 | + "metadata": {}, |
| 2554 | + "outputs": [ |
| 2555 | + { |
| 2556 | + "name": "stdout", |
| 2557 | + "output_type": "stream", |
| 2558 | + "text": [ |
| 2559 | + "Overwriting main.py\n" |
| 2560 | + ] |
| 2561 | + } |
| 2562 | + ], |
| 2563 | + "source": [ |
| 2564 | + "%%writefile main.py\n", |
2413 | 2565 | "import requests\n",
|
2414 | 2566 | "from requests.exceptions import ConnectionError\n",
|
2415 | 2567 | "\n",
|
|
2420 | 2572 | " response = requests.get(\"http://localhost:5432\")\n",
|
2421 | 2573 | " return response.json()\n",
|
2422 | 2574 | " except ConnectionError:\n",
|
2423 |
| - " return None\n", |
| 2575 | + " return None" |
| 2576 | + ] |
| 2577 | + }, |
| 2578 | + { |
| 2579 | + "cell_type": "code", |
| 2580 | + "execution_count": 36, |
| 2581 | + "id": "2f5b1755", |
| 2582 | + "metadata": {}, |
| 2583 | + "outputs": [ |
| 2584 | + { |
| 2585 | + "name": "stdout", |
| 2586 | + "output_type": "stream", |
| 2587 | + "text": [ |
| 2588 | + "Overwriting test_main.py\n" |
| 2589 | + ] |
| 2590 | + } |
| 2591 | + ], |
| 2592 | + "source": [ |
| 2593 | + "%%writefile test_main.py\n", |
| 2594 | + "from unittest.mock import patch\n", |
| 2595 | + "from requests.exceptions import ConnectionError\n", |
| 2596 | + "from main import get_data\n", |
2424 | 2597 | "\n",
|
2425 | 2598 | "\n",
|
2426 |
| - "def test_get_data_fails():\n", |
| 2599 | + "@patch(\"main.requests.get\")\n", |
| 2600 | + "def test_get_data_fails(mock_get):\n", |
2427 | 2601 | " \"\"\"Test the get_data function when the API call fails\"\"\"\n",
|
2428 |
| - " # Mock the requests.get function\n", |
2429 |
| - " with patch(\"requests.get\") as mock_get:\n", |
2430 |
| - " # Define what happens when the function is called\n", |
2431 |
| - " mock_get.side_effect = ConnectionError\n", |
2432 |
| - " assert get_data() is None\n", |
| 2602 | + " # Define what happens when the function is called\n", |
| 2603 | + " mock_get.side_effect = ConnectionError\n", |
| 2604 | + " assert get_data() is None\n", |
2433 | 2605 | "\n",
|
2434 | 2606 | "\n",
|
2435 |
| - "def test_get_data_succeeds():\n", |
| 2607 | + "@patch(\"main.requests.get\")\n", |
| 2608 | + "def test_get_data_succeeds(mock_get):\n", |
2436 | 2609 | " \"\"\"Test the get_data function when the API call succeeds\"\"\"\n",
|
2437 |
| - " # Mock the requests.get function\n", |
2438 |
| - " with patch(\"requests.get\") as mock_get:\n", |
2439 |
| - " # Define the return value of the function\n", |
2440 |
| - " mock_get.return_value.json.return_value = {\"data\": \"test\"}\n", |
2441 |
| - " assert get_data() == {\"data\": \"test\"}\n", |
2442 |
| - "\n", |
2443 |
| - "```" |
| 2610 | + " # Define the return value of the function\n", |
| 2611 | + " mock_get.return_value.json.return_value = {\"data\": \"test\"}\n", |
| 2612 | + " assert get_data() == {\"data\": \"test\"}" |
| 2613 | + ] |
| 2614 | + }, |
| 2615 | + { |
| 2616 | + "cell_type": "markdown", |
| 2617 | + "id": "3a0701b9", |
| 2618 | + "metadata": {}, |
| 2619 | + "source": [ |
| 2620 | + "These tests mock the `requests.get()` function to simulate both successful and failed API calls, allowing us to test our function's behavior in different scenarios without making actual network requests." |
| 2621 | + ] |
| 2622 | + }, |
| 2623 | + { |
| 2624 | + "cell_type": "code", |
| 2625 | + "execution_count": 38, |
| 2626 | + "id": "3af021c9", |
| 2627 | + "metadata": { |
| 2628 | + "tags": [ |
| 2629 | + "hide-cell" |
| 2630 | + ] |
| 2631 | + }, |
| 2632 | + "outputs": [ |
| 2633 | + { |
| 2634 | + "name": "stdout", |
| 2635 | + "output_type": "stream", |
| 2636 | + "text": [ |
| 2637 | + "\u001b[1m============================= test session starts ==============================\u001b[0m\n", |
| 2638 | + "platform darwin -- Python 3.11.2, pytest-7.4.3, pluggy-1.3.0\n", |
| 2639 | + "rootdir: /Users/khuyentran/book/Efficient_Python_tricks_and_tools_for_data_scientists/Chapter5\n", |
| 2640 | + "plugins: dvc-3.28.0, hydra-core-1.3.2, typeguard-4.1.5, anyio-4.2.0, hypothesis-6.88.4\n", |
| 2641 | + "collected 2 items \u001b[0m\n", |
| 2642 | + "\n", |
| 2643 | + "test_main.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m [100%]\u001b[0m\n", |
| 2644 | + "\n", |
| 2645 | + "\u001b[32m============================== \u001b[32m\u001b[1m2 passed\u001b[0m\u001b[32m in 0.12s\u001b[0m\u001b[32m ===============================\u001b[0m\n", |
| 2646 | + "\u001b[0m" |
| 2647 | + ] |
| 2648 | + } |
| 2649 | + ], |
| 2650 | + "source": [ |
| 2651 | + "!pytest test_main.py" |
2444 | 2652 | ]
|
2445 | 2653 | },
|
2446 | 2654 | {
|
2447 |
| - "attachments": {}, |
2448 | 2655 | "cell_type": "markdown",
|
2449 |
| - "id": "ff3f0491", |
| 2656 | + "id": "e2ffc227-fd00-40ef-aaa2-74e8997aa2cd", |
2450 | 2657 | "metadata": {},
|
2451 | 2658 | "source": [
|
2452 |
| - "[Link to mock](https://docs.python.org/3/library/unittest.mock.html)." |
| 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." |
2453 | 2660 | ]
|
2454 | 2661 | },
|
2455 | 2662 | {
|
|
2902 | 3109 | {
|
2903 | 3110 | "cell_type": "code",
|
2904 | 3111 | "execution_count": 12,
|
| 3112 | + "id": "e0ca0f88", |
2905 | 3113 | "metadata": {},
|
2906 | 3114 | "outputs": [
|
2907 | 3115 | {
|
|
4506 | 4714 | "celltoolbar": "Tags",
|
4507 | 4715 | "hide_input": false,
|
4508 | 4716 | "kernelspec": {
|
4509 |
| - "display_name": "venv", |
| 4717 | + "display_name": "Python 3 (ipykernel)", |
4510 | 4718 | "language": "python",
|
4511 | 4719 | "name": "python3"
|
4512 | 4720 | },
|
|
4520 | 4728 | "name": "python",
|
4521 | 4729 | "nbconvert_exporter": "python",
|
4522 | 4730 | "pygments_lexer": "ipython3",
|
4523 |
| - "version": "3.11.2" |
| 4731 | + "version": "3.11.6" |
4524 | 4732 | },
|
4525 | 4733 | "toc": {
|
4526 | 4734 | "base_numbering": 1,
|
|
0 commit comments