Boxes v0.16

In the previous lesson we created a test with some basic behavior we expected, it failed, we fixed the code, and it passed. Nice!

We specifically tested the part in justify_row that handles lines without any spaces or hyphens. Let's add more tests to see if justify_row does the right thing. This is just thinking behavior and creating tests for it.

Let's try some very basic behavior we want when there are spaces:

If we feed "aaaaa aaaaa" to justify_row we expect to have the row fully justified, and the space in the middle to have grown a lot.

30def test_justify_with_spaces():
31    """Test a simple use case with spaces."""
32    row = [boxes.Box(x=i, w=1, h=1, letter="a") for i in range(10)]
33    row[5].letter = " "
34    row[5].stretchy = True
35    page = boxes.Box(w=50, h=50)
36    separation = .1
37
38    assert len(row) == 10
39    assert row[-1].x + row[-1].w == 10
40
41    boxes.justify_row(row, page, separation)
42
43    # Should have the same number of elements
44    assert len(row) == 10
45    # The first element should be flushed-left
46    assert row[0].x == page.x
47    # The last element should be flushed-right
48    # Use approx() because floating point adds a tiny error here
49    assert pytest.approx(row[-1].x + row[-1].w) == page.x + page.w
50    # The element in position 5 must have absorbed all the slack
51    # So is 1 (it's width) + 40 (slack) units wide
52    assert row[5].w == 41

We can tell pytest to run only this specific test:

 1$ env PYTHONPATH=. pytest tests/test_justify_row.py::test_justify_with_spaces
 2
 3============================= test session starts ==============================
 4platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
 5rootdir: code/lesson5, inifile:
 6collected 1 item
 7
 8tests/test_justify_row.py .                                               [100%]
 9
10=========================== 1 passed in 0.24 seconds ===========================

Nice!

How about other behaviors? Our function removes spaces from the right:

66    # Remove all right-margin spaces
67    while row[-1].letter == " ":
68        row.pop()

Let's do a test to make sure we do that!

76def test_justify_trailing_spaces():
77    """Test a use case with traling spaces."""
78    row = [boxes.Box(x=i, w=1, h=1, letter="a") for i in range(10)]
79    row[-1].letter = " "
80    row[-2].letter = " "
81    page = boxes.Box(w=50, h=50)
82    separation = .1
83
84    assert len(row) == 10
85    assert row[-1].x + row[-1].w == 10
86
87    boxes.justify_row(row, page, separation)
88
89    # Should have lost the 2 trailing spaces
90    assert len(row) == 8
91    # The first element should be flushed-left
92    assert row[0].x == page.x
93    # The last element should be flushed-right
94    assert row[-1].x + row[-1].w == page.x + page.w

That works too!

How about lines with a newline character at the end? If we have "aaa aaa aaa\n" then that should not actually be justified, right?

56def test_justify_ends_with_newline():
57    """Test a use case with a newline."""
58    row = [boxes.Box(x=i, w=1, h=1, letter="a") for i in range(10)]
59    row[-1].letter = "\n"
60    page = boxes.Box(w=50, h=50)
61    separation = .1
62
63    assert len(row) == 10
64    assert row[-1].x + row[-1].w == 10
65
66    boxes.justify_row(row, page, separation)
67
68    # Should have the same number of elements
69    assert len(row) == 10
70    # The first element should be flushed-left
71    assert row[0].x == page.x
72    # The last element should NOT be flushed-right
73    assert row[-1].x + row[-1].w == 10
 1$ env PYTHONPATH=. pytest tests/test_justify_row.py::test_justify_ends_with_newline
 2
 3[skipping]
 4
 5        # The last element should NOT be flushed-right
 6>       assert row[-1].x + row[-1].w == 10
 7E       assert (49.0 + 1) == 10
 8E        +  where 49.0 = Box(49.0, 0, 1, 0, "\n").x
 9E        +  and   1 = Box(49.0, 0, 1, 0, "\n").w
10
11tests/test_justify_row.py:68: AssertionError

And it fails. That is not surprising because if you look at our implementation of justify_row() it doesn't check for newlines at all! That is because we are handling that case in layout() instead. This is arguably correct. On the other hand, if we want to encapsulate the justification of a row in this function it makes sense to move that here.

At this point, however, that is not implemented but it's not really a bug because it is handled. So, we can mark this test as an "expected failure" and put a pin in it so we can come back to it. We mark it as expected failure using the pytest.mark.xfail decorator, and add a FIXME comment to remember to come back.

55@pytest.mark.xfail  # FIXME: justify doesn't handle newlines yet!
56def test_justify_ends_with_newline():
57    """Test a use case with a newline."""

The other thing this function does is put everything in an actual row considering "separation":

82        # And we put each thing next to the previous one
83        for j, b in enumerate(row[1:], 1):
84            b.x = row[j - 1].x + row[j - 1].w + separation

Let's test if it does indeed do that!

97def test_justify_puts_things_in_a_row():
98    """Test a simple use case with spaces."""
99    row = [boxes.Box(x=i, w=1, h=1, letter="a") for i in range(10)]
100    row[5].letter = " "
101    row[5].stretchy = True
102    page = boxes.Box(w=50, h=50)
103    separation = .1
104
105    assert len(row) == 10
106    assert row[-1].x + row[-1].w == 10
107
108    boxes.justify_row(row, page, separation)
109
110    # Should have the same number of elements
111    assert len(row) == 10
112    # The first element should be flushed-left
113    assert row[0].x == page.x
114    # The last element should be flushed-right
115    # Use approx() because floating point adds a tiny error here
116    assert pytest.approx(row[-1].x + row[-1].w) == page.x + page.w
117    # All elements should be separated correctly.
118    separations = [
119        separation - (row[i].x - (row[i - 1].x + row[i - 1].w))
120        for i in range(1, len(row))
121    ]
122    # Again, floating point is inaccurate
123    assert max(separations) < 0.00001
124    assert min(separations) > -0.00001

And we finish running our whole test suite and see what happens.

1$ env PYTHONPATH=. pytest
2============================= test session starts ==============================
3platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
4rootdir: part2/code/lesson5, inifile:
5collected 5 items
6
7tests/test_justify_row.py ..x..                                           [100%]
8
9===================== 4 passed, 1 xfailed in 0.37 seconds ======================

Notice how we have 4 passing tests and an xfail.

What we have been working on is called "test coverage". We now have tests that cover the expected behavior of justify_row. That means that when, in the next lesson, we change that behavior, we can be fairly sure we know how we are changing it.


results matching ""

    No results matching ""