test-driven development (TDD) -- Testing your application White-box tests are those which exercise the internals of the code, they inspect it down to a very fine level of granularity. On the other hand, black-box tests are those which consider the software under testing as if being within a box, the internals of which are ignored. -- The anatomy of a test Preparation Execution Verification -- Testing guidelines Keep them as simple as possible Tests should verify one thing and one thing only Tests should not make any unnnecessary assumption when verifying data Tests should exercise the what, rather than the how Tests should assume the leset possible in the preparation phase Tests should use upthe least possible amount of resources Unit testing Writing a unit test data.py def get_clean_data(source): data = load_data(source) cleaned_data = clean_data(data) return cleaned_data Mock objects and patching The fake objects are called mocks. the mock library was a third-party library that basically every project would install via pip. The act of replacing a real object or function with a mock is called patching. Once you have replaced everything you need not to run, with suitable mocks, you can pass to the second phase of the test and run the code you are exercising. After the execution, you will be able to check the mocks to verify your code has worked correctly. Assertions An assertion is a function (or method) that you can use to verify equality between objects, as well as other conditions. A classic unit test example Mocks, patches, and assertions are the basic tools we'll be using to write tests. filter_funcs.py def filter_ints(v): return [num for num in v if is_positive(num)] def is_positive(n): return n > 0 tests/test_ch7/test_filter_funcs.py from unittest import TestCase # 1 from unittest.mock import patch, call # 2 from nose.tools import assert_equal # 3 from ch7.filter_funcs import filter_ints # 4 class FilterIntsTestCase(TestCase): # 5 @patch('ch7.filter_funcs.is_positive') # 6 def test_filter_ints(self, is_positive_mock): # 7 # preparation v = [3, -4, 0, 5, 8] # execution filter_ints(v) # 8 # verification assert_equal( [call(3), call(-4), call(0), call(5), call(8)], is_positive_mock.call_args_list ) # 9 $ pip install nose $ nosetests tests/test_ch7/ Making a test fail $ nosetests tests/test_ch7/ Interface testing tests/test_ch7/test_filter_funcs.py def test_filter_ints_return_value(self): v = [3, -4, 0, -2, 5, 0, 8, -1] result = filter_ints(v) assert_list_equal([3, 5, 8], result) $ nosetests tests/test_ch7/ Comparing tests with and without mocks filter_funcs_refactored.py def filter_ints(v): v = [num for num in v if num != 0] # 1 return [num for num in v if is_positive(num)] $ nosetests tests/test_ch7/test_filter_funcs_refactored.py You must keep your mocks up-to-date and in sync with the code they are replacing, otherwise you risk having issues like the preceding one, or even worse. tests/test_ch7/test_filter_funcs_final.py from unittest import TestCase from nose.tools import assert_list_equal from ch7.filter_funcs import filter_ints class FilterIntsTestCase(TestCase): def test_filter_ints_return_value(self): v = [3, -4, 0, -2, 5, 0, 8, -1] result = filter_ints(v) assert_list_equal([3, 5, 8], result) filter_funcs_triangulation.py def filter_ints(v): return [3, 5, 8] tests/test_ch7/test_filter_funcs_final_triangulation.py def test_filter_ints_return_value(self): v1 = [3, -4, 0, -2, 5, 0, 8, -1] v2 = [7, -3, 0, 0, 9, 1] assert_list_equal([3, 5, 8], filter_ints(v1)) assert_list_equal([7, 9, 1], filter_ints(v2)) Boundaries and granularity tests/test_ch7/test_filter_funcs_is_positive_loose.py def test_is_positive(self): assert_equal(False, is_positive(-2)) # before boundary assert_equal(False, is_positive(0)) # on the boundary assert_equal(True, is_positive(2)) # after the boundary tests/test_ch7/test_filter_funcs_is_positive_correct.py def test_is_positive(self): assert_equal(False, is_positive(-1)) assert_equal(False, is_positive(0)) assert_equal(True, is_positive(1)) tests/test_ch7/test_filter_funcs_is_positive_better.py def test_is_positive(self): assert_equal(False, is_positive(0)) for n in range(1, 10 ** 4): assert_equal(False, is_positive(-n)) assert_equal(True, is_positive(n)) A more interesting example data_flatten.py nested = { 'fullname': 'Alessandra', 'age': 41, 'phone-numbers': ['+447421234567', '+447423456789'], 'residence': { 'address': { 'first-line': 'Alexandra Rd', 'second-line': '', }, 'zip': 'N8 0PP', 'city': 'London', 'country': 'UK', }, } flat = { 'fullname': 'Alessandra', 'age': 41, 'phone-numbers': ['+447421234567', '+447423456789'], 'residence.address.first-line': 'Alexandra Rd', 'residence.address.second-line': '', 'residence.zip': 'N8 0PP', 'residence.city': 'London', 'residence.country': 'UK', } data_flatten.py def flatten(data, prefix='', separator='.'): """Flattens a nested dict structure. """ if not isinstance(data, dict): return {prefix: data} if prefix else data result = {} for (key, value) in data.items(): result.update( flatten( value, _get_new_prefix(prefix, key, separator), separator=separator)) return result def _get_new_prefix(prefix, key, separator): return (separator.join((prefix, str(key))) if prefix else str(key)) tests/test_ch7/test_data_flatten.py # ... imports omitted ... class FlattenTestCase(TestCase): def test_flatten(self): test_cases = [ ({'A': {'B': 'C', 'D': [1, 2, 3], 'E': {'F': 'G'}}, 'H': 3.14, 'J': ['K', 'L'], 'M': 'N'}, {'A.B': 'C', 'A.D': [1, 2, 3], 'A.E.F': 'G', 'H': 3.14, 'J': ['K', 'L'], 'M': 'N'}), (0, 0), ('Hello', 'Hello'), ({'A': None}, {'A': None}), ] for (nested, flat) in test_cases: assert_equal(flat, flatten(nested)) def test_flatten_custom_separator(self): nested = {'A': {'B': {'C': 'D'}}} assert_equal( {'A#B#C': 'D'}, flatten(nested, separator='#')) -- Test-driven development TDD is a software development methodology that is based on the continuous repetition of a very short development cycle. Pros: You will refactor with much more confidence. The code will be more readable. The code will be more loose-coupled and easier to test and maintain. Writing tests first requires you to have a better understanding of the business requirements. Having everything unit tested means the code will be easier to debug. Cons: The whole company needs to believe in it. If you fail to understand the business requirements, this will reflect in the tests you write, and therefore it will reflect in the code too. Badly written tests are hard to maintain. -- Exceptions exceptions/first.example.py gen = (n for n in range(2)) next(gen) next(gen) next(gen) print(undefined_var) mylist = [1, 2, 3] mylist[5] mydict = {'a': 'A', 'b': 'B'} mydict['c'] 1 / 0 exceptions/try.syntax.py def try_syntax(numerator, denominator): try: print('In the try block: {}/{}' .format(numerator, denominator)) result = numerator / denominator except ZeroDivisionError as zde: print(zde) else: print('The result is:', result) return result finally: print('Exiting') print(try_syntax(12, 4)) print(try_syntax(11, 0)) $ python exceptions/try.syntax.py exceptions/json.example.py import json json_data = '{}' try: data = json.loads(json_data) except (ValueError, TypeError) as e: print(type(e), e) exceptions/multiple.except.py try: # some code except Exception1: # react to Exception1 except (Exception2, Exception3): # react to Exception2 and Exception3 except Exception3: # react to Exception3 ... exceptions/for.loop.py n = 100 found = False for a in range(n): if found: break for b in range(n): if found: break for c in range(n): if 42 * a + 17 * b + c == 5096: found = True print(a, b, c) # 79 99 95 exceptions/for.loop.py class ExitLoopException(Exception): pass try: n = 100 for a in range(n): for b in range(n): for c in range(n): if 42 * a + 17 * b + c == 5096: raise ExitLoopException(a, b, c) except ExitLoopException as ele: print(ele) # (79, 99, 95) -- Profiling Python Profiling means having the application run while keeping track of several different parameters, like the number of times a function is called, the amount of time spent inside it, and so on. Profiling can help us find the bottlenecks in our application, so that we can improve only what is really slowing us down. profiling/triples.py def calc_triples(mx): triples = [] for a in range(1, mx + 1): for b in range(a, mx + 1): hypotenuse = calc_hypotenuse(a, b) if is_int(hypotenuse): triples.append((a, b, int(hypotenuse))) return triples def calc_hypotenuse(a, b): return (a**2 + b**2) ** .5 def is_int(n): # n is expected to be a float return n.is_integer() triples = calc_triples(1000) $ python -m cProfile profiling/triples.py profiling/triples.py def calc_hypotenuse(a, b): return (a*a + b*b) ** .5 profiling/triples.py def is_int(n): return n == int(n)
Thursday, March 24, 2016
Learning Python 7 - Tesing, Profiling, and Dealing with Exceptions
Labels:
Python
Subscribe to:
Post Comments (Atom)
Blog Archive
-
▼
2016
(87)
-
▼
March
(25)
- Learning Python 12 - Summing Up
- Learning Python 11 - Debugging and Troubleshooting
- Learning Python 10 - Web Development Done Right
- Learning Python 9 - Data Science
- Learning Python 8 - The GUIs and Scripts
- Learning Python 7 - Tesing, Profiling, and Dealing...
- Learning Python 6 - OOP, Decorators, and Iterators
- Learning Python 5 - Saving Time and Memory
- Learning Python 4 - Functions
- Learning Python 3 - Interating and Making Decisions
- Learning Python 2 - Build-in Data Types
- Learning Python 1 - Introduction
- Bandit algorithms 7 - Bandits in the Real World: C...
- Bandit algorithms 6 - UCB - The Upper Confidence B...
- Bandit algorithms 5 - The Softmax Algorithm
- Bandit algorithms 4 - Debugging Bandit Algorithms
- Bandit algorithms 3 - The Epsilon-Greedy Algorithm
- Bandit algorithms 2 - Multiarmed Bandit Algorithms
- Bandit algorithms 1 - Exploration and Exploitation
- Python Data Analysis 11 - Recognizing Handwritten ...
- Python Data Analysis 10 - Embedding the JavaScript...
- Python Data Analysis 9 - An Example - Meteorologic...
- Python Data Analysis 8 - Machine Learning with sci...
- Python Data Analysis 7 - Data Visualization with m...
- Python Data Analysis 6 - pandas in Depth: Data Man...
-
▼
March
(25)
No comments:
Post a Comment