├── pytest.pdf ├── Integer ├── integr.py └── test │ └── test_integr.py ├── test_block_unit.py ├── block ├── test_block │ └── test_block.py └── block.py └── README.txt /pytest.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattharrison/pytest-mar-21-2018/master/pytest.pdf -------------------------------------------------------------------------------- /Integer/integr.py: -------------------------------------------------------------------------------- 1 | def parse(data): 2 | res = [] 3 | for thing in data.split(','): 4 | res.append(int(thing)) 5 | return res 6 | -------------------------------------------------------------------------------- /test_block_unit.py: -------------------------------------------------------------------------------- 1 | from block import Amount 2 | 3 | import unittest 4 | 5 | 6 | class TestAmount(unittest.TestCase): 7 | def test_todict(self): 8 | a = Amount('matt', 5) 9 | res = a.todict() 10 | self.assertEqual(res, 11 | {'uuid': 'matt', 'amount': 5}) 12 | 13 | 14 | if __name__ == '__main__': 15 | unittest.main() 16 | -------------------------------------------------------------------------------- /Integer/test/test_integr.py: -------------------------------------------------------------------------------- 1 | from integr import parse 2 | 3 | import pytest 4 | 5 | 6 | def test_basic(): 7 | data = '1,3,5,9' 8 | res = parse(data) 9 | print(f'Res {res}') 10 | assert res == [0, 3, 5, 9] 11 | 12 | 13 | def test_bad1(): 14 | with pytest.raises(ValueError): 15 | parse('bad input') 16 | 17 | 18 | @pytest.mark.xfail(raises=ValueError) 19 | def test_bad2(): 20 | parse('1-3,12-15') 21 | 22 | 23 | if __name__ == '__main__': 24 | pytest.main() 25 | -------------------------------------------------------------------------------- /block/test_block/test_block.py: -------------------------------------------------------------------------------- 1 | from block import Amount, Block, Node, Transaction 2 | 3 | 4 | def test_amount_todict(): 5 | a = Amount('matt', 5) 6 | res = a.todict() 7 | assert res == {'uuid': 'matt', 'amount': 5} 8 | 9 | 10 | def test_transaction(): 11 | t = Transaction([Amount('matt', 5)], 12 | [Amount('matt', 1), 13 | Amount('fred', 4)]) 14 | assert t.todict() == {'inputs': [{'amount': 5, 'uuid': 'matt'}], 15 | 'outputs': [{'amount': 1, 'uuid': 'matt'}, {'amount': 4, 'uuid': 'fred'}]} 16 | 17 | 18 | def test_block(): 19 | b = Block([], '') 20 | assert b.todict() == {'body': 21 | {'txns': []}, 22 | 'header': {'body_hash': '5c12d30d9ba5ddf3b2ba5ae8bf652b902fb005f91e2e0877269b5a9cec975052', 'nonce': None, 'prev_hash': ''}} 23 | 24 | 25 | def test_txn(): 26 | node = Node('matt') 27 | gb, hash = node.process_txns([]) 28 | assert hash.startswith('0') 29 | 30 | 31 | if __name__ == '__main__': 32 | import pytest 33 | pytest.main() 34 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | ===================== 2 | Pytest Introduction 3 | ===================== 4 | 5 | Copyright 2018 - Matt Harrison 6 | 7 | @__mharrison__ 8 | 9 | http://bit.ly/pytest-mar-21-2018 10 | 11 | Assignment 1 12 | ============ 13 | 14 | * Install pytest in a virtual environment 15 | * Run:: 16 | 17 | pytest -h 18 | 19 | 20 | Assignment 2 21 | ============ 22 | 23 | * Create a directory/module ``Integer/integr.py`` 24 | (note spelling). 25 | 26 | * Create a function, ``parse``, that accepts a 27 | string of the form ``"1,3,5,9"`` that returns 28 | a list of integers (``[1, 3, 5, 9]``) 29 | 30 | * Create a test directory and test file ``Integer/test/test_integr.py`` 31 | 32 | * Create a test function, ``test_basic`` that 33 | asserts that ``integr.parse`` works with the 34 | input ``"1,3,5,9"`` 35 | 36 | * Run pytest on ``test/test_integr.py`` 37 | 38 | 39 | Assignment 3 40 | ============ 41 | 42 | * Create a test function, ``test_bad1`` that 43 | asserts that an error is raised when 44 | ``integr.parse`` is called with ``'bad input'``. 45 | Use a context manager (``with``) 46 | 47 | * Create a test function, ``test_bad2`` that 48 | asserts that an error is raised when 49 | ``integr.parse`` is called with ``'1-3,12-15'``. 50 | Use the ``@pytest.mark.xfail`` decorator. 51 | 52 | 53 | Assignment 4 54 | ============ 55 | 56 | * Add a ``__name__`` check that will run pytest 57 | on ``test/test_integr.py`` if it is executed 58 | with ``python`` 59 | 60 | * Run the command line option to collect the tests 61 | (but not execute them). 62 | 63 | * Run only the ``test_basic`` test from the command line. 64 | 65 | 66 | Assignment 5 67 | ============ 68 | 69 | * Create a doctest on the ``integr.py`` module that 70 | shows an example of running the ``parse`` function. 71 | 72 | * Run the doctest via pytest with a command line option 73 | 74 | * Create a ``pytest.ini`` file. Add an option to the 75 | configuration file to run the doctests when ``pytest`` 76 | is invoked 77 | 78 | * Run the doctest via pytest without a command line option 79 | 80 | 81 | 82 | Assignment 6 83 | ============ 84 | 85 | * Run the tests that have ``bad`` in the name 86 | 87 | * Mark ``test_bad1`` and ``test_bad2`` with the ``wrong`` name. 88 | 89 | * Run only tests that are marked with ``wrong``. 90 | 91 | * Run pytest with ``--strict`` 92 | 93 | * Register ``bad`` as a marker in ``pytest.ini`` 94 | 95 | * Run pytest with ``--strict`` 96 | 97 | Assignment 7 98 | ============ 99 | 100 | * Make a new test, ``test_good``, that is parameterized 101 | to check that ``'1,2,3'`` and ``'9,8,1'`` both work. 102 | 103 | * Make a new test, ``test_fail``, that is parameterized 104 | to check that ``''``, ``None``, and ``[]`` fail. 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /block/block.py: -------------------------------------------------------------------------------- 1 | """ 2 | Basic blockchain implementation 3 | 4 | http://bitcoin.org/bitcoin.pdf 5 | 6 | * Coin - chain of digital signatures 7 | * Chain - sequence of hashes. Hash(block of items, prev hash) 8 | * Block - header(prev hash, nonce, hash), transaction 9 | * Proof of work - Create value when hash starts with 10 | a certain number of zeros. 11 | * Nodes - Each node collects transactions, then node 12 | mines the block. Each node tries to find POW, and broadcast 13 | it. Nodes accepts the block by working on the next block. 14 | * Transaction - inputs and outputs. Normally 2 outputs. 15 | 16 | >>> a = Amount('matt', 5) 17 | >>> a.todict() 18 | {'uuid': 'matt', 'amount': 5} 19 | 20 | """ 21 | import hashlib 22 | import json 23 | 24 | # this is a comment 25 | MINING_COST = 1 26 | 27 | 28 | class Amount: 29 | def __init__(self, uuid, amount): 30 | self.uuid = uuid 31 | self.amount = amount 32 | 33 | def todict(self): 34 | return {'uuid': self.uuid, 'amount': self.amount} 35 | 36 | 37 | class Transaction: 38 | def __init__(self, inputs, outputs): 39 | self.inputs = inputs 40 | self.outputs = outputs 41 | 42 | def todict(self): 43 | return {'inputs': [i.todict() for i in self.inputs], 44 | 'outputs': [o.todict() for o in self.outputs]} 45 | 46 | 47 | class Block: 48 | def __init__(self, txns, prev_hash): 49 | self.prev_hash = prev_hash 50 | self.txns = txns 51 | self.nonce = None 52 | 53 | def todict(self, nonce=None): 54 | nonce = nonce if nonce is not None else self.nonce 55 | body = {'txns': [txns.todict() for txns in self.txns]} 56 | headers = {'prev_hash': self.prev_hash, 57 | 'body_hash': get_hash(body), 58 | 'nonce': nonce} 59 | data = {'header': headers, 60 | 'body': body} 61 | return data 62 | 63 | def get_hash(self, nonce): 64 | return get_hash(self.todict(nonce)) 65 | 66 | 67 | def get_hash(dict_data): 68 | sha = hashlib.sha256() 69 | data = json.dumps(dict_data, sort_keys=True) 70 | sha.update(data.encode('utf-8')) 71 | return sha.hexdigest() 72 | 73 | 74 | class Node: 75 | def __init__(self, uuid): 76 | self.uuid = uuid 77 | self.blocks = [] 78 | 79 | def process_txns(self, txns, difficulty=1): 80 | txns.insert(0, Transaction([], [Amount(self.uuid, 81 | MINING_COST)])) 82 | if self.blocks: 83 | prev_hash = self.blocks[-1].prev_hash 84 | else: 85 | prev_hash = '' 86 | block = Block(txns, prev_hash) 87 | nonce = 0 88 | while True: 89 | hash = block.get_hash(nonce) 90 | if hash.startswith('0'*difficulty): 91 | block.nonce = nonce 92 | self.blocks.append(block) 93 | return block, hash 94 | nonce += 1 95 | 96 | 97 | 98 | 99 | if __name__ == '__main__': 100 | import doctest 101 | doctest.testmod() 102 | --------------------------------------------------------------------------------