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