| Bri Hatch | Personal | Work |
|---|---|---|
| bri@ifokr.org |
Dropzone AI daethnir@dropzone.ai |
def min_coins(target_value, coins):
"""Compute fewest coins to add to target value.
Input is a list of coin values.
target_value is value they should sum to
Returns a dict of coins and quantities.
Returns None if not possible.
"""
for coin in coins:
...
return sumthin
$ ipython3
>>> from mycode import min_coins
>>> min_coins(121, [5, 2, 1])
{5: 24, 1: 1}
$ tail -4 mycode.py
if __name__ == '__main__':
print(min_coins(121, [5, 2, 1]), "should be {5: 24, 1: 1}")
print(min_coins(24, [7, 3]), "should be {7: 3, 3: 1}")
print(min_coins(25, [4, 2]), "should be None")
$ ./mycode.py
{5: 24, 1: 1} should be {5: 24, 1: 1}
{7: 3, 3: 1} should be {7: 3, 3: 1}
None should be None
$ less tests.py
#!/usr/bin/env python3
import unittest
from mycode import min_coins
class Tests(unittest.TestCase):
def test_min_coin_one(self):
self.assertEqual(
min_coins(121, [5, 2, 1]),
{5: 24, 1: 1}
)
if __name__ == '__main__':
unittest.main()
$ ./tests.py . ---------------------------------------- Ran 1 test in 0.001s OK $ echo $? 0
$ ./tests.py -v test_min_coin_one (__main__.Tests) ... ok ---------------------------------------- Ran 1 test in 0.001s OK $ echo $? 0
Commonly used assertions:
self.assertEqual(a, b)self.assertNotEqual(a, b)self.assertTrue(bool)self.assertFalse(bool)self.assertIn(a in b)self.assertNotIn(a in b)self.assertIsNone(a)self.assertRaises(exception, func, *args, **kwargs)self.assertRaisesRegex(exception, regexp, func, *args, **kwargs)self.assertLogs(logger, level) / NoLogs()
self.assertRaises(RuntimeError, min_coin, 125, [])
with self.self.assertLogs(level='INFO') as cm:
min_coins(121, [-1])
self.assertEqual(cm.output,
['ERROR: bozo sent us a negative coin value'])
$ less tests.py
class Tests(unittest.TestCase):
def test_min_coin_bigendian(self):
self.assertEqual(min_coins(121, [5, 2, 1]), {5: 24, 1: 1})
def test_min_coin_unnecessary(self):
self.assertEqual(min_coins(121, [5, 2, 1]), {1: 1, 5: 24})
def test_min_coin_littlendian(self):
self.assertEqual(min_coins(121, [1, 2, 5]), {1: 1, 5: 24})
if __name__ == '__main__':
unittest.main()
$ ./tests.py
.F.
======================================================================
FAIL: test_min_coin_littlendian (__main__.Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./tests.py", line 17, in test_min_coin_littlendian
self.assertEqual(min_coins(121, [1, 2, 5]), {1: 1, 5: 24})
AssertionError: {1: 121} != {1: 1, 5: 24}
- {1: 121}
+ {1: 1, 5: 24}
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
$ echo $?
1
$ ./tests.py -v
test_min_coin_bigendian (__main__.Tests) ... ok
test_min_coin_littlendian (__main__.Tests) ... FAIL
test_min_coin_unnecessary (__main__.Tests) ... ok
======================================================================
FAIL: test_min_coin_littlendian (__main__.Tests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "./tests.py", line 17, in test_min_coin_littlendian
self.assertEqual(min_coins(121, [1, 2, 5]), {1: 1, 5: 24})
AssertionError: {1: 121} != {1: 1, 5: 24}
- {1: 121}
+ {1: 1, 5: 24}
----------------------------------------------------------------------
Ran 3 tests in 0.002s
FAILED (failures=1)
$ less tests.py
class Tests(unittest.TestCase):
def test_coins(self):
test_data = [
[121, [1, 2, 5], {1: 1, 5: 24}],
[121, [5, 1, 2], {1: 1, 5: 24}],
[121, [5, 1, 2], {1: 1, 5: 24}],
[24, [7, 3], {7: 3, 3: 1}],
[24, [3, 7], {7: 3, 3: 1}],
[25, [4, 2], None]
]
for data in test_data:
self.assertEqual(
min_coins(data[0], data[1]),
data[2]
)
$ cat testdata/min_coins.yml
coin_tests:
- total: 121
coins: [1, 2, 5]
expected:
1: 1
5: 24
- total: 121
coins: [5, 1, 2]
expected:
1: 1
5: 24
- total: 24
coins: [7, 3]
expected:
7: 3
3: 14
class Tests(unittest.TestCase):
def test_coins(self):
with open(os.path.join(
os.path.dirname(__file__), 'testdata', 'min_coins.yml'),
'r'
) as _:
self.test_data = yaml.safe_load(_)
for data in self.test_data['coin_tests']:
self.assertEqual(
min_coins(data['total'], data['coins']),
data['expected']
)
Useful to
class Tests(unittest.TestCase):
def setUp(self):
with open(os.path.join(
os.path.dirname(__file__), 'testdata', 'min_coins.yml'),
'r'
) as _:
self.test_data = yaml.safe_load(_)
def test_coins(self):
for data in self.test_data['coin_tests']:
self.assertEqual(
min_coins(data['total'], data['coins']),
data['expected']
)
def test_dice(self):
data = self.test_data['dice_tests']
...
$ get-hr-data name,start_date,end_date Alice Anderson,2023-04-01, Bob Brown,2018-03-15,2022-01-01 Charlie Chapman,2021-04-07,2022-02-02 Diana Doyle,2017-05-10, Evan Evans,2022-04-15, Fiona Fisher,2020-07-20,2023-01-01 George Green,2019-04-10, Holly Hunt,2015-12-05, Ivan Ivanov,2021-04-01, Julia James,2010-06-30, Kevin King,2016-08-25, Lily Long,2019-04-01, Molly Morris,2015-04-20, Nancy Newman,2020-02-28, Oliver Owens,2017-04-30,
def say_happy_anniversary():
now = datetime.now()
proc = subprocess.run(
['get-hr-data'], capture_output=True, text=True
)
reader = csv.DictReader(proc.stdout.splitlines())
for row in reader:
start = datetime.strptime(
row['start_date'], '%Y-%m-%d'
)
if start.month == now.month and start.year < now.year:
print(f"Happy anniversary, {row['name']}!")
$ python3 happy_anniversary.py
Happy anniversary, Alice Anderson!
Happy anniversary, Charlie Chapman!
Happy anniversary, Evan Evans!
Happy anniversary, George Green!
...
def say_happy_anniversary():
for name in get_anniversary_people():
print(f"Happy anniversary, {name}!")
def get_anniversary_people():
now = datetime.now()
proc = subprocess.run(
['get-hr-data'], capture_output=True, text=True
)
reader = csv.DictReader(proc.stdout.splitlines())
names = []
for row in reader:
start = datetime.strptime(
row['start_date'], '%Y-%m-%d'
)
if start.month == now.month and start.year < now.year:
names.append(row['name'])
return names
class Tests(unittest.TestCase):
def test_anniversary(self):
expected = ['Alice Anderson', 'Charlie Chapman', 'Evan Evans',
'George Green', 'Ivan Ivanov', 'Lily Long', 'Molly Morris',
'Oliver Owens']
self.assertEqual(
happy_anniversary.get_anniversary_people(),
expected,
msg="get_anniversary_people has a bug, oh noes!"
)
$ ./happy_anniversary_tests.py
Ran 1 test in 0.004s
OK
subprocess in unit tests$ faketime 2024-05-01 ./happy_anniversary_tests.py First differing element 0: 'Diana Doyle' 'Alice Anderson' Second list contains 7 additional elements. First extra element 1: 'Charlie Chapman' - ['Diana Doyle'] + ['Alice Anderson', + 'Charlie Chapman', + 'Evan Evans', + 'George Green', + 'Ivan Ivanov', + 'Lily Long', + 'Molly Morris', + 'Oliver Owens'] : get_anniversary_people has a bug, oh noes!
from unittest.mock import patch, MagicMock
class Tests(unittest.TestCase):
def setUp(self):
with open(os.path.join(
os.path.dirname(__file__), 'testdata', 'hrdata.csv'), 'r'
) as _:
self.hrdata = _.read()
...
from unittest.mock import patch, MagicMock
class Tests(unittest.TestCase):
...
@patch('happy_anniversary.subprocess')
def test_anniversary(self, mock_subprocess):
mock_proc = MagicMock()
mock_proc.stdout = self.hrdata
mock_subprocess.run.return_value = mock_proc
expected = ['Alice Anderson', 'Charlie Chapman', 'Evan Evans',
...
$ ./happy_anniversary_tests.py
Ran 1 test in 0.004s
OK
from unittest.mock import patch, MagicMock
...
@patch('happy_anniversary.subprocess')
@patch('happy_anniversary.datetime.now')
def test_anniversary(self, mock_datetime, mock_subprocess):
mock_datetime.now.return_value = datetime(2024, 4, 19)
mock_proc = MagicMock()
mock_proc.stdout = self.hrdata
mock_subprocess.run.return_value = mock_proc
...
from unittest.mock import patch, MagicMock
...
@patch('happy_anniversary.subprocess')
@patch('happy_anniversary.datetime.now')
def test_anniversary(self, mock_datetime, mock_subprocess):
mock_datetime.now.return_value = datetime(2024, 4, 19)
mock_proc = MagicMock()
mock_proc.stdout = self.hrdata
mock_subprocess.run.return_value = mock_proc
...
$ ./happy_anniversary_tests.py
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/lib/python3.8/unittest/mock.py", line 1490, in __enter__
TypeError: can't set attrs of built-in type 'datetime.datetime'
Many unittest.mock examples at https://docs.python.org/3/library/unittest.mock-examples.html
@patch('module_under_test.date')
def funcname(self, mock_date):
mock_date.today.return_value = date(2024, 4, 19)
mock_date.side_effect = lambda *args, **kw: date(*args, **kw)
class HappyAnniversary():
def hrdata(self):
print("I AM RUNNING HRDATA()")
proc = subprocess.run(['get-hr-data'], capture_output=True, text=True)
return proc.stdout.splitlines()
def now(self):
print("I AM RUNNING NOW()")
return datetime.now()
def get_anniversary_people(self):
now = self.now()
reader = csv.DictReader(self.hrdata())
names = []
for row in reader:
start = datetime.strptime(
row['start_date'], '%Y-%m-%d')
if (start.month == now.month and start.year < now.year):
names.append(row['name'])
return names
def say_happy_anniversary(self):
for name in self.get_anniversary_people():
print(f"Happy anniversary, {name}!")
if __name__ == "__main__":
ha = HappyAnniversary()
ha.say_happy_anniversary()
$ ./happy_anniversary.py
I AM RUNNING NOW()
I AM RUNNING HRDATA()
Happy anniversary, Alice Anderson!
Happy anniversary, Charlie Chapman!
Happy anniversary, Evan Evans!
Happy anniversary, George Green!
Happy anniversary, Ivan Ivanov!
Happy anniversary, Lily Long!
Happy anniversary, Molly Morris!
Happy anniversary, Oliver Owens!
#!/usr/bin/env python3
import os
import unittest
import happy_anniversary
class Tests(unittest.TestCase):
def setUp(self):
with open(os.path.join(
os.path.dirname(__file__), 'testdata', 'hrdata.csv'), 'r'
) as _:
self.hrdata = _.read()
self.ha = happy_anniversary.HappyAnniversary()
self.ha.now = lambda: datetime(2024, 5, 1)
self.ha.hrdata = lambda: self.hrdata.splitlines()
def test_anniversary(self):
expected = ['Diana Doyle']
self.assertEqual(
self.ha.get_anniversary_people(),
expected
)
if __name__ == '__main__':
unittest.main()
$ ./tests.py . ---------------------------------------- Ran 1 test in 0.001s
| Personal | Work |
|---|---|
| Bri Hatch bri@ifokr.org |
Bri Hatch |
Copyright 2024, Bri Hatch, Creative Commons BY-NC-SA License