Using_Python_to_Interact_wi.../module5.ipynb
2025-01-27 16:51:28 +03:00

1050 lines
38 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Simple Tests"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Unit Tests"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Concepts**\n",
"\n",
"Unittest relies on the following concepts:\n",
"\n",
"- **Test fixture:** This refers to preparing to perform one or more tests. In addition, test fixtures also include any actions involved in testing cleanup. This could involve creating temporary or proxy databases, directories, or starting a server process.\n",
"- **Test case:** This is the individual unit of testing that looks for a specific response to a set of inputs. If needed, TestCase is a base class provided by unittest and can be used to create new test cases.\n",
"- **Test suite:** This is a collection of test cases, test suites, or a combination of both. It is used to compile tests that should be executed together.\n",
"- **Test runner:** This runs the test and provides developers with the outcomes data. The test runner can use different interfaces, like graphical or textual, to provide the developer with the test results. It can also provide a special value to developers to communicate the test results. "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(['flour', 'sugar', 'eggs', 'cocoa', 'sprinkles', 'cherries'], 14)"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from typing import List\n",
"\n",
"\n",
"class CakeFactory:\n",
" def __init__(self, cake_type: str, size: str):\n",
" self.cake_type = cake_type\n",
" self.size = size\n",
" self.toppings = []\n",
"\n",
" # Price based on cake type and size\n",
" self.price = 10 if self.cake_type == \"chocolate\" else 8\n",
" self.price += 2 if self.size == \"medium\" else 4 if self.size == \"large\" else 0\n",
"\n",
" def add_topping(self, topping: str):\n",
" self.toppings.append(topping)\n",
" # Adding 1 to the price for each topping\n",
" self.price += 1\n",
"\n",
" def check_ingredients(self) -> List[str]:\n",
" ingredients = ['flour', 'sugar', 'eggs']\n",
" ingredients.append('cocoa') if self.cake_type == \"chocolate\" else ingredients.append('vanilla extract')\n",
" ingredients += self.toppings\n",
" return ingredients\n",
"\n",
" def check_price(self) -> float:\n",
" return self.price\n",
"\n",
"# Example of creating a cake and adding toppings\n",
"cake = CakeFactory(\"chocolate\", \"medium\")\n",
"cake.add_topping(\"sprinkles\")\n",
"cake.add_topping(\"cherries\")\n",
"cake_ingredients = cake.check_ingredients()\n",
"cake_price = cake.check_price()\n",
"\n",
"\n",
"cake_ingredients, cake_price"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import unittest\n",
"\n",
"class TestCakeFactory(unittest.TestCase):\n",
" def test_create_cake(self):\n",
" cake = CakeFactory(\"vanilla\", \"small\")\n",
" self.assertEqual(cake.cake_type, \"vanilla\")\n",
" self.assertEqual(cake.size, \"small\")\n",
" self.assertEqual(cake.price, 8) # Vanilla cake, small size\n",
"\n",
" def test_add_topping(self):\n",
" cake = CakeFactory(\"chocolate\", \"large\")\n",
" cake.add_topping(\"sprinkles\")\n",
" self.assertIn(\"sprinkles\", cake.toppings)\n",
"\n",
" def test_check_ingredients(self):\n",
" cake = CakeFactory(\"chocolate\", \"medium\")\n",
" cake.add_topping(\"cherries\")\n",
" ingredients = cake.check_ingredients()\n",
" self.assertIn(\"cocoa\", ingredients)\n",
" self.assertIn(\"cherries\", ingredients)\n",
" self.assertNotIn(\"vanilla extract\", ingredients)\n",
"\n",
" def test_check_price(self):\n",
" cake = CakeFactory(\"vanilla\", \"large\")\n",
" cake.add_topping(\"sprinkles\")\n",
" cake.add_topping(\"cherries\")\n",
" price = cake.check_price()\n",
" self.assertEqual(price, 13) # Vanilla cake, large size + 2 toppings\n",
"\n",
"\n",
"# Running the unittests\n",
"unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(TestCakeFactory))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## pytest"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pytest\n",
"class Fruit:\n",
" def __init__(self, name):\n",
" self.name = name\n",
" self.cubed = False\n",
"\n",
"\n",
" def cube(self):\n",
" self.cubed = True\n",
"\n",
"\n",
"class FruitSalad:\n",
" def __init__(self, *fruit_bowl):\n",
" self.fruit = fruit_bowl\n",
" self._cube_fruit()\n",
"\n",
"\n",
" def _cube_fruit(self):\n",
" for fruit in self.fruit:\n",
" fruit.cube()\n",
"\n",
"\n",
"# Arrange\n",
"@pytest.fixture\n",
"def fruit_bowl():\n",
" return [Fruit(\"apple\"), Fruit(\"banana\")]\n",
"\n",
"\n",
"def test_fruit_salad(fruit_bowl):\n",
" # Act\n",
" fruit_salad = FruitSalad(*fruit_bowl)\n",
"\n",
"\n",
" # Assert\n",
" assert all(fruit.cubed for fruit in fruit_salad.fruit)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Comparing unittest and pytest"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Both unittest and pytest provide developers with tools to create robust and reliable code through different forms of tests. Both can be used while creating programs within Python, and it is the developers preference on which type they want to use.\n",
"\n",
"In this reading, you will learn about the differences between unittest and pytest, and when to use them.\n",
"\n",
"**Key differences**\n",
"\n",
"Unittest is a tool that is built directly into Python, while pytest must be imported from outside your script. Test discovery acts differently for each test type. Unittest has the functionality to automatically detect test cases within an application, but it must be called from the command line. Pytests are performed automatically using the prefix test_. Unittests use an object-oriented approach to write tests, while pytests use a functional approach. Pytests use built-in assert statements, making tests easier to read and write. On the other hand, unittests provide special assert methods like assertEqual() or assertTrue().\n",
"\n",
"Backward compatibility exists between unittest and pytest. Because unittest is built directly into Python, these test suites are more easily executed. But that doesnt mean that pytest cannot be executed. Because of backward compatibility, the unittest framework can be seamlessly executed using the pytest framework without major modifications. This allows developers to adopt pytest gradually and integrate them into their code.\n",
"\n",
"**Key takeaways**\n",
"\n",
"Unittest and pytest are both beneficial to developers in executing tests on their code written in Python. Each one has its pros and cons, and it is up to the developer and their preference on which type of testing framework they want to use. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"import re\n",
"def rearrange_name(name):\n",
" result = re.search(r\"^([\\w .]*), ([\\w .]*)$\", name)\n",
" return \"{} {}\".format(result[2], result[1])\n",
"from rearrange import rearrange_name\n",
"\n",
"rearrange_name(\"Lovelace, Ada\") "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Writing unit tests in python"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"import re\n",
"def rearrange_name(name):\n",
" result = re.search(r\"^([\\w .]*), ([\\w .]*)$\", name)\n",
" return \"{} {}\".format(result[2], result[1])\n",
"\n",
"#!/usr/bin/env python3\n",
"import unittest\n",
"\n",
"from rearrange import rearrange_name\n",
"class TestRearrange(unittest.TestCase):\n",
" def test_basic(self):\n",
" testcase = \"Lovelace, Ada\"\n",
" expected = \"Ada Lovelace\"\n",
" self.assertEqual(rearrange_name(testcase), expected)\n",
"\n",
"# Run the tests\n",
"unittest.main()\n",
"\n",
"chmod +x rearrange_test.py \n",
"./rearrange_test.py "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Edge cases"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def test_empty(self):\n",
" testcase = \"\"\n",
" expected = \"\"\n",
" self.assertEqual(rearrange_name(testcase), expected)\n",
"\n",
"./rearrange_test.py \n",
"\n",
"#!/usr/bin/env python3\n",
"import re\n",
"def rearrange_name(name):\n",
" result = re.search(r\"^([\\w .-]*), ([\\w .-]*)$\", name)\n",
" if result is None:\n",
" return \"\"\n",
" return \"{} {}\".format(result[2], result[1])\n",
"\n",
"./rearrange_test.py"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Additional test cases"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from rearrange import rearrange_name\n",
"import unittest\n",
"\n",
"class TestRearrange(unittest.TestCase):\n",
" \n",
" def test_basic(self):\n",
" testcase = \"Lovelace, Ada\"\n",
" expected = \"Ada Lovelace\"\n",
" self.assertEqual(rearrange_name(testcase), expected)\n",
"\n",
" def test_empty(self):\n",
" testcase = \"\"\n",
" expected = \"\"\n",
" self.assertEqual(rearrange_name(testcase), expected)\n",
"\n",
" def test_double_name(self):\n",
" testcase = \"Hopper, Grace M.\"\n",
" expected = \"Grace M. Hopper\"\n",
" self.assertEqual(rearrange_name(testcase), expected)\n",
"\n",
" def test_one_name(self):\n",
" testcase = \"Voltaire\"\n",
" expected = \"Voltaire\"\n",
" self.assertEqual(rearrange_name(testcase), expected)\n",
"\n",
"# Run the tests\n",
"unittest.main()\n",
"\n",
"import re\n",
"def rearrange_name(name):\n",
" result = re.search(r\"^([\\w .]*), ([\\w .]*)$\", name)\n",
" if result is None:\n",
" return name\n",
" return \"{} {}\".format(result[2], result[1])\n",
"\n",
"./rearrange_test.py "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Other Test Concepts"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Assertions**\n",
"\n",
"The TestCase class also employs its own assert methods that work similarly to the assert statement: if a test fails, an exception is raised with an explanatory message, and unittest identifies the test case as a failure. In the above example, there are several assertions used:\n",
"- An assertEqual() to check for an expected result\n",
"- An assertTrue() and an assertFalse() to verify a condition\n",
"- An assertRaises() to verify that a specific exception gets raised\n",
"\n",
"Each of these assert methods is used in place of the standard assert statement so the test runner can gather all the test results and generate a report.\n",
"\n",
"Below is a list of commonly used assert methods in the TestCase class. For more information on each method, select the embedded link in the list provided. \n",
"- The assertEqual(a, b) method checks that a == b\n",
"- The assertNotEqual(a, b) method checks that a != b\n",
"- The assertTrue(x) method checks that bool(x) is True\n",
"- The assertFalse(x) method checks that bool(x) is False\n",
"- The assertIs(a, b) method checks that a is b\n",
"- The assertIsNot(a, b) method checks that a is not b\n",
"- The assertIsNone(x) method checks that x is None\n",
"- The assertIsNotNone(x) method checks that x is not None\n",
"- The assertIn(a, b) method checks that a in b\n",
"- The assertNotIn(a, b) method checks that a not in b\n",
"- The assertIsInstance(a, b) method checks that isinstance(a, b)\n",
"- The assertNotIsInstance(a, b) method checks that not isinstance(a, b)\n",
"\n",
"You can also use assert methods to generate exceptions, warnings, and log messages. For example, another important assert method in unit testing is assertRaises. It allows you to test whether exceptions are raised when they should be, ensuring that your program can handle errors. assertRaises also allows developers to check which specific exception type is raised, ensuring that the correct error handling is in place."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Command-line interface**\n",
"\n",
"The command-line interface allows you to interact with an application or program through your operating system command line, terminal, or console by beginning your code with a text command. When you want to run tests in Python, you can use the unittest module from the command line to run tests from modules, classes, or even individual test methods. This also allows you to run multiple files at one time. \n",
"\n",
"**To call an entire module:**\n",
"python -m unittest test_module1 test_module2 \n",
"\n",
"**To call a test class:**\n",
"python -m unittest test_module.TestClass\n",
"\n",
"**To call a test method:**\n",
"python -m unittest test_module.TestClass.test_method\n",
"\n",
"**Test modules can also be called using a file path, as written below:**\n",
"python -m unittest tests/test_something.py"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import unittest \n",
"class TestStringMethods(unittest.TestCase): \n",
" def test_upper(self): \n",
" # This function tests whether 'foo' in lowercase converts to uppercase correctly.\n",
" # If not, it will fail.\n",
" self.assertEqual('foo'.upper(), 'FOO') \n",
" def test_isupper(self): \n",
" # This function tests two things:\n",
" # 1) The string 'FOO' is already in uppercase and therefore should return True when\n",
" # the method .isupper() is called on it. If not, it will fail.\n",
" # 2) The string 'Foo' is not entirely in uppercase (it contains a lowercase letter)\n",
" # and therefore should return False when the method .isupper() is called on it.\n",
" # If not, it will also fail.\n",
" self.assertTrue('FOO'.isupper()) \n",
" self.assertFalse('Foo'.isupper()) \n",
" def test_split(self): \n",
" # This function tests the split method of strings:\n",
" # It first creates a string 'hello world', and checks whether it is correctly split\n",
" # into two separate words when the method .split() is called on it. If not, it will fail.\n",
" s = 'hello world' \n",
" self.assertEqual(s.split(), ['hello', 'world']) \n",
" # This tests that if you call s.split(2) (where 2 isn't a string separator),\n",
" # Python raises a TypeError. If it doesnt or if it wrongly returns something else,\n",
" # the test will fail.\n",
" with self.assertRaises(TypeError): \n",
" s.split(2) \n",
"if __name__ == '__main__': \n",
" unittest.main() \n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"class Library:\n",
"\tdef __init__(self):\n",
"\t\tself.collection = []\n",
"\tdef add_book(self, book_title):\n",
"\t\tself.collection.append(book_title)\n",
"\tdef has_book(self, book_title):\n",
"\t\treturn book_title in self.collection\n",
"\n",
"# Unit test for the Library system\n",
"class TestLibrary(unittest.TestCase):\n",
"\tdef test_adding_book_to_library(self):\n",
" \t# Arrange\n",
"\t\tlibrary = Library()\n",
"\t\tnew_book = \"Python Design Patterns\"\n",
" \t# Act\n",
" \tlibrary.add_book(new_book)\n",
" \t# Assert\n",
" \tself.assertTrue(library.has_book(new_book))\n",
"\n",
"# Running the test\n",
"library_test_output = unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(TestLibrary))\n",
"print(library_test_output)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Test suites\n",
"Testing can be time-intensive, but there are ways that you can optimize the testing process. The following methods and modules allow you to define instructions that execute before and after each test method:\n",
"- setUp() can be called automatically with every test thats run to set up code. \n",
"- tearDown() helps clean up after the test has been run. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import unittest\n",
"import os\n",
"import shutil\n",
"# Function to test\n",
"def simple_addition(a, b):\n",
"\treturn a + b\n",
"# Paths for file operations\n",
"ORIGINAL_FILE_PATH = \"/tmp/original_test_file.txt\"\n",
"COPIED_FILE_PATH = \"/mnt/data/copied_test_file.txt\"\n",
"# Global counter\n",
"COUNTER = 0\n",
"# This method will be run once before any tests or test classes\n",
"def setUpModule():\n",
"\tglobal COUNTER\n",
"\tCOUNTER = 0\n",
"\t# Create a file in /tmp\n",
"\twith open(ORIGINAL_FILE_PATH, 'w') as file:\n",
" \tfile.write(\"Test Results:\\n\")\n",
"# This method will be run once after all tests and test classes\n",
"def tearDownModule():\n",
"\t# Copy the file to another directory\n",
"\tshutil.copy2(ORIGINAL_FILE_PATH, COPIED_FILE_PATH)\n",
"\t# Remove the original file\n",
"\tos.remove(ORIGINAL_FILE_PATH)\n",
"class TestSimpleAddition(unittest.TestCase):\n",
"\t# This method will be run before each individual test\n",
"\tdef setUp(self):\n",
" \tglobal COUNTER\n",
" \tCOUNTER += 1\n",
"\t# This method will be run after each individual test\n",
"\tdef tearDown(self):\n",
" \t# Append the test result to the file\n",
" \twith open(ORIGINAL_FILE_PATH, 'a') as file:\n",
" \tresult = \"PASSED\" if self._outcome.success else \"FAILED\"\n",
" \tfile.write(f\"Test {COUNTER}: {result}\\n\")\n",
"\tdef test_add_positive_numbers(self):\n",
" \tself.assertEqual(simple_addition(3, 4), 7)\n",
"\tdef test_add_negative_numbers(self):\n",
" \tself.assertEqual(simple_addition(-3, -4), -7)\n",
"# Running the tests\n",
"suite = unittest.TestLoader().loadTestsFromTestCase(TestSimpleAddition)\n",
"runner = unittest.TextTestRunner()\n",
"runner.run(suite)\n",
"# Read the copied file to show the results\n",
"with open(COPIED_FILE_PATH, 'r') as result_file:\n",
"\ttest_results = result_file.read()\n",
"print(test_results)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Errors and Exceptions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## The Try-Except concept"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"def character_frequency(filename):\n",
" \"\"\"Counts the frequency of each character in the given file.\"\"\"\n",
" # First try to open the file\n",
" try:\n",
" f = open(filename)\n",
" except OSError:\n",
" return None\n",
" # Now process the file\n",
" characters = {}\n",
" for line in f:\n",
" for char in line:\n",
" characters[char] = characters.get(char, 0) + 1\n",
" f.close() \n",
" return characters"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Raising errors"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"def validate_user(username, minlen):\n",
" if minlen < 1:\n",
" raise ValueError(\"minlen must be at least 1\")\n",
" if len(username) < minlen:\n",
" return False\n",
" if not username.isalnum():\n",
" return False\n",
" return True\n",
"\n",
"from validations import validate_user\n",
"validate_user(\"\", -1)\n",
"\n",
"from validations import validate_user\n",
"validate_user(\"\", 1)\n",
"validate_user(\"myuser\", 1)\n",
"\n",
"from validations import validate_user\n",
"validate_user(88, 1)\n",
"\n",
"from validations import validate_user\n",
"validate_user([], 1)\n",
"\n",
"from validations import validate_user\n",
"validate_user([\"name\"], 1)\n",
"\n",
"#!/usr/bin/env python3\n",
"def validate_user(username, minlen):\n",
" assert type(username) == str, \"username must be a string\"\n",
" if minlen < 1:\n",
" raise ValueError(\"minlen must be at least 1\")\n",
" if len(username) < minlen:\n",
" return False\n",
" if not username.isalnum():\n",
" return False\n",
" return True\n",
"\n",
" from validations import validate_user\n",
"validate_user([3], 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Testing for expected errors"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"import unittest\n",
"from validations import validate_user\n",
"class TestValidateUser(unittest.TestCase):\n",
" def test_valid(self):\n",
" self.assertEqual(validate_user(\"validuser\", 3), True)\n",
" def test_too_short(self):\n",
" self.assertEqual(validate_user(\"inv\", 5), False)\n",
" def test_invalid_characters(self):\n",
" self.assertEqual(validate_user(\"invalid_user\", 1), False)\n",
" def test_invalid_minlen(self):\n",
" self.assertRaises(ValueError, validate_user, \"user\", -1)\n",
"# Run the tests\n",
"unittest.main()\n",
"\n",
"./validations_test.py"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Exception handling**\n",
"\n",
"When performing exception handling, it is important to predict which exceptions can happen. Sometimes, to figure out which exceptions you need to account for, you have to let your program fail.\n",
"The simplest way to handle exceptions in Python is by using the try and except clauses. \n",
"In the try clause, Python executes all statements until it encounters an exception. You use the except clause to catch and handle the exception(s) that Python encounters in the try clause.\n",
"Here is the process for how it works: \n",
"1. Python runs the try clause, e.g., the statement(s) between the try and except keywords.\n",
"2. If no error occurs, Python skips the except clause and the execution of the try statement is finished.\n",
"3. If an error occurs during execution of the try clause, Python skips the rest of the try clause and transfers control to the corresponding except block. If the type of error matches what is listed after the except keyword, Python executes the except clause. The execution then continues on after the try/except block.\n",
"4. If an exception occurs but it does not match what is listed in the except clause, it is passed onto try statements outside of that try/except block. However, if a handler for that exception cannot be found, the exception becomes an unhandled exception, the execution stops, and Python displays a designated error message. \n",
"\n",
"Sometimes, a try statement can have more than one except clause so that the code can specify handlers for different exceptions. This can help to reduce the number of unhandled exceptions.\n",
"\n",
"You can use exceptions to catch almost everything. It is good practice as a developer or programmer to be as specific as possible with the types of exceptions that you intend to handle, especially if youre creating your own exceptions. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Raise exceptions**\n",
"\n",
"As a developer or programmer, you might want to raise an error yourself. Usually, this happens when some of the conditions necessary for a function to do its job properly aren't met and returning none or some other base value isn't good enough. You can raise an error or raise an exception (also known as “throwing an exception”), which forces a particular exception to occur, and notifies you that something in your code is going wrong or an error has occurred. \n",
"\n",
"Here are some instances where raising an exception is a useful tool:\n",
"- A file doesnt exist\n",
"- A network or database connection fails\n",
"- Your code receives invalid input\n",
"\n",
"In the example below, the code raises two built-in Python exceptions: raise ValueError and raise ZeroDivisionError. You can find more information on these raises in the example below, along with explanations of potential errors that may occur during an exception."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# File reading function with exception handling\n",
"def read_file(filename):\n",
"\ttry:\n",
"\t\twith open(filename, 'r') as f:\n",
"\t\t\treturn f.read()\n",
"\texcept FileNotFoundError:\n",
"\t\treturn \"File not found!\"\n",
"\tfinally:\n",
"\t\tprint(\"Finished reading file.\")\n",
"\n",
"def faulty_read_and_divide(filename):\n",
"\twith open(filename, 'r') as file:\n",
"\t\tdata = file.readlines()\n",
"\t\tnum1 = int(data[0])\n",
"\t\tnum2 = int(data[1])\n",
"\t\treturn num1 / num2"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def enhanced_read_and_divide(filename):\n",
"\ttry:\n",
"\t\twith open(filename, 'r') as file:\n",
"\t\t\tdata = file.readlines()\n",
" # Ensure there are at least two lines in the file\n",
" if len(data) < 2:\n",
" raise ValueError(\"Not enough data in the file.\")\n",
" num1 = int(data[0])\n",
" num2 = int(data[1])\n",
" # Check if second number is zero\n",
" if num2 == 0:\n",
" raise ZeroDivisionError(\"The denominator is zero.\")\n",
" return num1 / num2\n",
"\texcept FileNotFoundError:\n",
" return \"Error: The file was not found.\"\n",
"\texcept ValueError as ve:\n",
" return f\"Value error: {ve}\"\n",
"\texcept ZeroDivisionError as zde:\n",
" return f\"Division error: {zde}\"\n",
"\n",
"# The errors should read:\n",
"# File-level issues:\n",
"# Value error: Not enough data in the file.\n",
"# Error: The file was not found.\n",
"# Data-level issues: Value error: invalid literal for int() with base 10: 'apple'\n",
"# Division error: The denominator is zero."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**assert statements**\n",
"\n",
"assert statements help you to verify if a certain condition is met and throw an exception if it isnt. As is stated in the name, their purpose is to \"assert\" that certain conditions are true at specific points in your program. \n",
"\n",
"The assert statement exists in almost every programming language and has two main uses:\n",
"\n",
"- To help detect problems earlier in development, rather than later when some other operation fails. Problems that arent addressed until later in the development process can turn out to be more time-intensive and costly to fix.\n",
"- To provide a form of documentation for other developers reading the code."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def OrganizeList(myList):\n",
" for item in myList:\n",
" assert type(item) == int, \"Error: List items must be integers.\"\n",
" myList.sort()\n",
" return myList\n",
"\n",
"print(OrganizeList(my_new_list))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Terms and definitions from module 5**\n",
"\n",
"**Automatic testing:** A process where software checks itself for errors and confirms that it works correctly\n",
"**Black-box tests:** A test where there is an awareness of what the program is supposed to do but not how it does it\n",
"**Edge cases:** Inputs to code that produce unexpected results, found at the extreme ends of the ranges of input\n",
"**Pytest:** A powerful Python testing tool that assists programmers in writing more effective and stable programs\n",
"**Software testing:** A process of evaluating computer code to determine whether or not it does what is expected\n",
"**Test case:** This is the individual unit of testing that looks for a specific response to a set of inputs\n",
"**Test fixture:** This prepared to perform one or more tests\n",
"**Test suite:** This is used to compile tests that should be executed together\n",
"**Test runner:** This runs the test and provides developers with the outcomes data\n",
"**unittest:** A set of Python tools to construct and run unit tests\n",
"**Unit tests:** A test to verify that small isolated parts of a program work correctly\n",
"**White-box test:** A test where test creator knows how the code works and can write test cases that use the understanding to make sure it performs as expected"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Exemplar: Implementing unit testing"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# emails. py\n",
"# student@14119fc213a3 :~/scripts$ cat emails. py\n",
"#!/usr/bin/env python3\n",
"import sys\n",
"import csv\n",
"def populate_dictionary(filename):\n",
" \"\"\"Populate a dictionary with name/email pairs for easy lookup.\"\"\"\n",
" email_dict = {}\n",
" with open(filename) as csvfile:\n",
" lines = csv.reader(csvfile, delimiter = ',')\n",
" for row in lines:\n",
" name = str(row[0].lower())\n",
" email_dict[name] = row[l]\n",
" return email_dict\n",
"def find_email(argv):\n",
" \"\"\"Return an email address based on the username given.\"\"\"\n",
" # Create the username based on the command line input .\n",
" fullname = str(argv[1] + \" \" + argv[2])\n",
" # Preprocess the data\n",
" email_dict = populate_dictionary('/home/student/data/user_emails.csv')\n",
" # Find and print the email\n",
" return email_dict.get(fullname. lower())\n",
"def main():\n",
" print(find_email(sys.argv))\n",
"\n",
"if __name__ == \"__main__\":\n",
" main()\n",
"\n",
"# python3 emails.py Blossom Gill\n",
"# blossom@abc.edu"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"import unittest\n",
"from emails import find_email\n",
"class EmailsTest(unittest.TestCase):\n",
" def test_basic(self):\n",
" testcase = [None, \"Bree\", \"Campbell\"]\n",
" expected = \"breee@abc.edu\"\n",
" self.assertEqual(find_email(testcase), expected)\n",
"if __name__ == '__main__':\n",
" unittest.main()\n",
"\n",
"# Output:\n",
"# .\n",
"# ----------------------------------------------------------------------\n",
"# Ran 1 test in 0.001s\n",
"# OK"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Test Case 1: Missing parameters**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"Traceback (most recent call last):\n",
" File \"emails.py\", line 30, in <module>\n",
" main()\n",
" File \"emails.py\", line 27, in main\n",
" print(find_email(sys.argv))\n",
" File \"emails.py\", line 20, in find_email\n",
" fullname = str(argv[1] + \" \" + argv[2])\n",
"IndexError: list index out of range\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"import unittest\n",
"from emails import find_email\n",
"class TestFile(unittest.TestCase):\n",
" def test_basic(self):\n",
" testcase = [None, \"Bree\", \"Campbell\"]\n",
" expected = \"breee@abc.edu\"\n",
" self.assertEqual(find_email(testcase), expected)\n",
" def test_one_name(self):\n",
" testcase = [None, \"John\"]\n",
" expected = \"Missing parameters\"\n",
" self.assertEqual(find_email(testcase), expected)\n",
"if __name__ == '__main__':\n",
" unittest.main()\n",
"\n",
"# Output:\n",
"# .E\n",
"# ======================================================================\n",
"# ERROR: test_one_name (__main__.TestFile)\n",
"# ----------------------------------------------------------------------\n",
"# Traceback (most recent call last):\n",
"# File \"./emails_test.py\", line 15, in test_one_name\n",
"# self.assertEqual(find_email(testcase), expected)\n",
"# File \"/home/student/scripts/emails.py\", line 20, in find_email\n",
"# fullname = str(argv[1] + \" \" + argv[2])\n",
"# IndexError: list index out of range\n",
"# ----------------------------------------------------------------------\n",
"# Ran 2 tests in 0.002s\n",
"# FAILED (errors=1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"import sys\n",
"import csv\n",
"def populate_dictionary(filename):\n",
" \"\"\"Populate a dictionary with name/email pairs for easy lookup.\"\"\"\n",
" email_dict = {}\n",
" with open(filename) as csvfile:\n",
" lines = csv.reader(csvfile, delimiter = ',')\n",
" for row in lines:\n",
" name = str(row[0].lower())\n",
" email_dict[name] = row[1]\n",
" return email_dict\n",
"def find_email(argv):\n",
" \"\"\" Return an email address based on the username given.\"\"\"\n",
" # Create the username based on the command line input.\n",
" try:\n",
" fullname = str(argv[1] + \" \" + argv[2])\n",
" # Preprocess the data\n",
" email_dict = populate_dictionary('/home/{{ username }}/data/user_emails.csv')\n",
" # Find and print the email\n",
" return email_dict.get(fullname.lower())\n",
" except IndexError:\n",
" return \"Missing parameters\"\n",
"def main():\n",
" print(find_email(sys.argv))\n",
"if __name__ == \"__main__\":\n",
" main()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**Test Case 2: Random email address**"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"import unittest\n",
"from emails import find_email\n",
"class EmailsTest(unittest.TestCase):\n",
" def test_basic(self):\n",
" testcase = [None, \"Bree\", \"Campbell\"]\n",
" expected = \"breee@abc.edu\"\n",
" self.assertEqual(find_email(testcase), expected)\n",
" def test_one_name(self):\n",
" testcase = [None, \"John\"]\n",
" expected = \"Missing parameters\"\n",
" self.assertEqual(find_email(testcase), expected)\n",
" def test_two_name(self):\n",
" testcase = [None, \"Roy\",\"Cooper\"]\n",
" expected = \"No email address found\"\n",
" self.assertEqual(find_email(testcase), expected)\n",
"if __name__ == '__main__':\n",
" unittest.main()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"#!/usr/bin/env python3\n",
"import csv\n",
"import sys\n",
"def populate_dictionary(filename):\n",
" \"\"\"Populate a dictionary with name/email pairs for easy lookup.\"\"\"\n",
" email_dict = {}\n",
" with open(filename) as csvfile:\n",
" lines = csv.reader(csvfile, delimiter = ',')\n",
" for row in lines:\n",
" name = str(row[0].lower())\n",
" email_dict[name] = row[1]\n",
" return email_dict\n",
"def find_email(argv):\n",
" \"\"\" Return an email address based on the username given.\"\"\"\n",
" # Create the username based on the command line input.\n",
" try:\n",
" fullname = str(argv[1] + \" \" + argv[2])\n",
" # Preprocess the data\n",
" email_dict = populate_dictionary('/home/{{ username }}/data/user_emails.csv')\n",
" # If email exists, print it\n",
" if email_dict.get(fullname.lower()):\n",
" return email_dict.get(fullname.lower())\n",
" else:\n",
" return \"No email address found\"\n",
" except IndexError:\n",
" return \"Missing parameters\"\n",
"def main():\n",
" print(find_email(sys.argv))\n",
"if __name__ == \"__main__\":\n",
" main()\n"
]
}
],
"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.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}