Python and Viv · Tests
Tests
What to notice
- A Viv
test "name" { ... }block is a test function and its runner in one: it runs where it stands, and printsPASS name, orFAIL namewith the first failing assertion's detail. A failure makes the process exit nonzero. - The Python side writes the runner itself, in a few lines, so the two print the same verdicts. pytest or unittest would find the
test_functions and print their own report instead. - A test name in Viv is a string with spaces; in Python it is a function name.
assert condisassert_true(cond), andmath.isclose(a, b, abs_tol=t)isassert_near(a, b, t).assert_near,assert_lt,assert_gtandassert_allcloseprint the numbers they compared when they fail.all(x > 0 for x in w)works in Viv too;all(w > 0)is the array form: the comparison makes an array of 1.0 and 0.0 andallreads it.normalizeis one array expression in Viv,xs / sum(xs), where the Python list needs a comprehension.- A top-level
assert_trueoutside any test prints its own line; the pairs on this site put their checks in test blocks.
Python test_blocks.py · 48 lines
Viv test_blocks.viv · 48 lines
py2viv refuses test_blocks.py
1 imports
4import math
2# nothing to import: test blocks and the assertions are part of the language
py2viv refuses this program:
test_blocks.py:40:9: not numeric Python: a list mixing func
TESTS = [test_median_of_an_odd_count, test_median_of_an_even_count,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Viv arrays hold numbers of one kind.
test_blocks.py:42:10: not numeric Python: iterating over a value of unknown type
for t in TESTS:
^^^^^
Loop over `range(len(a))` and index.
2 constructs outside the numeric subset (tools/py2viv/SUBSET.md) 2 the code under test
7def median(xs):
8 s = sorted(xs)
9 n = len(s)
10 mid = n // 2
11 if n % 2 == 1:
12 return s[mid]
13 return 0.5 * (s[mid - 1] + s[mid])
14
15def normalize(xs):
16 total = sum(xs)
17 return [x / total for x in xs]
5def median(xs) {
6 s = copy(xs)
7 sort(s)
8 n = len(s)
9 mid = n // 2
10 if n % 2 == 1 {
11 return s[mid]
12 }
13 return 0.5 * (s[mid - 1] + s[mid])
14}
15
16def normalize(xs) {
17 return xs / sum(xs)
18}
(refused)
3 one test per behavior
20def test_median_of_an_odd_count():
21 assert median([3.0, 1.0, 2.0]) == 2.0
22
23def test_median_of_an_even_count():
24 assert median([4.0, 1.0, 3.0, 2.0]) == 2.5
25
26def test_normalized_weights_sum_to_one():
27 w = normalize([0.1, 0.2, 0.3])
28 assert math.isclose(sum(w), 1.0, abs_tol=1e-15)
29 assert all(x > 0 for x in w)
30
31def test_a_list_may_end_with_a_comma():
32 xs = [
33 1.0,
34 2.0,
35 3.0,
36 ]
37 assert len(xs) == 3
21test "median of an odd count" {
22 assert_true(median([3.0, 1.0, 2.0]) == 2.0)
23}
24
25test "median of an even count" {
26 assert_true(median([4.0, 1.0, 3.0, 2.0]) == 2.5)
27}
28
29test "normalized weights sum to one" {
30 w = normalize([0.1, 0.2, 0.3])
31 assert_near(sum(w), 1.0, 1e-15)
32 assert_true(all(w > 0))
33}
34
35test "a list may end with a comma" {
36 xs = [
37 1.0,
38 2.0,
39 3.0,
40 ]
41 assert_true(len(xs) == 3)
42}
(refused)
4 run them and report
40TESTS = [test_median_of_an_odd_count, test_median_of_an_even_count,
41 test_normalized_weights_sum_to_one, test_a_list_may_end_with_a_comma]
42for t in TESTS:
43 name = t.__name__.removeprefix("test_").replace("_", " ")
44 try:
45 t()
46 print("PASS", name)
47 except AssertionError:
48 print("FAIL", name)
45# nothing to write: each test block runs where it stands and prints PASS or FAIL
(refused)
What they print
identical — the two programs print the same text.
$ python3 test_blocks.py
PASS median of an odd countPASS median of an even countPASS normalized weights sum to onePASS a list may end with a comma
$ viv run test_blocks.viv
PASS median of an odd countPASS median of an even countPASS normalized weights sum to onePASS a list may end with a comma