Skip to content

Commit 9708204

Browse files
committed
Add material on testing
1 parent 2bc20a8 commit 9708204

File tree

8 files changed

+168
-0
lines changed

8 files changed

+168
-0
lines changed

source-code/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ was used to develop it.
66

77
## What is it?
88

9+
1. `code-testing`: illustrations of using assertions and unit tests.
910
1. `context-manager`: illustrates how to write your own context managers.
1011
1. `coroutines`: illustrates how to write coroutines in Python.
1112
1. `decorators`: illustrates how to write decorators in Python.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Asserts
2+
3+
`assert` is a Python statement, so it not part of unit testing per se.
4+
It is mainly intended for contract-based programming and one can prevent
5+
code to be emitted for asserts by using the `-O` optimizatino flag when
6+
running the code.
7+
8+
## What is it?
9+
1. `fac.py`: implements factorial function, checking whether the
10+
argument is of type integer, and positive using asserts.
11+
1. `run.sh`: Bash shell script to run the Python code, once with, and once
12+
without code generated for assertions.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env python
2+
3+
def fac(n):
4+
'''compute the factorial of given number'''
5+
assert type(n) == int, 'argument must be integer'
6+
assert n >= 0, 'argument must be positive'
7+
if n > 1:
8+
return n*fac(n - 1)
9+
else:
10+
return 1
11+
12+
if __name__ == '__main__':
13+
for i in range(5):
14+
print('{0}! = {1}'.format(i, fac(i)))
15+
for i in [-2, 0.3, 3.0]:
16+
try:
17+
print('{0}! = {1}'.format(i, fac(i)))
18+
except AssertionError as error:
19+
print('{0}! failed: "{1}"'.format(i, error))
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/bin/bash
2+
3+
echo '# run with assertions enabled (default):'
4+
python ./fac.py
5+
echo '# --------------------'
6+
echo '# run with assertions disabled (-O optimization flag):'
7+
python -O ./fac.py

source-code/code-testing/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
CodeTesting
2+
===========
3+
4+
Python's standard library contains the `unittest` module, which allows
5+
to easily implement unit testing, i.e., it is a framework for setting
6+
up code for regression testing while developing, modifying, or refactoring
7+
a code base.
8+
9+
What is it?
10+
-----------
11+
1. `constant_db.py`: implements a class that creates a database containing
12+
constants (mathematical or fysical), and defines a number of methods
13+
to query it.
14+
1. `constant_db_test.py`: `unittest.TestCase` extension that implements a
15+
number of tests for the database. The database is set up in the
16+
`setUp` method, and closed and removed in the `tearDown` method.
17+
Various tests are implemented to illustrate some of the assert methods
18+
defined by `unittest.TestCase`.
19+
1. `run_test.sh`: shell script that actually runs the test code, and that
20+
uses discovery to minimize maintenance of test code.
21+
1. `Asserts`: `assert` is a Python statement, so it not part of unit
22+
testing per se. It is mainly intended for contract-based programming
23+
and one can prevent code to be emitted for asserts by using the
24+
`-O` optimizatino flag when running the code.
25+
26+
Note
27+
----
28+
The unit test that checks the number of entries in the database should
29+
fail to illustrate the output. Although there are only three constants
30+
defined in the database, `pi` was added twice, so the total number of
31+
rows is 4, not 3.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import sqlite3
2+
3+
4+
class UnknownConstantError(Exception):
5+
pass
6+
7+
8+
class ConstantDb(object):
9+
10+
def __init__(self, file_name='const.db'):
11+
self._conn = sqlite3.connect(file_name)
12+
13+
def close(self):
14+
self._conn.close()
15+
16+
def init(self):
17+
constants = {
18+
'g': (9.81, 'm/s**2'),
19+
'pi': (3.14, None),
20+
'mach': (0.00338, 'm/s'),
21+
}
22+
cursor = self._conn.cursor()
23+
cursor.execute('''CREATE TABLE constants (
24+
name TEXT NOT NULL,
25+
value REAL NOT NULL,
26+
unit TEXT
27+
)''')
28+
for name, data in constants.items():
29+
cursor.execute('''INSERT INTO constants
30+
(name, value, unit) VALUES (?, ?, ?)''',
31+
(name, data[0], data[1]))
32+
cursor.execute('''INSERT INTO constants
33+
(name, value, unit) VALUES (?, ?, ?)''',
34+
('pi', 3.1415, None))
35+
self._conn.commit()
36+
cursor.close()
37+
38+
def get_value(self, name):
39+
cursor = self._conn.cursor()
40+
cursor.execute('''SELECT value FROM constants
41+
WHERE name = ?''', (name, ))
42+
rows = cursor.fetchall()
43+
cursor.close()
44+
if not rows:
45+
msg = "constant '{0}' is undefined".format(name)
46+
raise UnknownConstantError(msg)
47+
else:
48+
return rows[0][0]
49+
50+
def get_names(self):
51+
cursor = self._conn.cursor()
52+
cursor.execute('''SELECT name FROM constants''')
53+
rows = cursor.fetchall()
54+
cursor.close()
55+
return [row[0] for row in rows]
56+
57+
def get_nr_constants(self):
58+
return len(self.get_names())
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import os
2+
import unittest
3+
4+
from constant_db import ConstantDb, UnknownConstantError
5+
6+
7+
class ConstantDbTest(unittest.TestCase):
8+
9+
def setUp(self):
10+
self.file_name = 'test.db'
11+
self.db = ConstantDb(self.file_name)
12+
self.db.init()
13+
14+
def test_g_value(self):
15+
expected_value = 9.81
16+
value = self.db.get_value('g')
17+
self.assertAlmostEqual(expected_value, value, 1.0e-4)
18+
19+
def test_constant_names(self):
20+
expected_names = {'g', 'pi', 'mach'}
21+
names = self.db.get_names()
22+
for name in names:
23+
self.assertIn(name, expected_names)
24+
25+
def test_constant_unknown(self):
26+
self.assertRaises(UnknownConstantError, self.db.get_value, 'c')
27+
28+
def test_nr_constants(self):
29+
expected_nr_constants = 3
30+
nr_constats = self.db.get_nr_constants()
31+
self.assertEquals(expected_nr_constants, nr_constats,
32+
'This test is supposed to fail by way '
33+
'of illustration')
34+
35+
def tearDown(self):
36+
self.db.close()
37+
os.remove(self.file_name)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash -l
2+
3+
python -m unittest discover -p '*_test.py'

0 commit comments

Comments
 (0)