test traffic_light with list comprehensions
I want to use for loops in the Traffic Light project. It has for a Traffic Light that changes color based on what RED phase the traffic is in ('cross' or 'parallel') AND if a timer is done or NOT done
The outputs are the next lights for Parallel and Cross Traffic which gave me this truth table
red |
current |
current |
next |
next |
|
|---|---|---|---|---|---|
phase |
parallel |
cross |
timer |
parallel |
cross |
‘cross’ |
GREEN |
RED |
NOT done |
GREEN |
RED |
‘cross’ |
GREEN |
RED |
done |
YELLOW |
RED |
‘cross’ |
YELLOW |
RED |
NOT done |
YELLOW |
RED |
‘cross’ |
YELLOW |
RED |
done |
safety RED |
safety RED |
‘cross’ |
RED |
RED |
NOT done |
safety RED |
safety RED |
‘cross’ |
RED |
RED |
done |
RED |
GREEN |
red |
current |
current |
next |
next |
|
|---|---|---|---|---|---|
phase |
parallel |
cross |
timer |
parallel |
cross |
‘parallel’ |
RED |
GREEN |
NOT done |
RED |
GREEN |
‘parallel’ |
RED |
GREEN |
done |
RED |
YELLOW |
‘parallel’ |
RED |
YELLOW |
NOT done |
RED |
YELLOW |
‘parallel’ |
RED |
YELLOW |
done |
safety RED |
safety RED |
‘parallel’ |
RED |
RED |
NOT done |
safety RED |
safety RED |
‘parallel’ |
RED |
RED |
done |
GREEN |
RED |
It also makes sure that there is never a case where the lights allow cars to move through the intersection at the same time (to try to avoid accidents).
preview
These are the tests I have at the end of the chapter
1import src.traffic_light
2import unittest
3
4
5GREEN, YELLOW, RED = 'GREEN', 'YELLOW', 'RED'
8
9class TestTrafficLight(unittest.TestCase):
10
11 def test_traffic_light_control(self):
12 for row in (
13 ('cross', GREEN, RED, False, (GREEN, RED, WALK)),
14 ('cross', GREEN, RED, True, (YELLOW, RED, NOT_WALK)),
15 ('cross', YELLOW, RED, False, (YELLOW, RED, NOT_WALK)),
16 ('cross', YELLOW, RED, True, (RED, RED, NOT_WALK)),
17 ('cross', RED, RED, False, (RED, RED, NOT_WALK)),
18 ('cross', RED, RED, True, (RED, GREEN, NOT_WALK)),
19 ('parallel', RED, GREEN, False, (RED, GREEN, NOT_WALK)),
20 ('parallel', RED, GREEN, True, (RED, YELLOW, NOT_WALK)),
21 ('parallel', RED, YELLOW, False, (RED, YELLOW, NOT_WALK)),
22 ('parallel', RED, YELLOW, True, (RED, RED, NOT_WALK)),
23 ('parallel', RED, RED, False, (RED, RED, NOT_WALK)),
24 ('parallel', RED, RED, True, (GREEN, RED, WALK)),
25 ):
26 red_phase = row[0]
27 current_parallel = row[1]
28 current_cross = row[2]
29 timer_done = row[3]
30
31 with self.subTest(
32 red_phase=red_phase,
33 current_parallel=current_parallel,
34 current_cross=current_cross,
35 timer_done=timer_done,
36 ):
37 self.assertEqual(
38 src.traffic_light.control(
39 red_phase=red_phase,
40 current_parallel=current_parallel,
41 current_cross=current_cross,
42 timer_done=timer_done,
43 ),
44 row[4]
47 def test_traffic_light_failsafe(self):
48 inputs = (GREEN, YELLOW, 'bad input')
49 for red_phase in ('cross', 'parallel', 'bad input'):
50 for current_parallel in inputs:
51 for current_cross in inputs:
52 for timer_done in (False, True):
53 with self.subTest(
54 red_phase=red_phase,
55 current_parallel=current_parallel,
56 current_cross=current_cross,
57 timer_done=timer_done,
58 ):
59 self.assertEqual(
60 src.traffic_light.control(
61 red_phase=red_phase,
62 current_parallel=current_parallel,
63 current_cross=current_cross,
64 timer_done=timer_done,
65 ),
66 (RED, RED, NOT_WALK)
67 )
68
69
70# Exceptions seen
71# AssertionError
72# NameError
73# AttributeError
74# TypeError
open the project
I open a terminal
I change directory to the traffic_light folder in the
pumping_pythonfolderI open
test_traffic_light.pyfrom thetestsfolderI use pytest-watcher to run the tests automatically
test_traffic_light_control
RED: make it fail
I add a test with a for loop for all the cases from all the tests except test_traffic_light_failsafe
8class TestTrafficLight(unittest.TestCase):
9
10 def test_traffic_light_control(self):
11 for row in (
12 ('cross', GREEN, RED, False, (GREEN, RED)),
13 ('cross', GREEN, RED, True, (YELLOW, RED)),
14 ('cross', YELLOW, RED, False, (YELLOW, RED)),
15 ('cross', YELLOW, RED, True, (RED, RED)),
16 ('cross', RED, RED, False, (RED, RED)),
17 ('cross', RED, RED, True, (RED, GREEN)),
18 ('parallel', RED, GREEN, False, (RED, GREEN)),
19 ('parallel', RED, GREEN, True, (RED, YELLOW)),
20 ('parallel', RED, YELLOW, False, (RED, YELLOW)),
21 ('parallel', RED, YELLOW, True, (RED, RED)),
22 ('parallel', RED, RED, False, (RED, RED)),
23 ('parallel', RED, RED, True, (GREEN, RED)),
24 ):
25 red_phase = row[0]
26 current_parallel = row[1]
27 current_cross = row[2]
28 timer_done = row[3]
30 with self.subTest(
31 red_phase=red_phase,
32 current_parallel=current_parallel,
33 current_cross=current_cross,
34 timer_done=timer_done,
35 ):
36 self.assertEqual(
37 src.traffic_light.control(
38 red_phase=red_phase,
39 current_parallel=current_parallel,
40 current_cross=current_cross,
41 timer_done=timer_done,
42 ),
43 'BOOM!!!'
44 )
45
46 def test_parallel_green_cross_red_timer_not_done(self):
the terminal is my friend, and shows AssertionError
SUBFAILED(
red_phase='cross', current_parallel='GREEN',
current_cross='RED', timer_done=False
) ...AssertionError: ('GREEN', 'RED') != 'BOOM!!!'
SUBFAILED(
red_phase='cross', current_parallel='GREEN',
current_cross='RED', timer_done=True
) ...AssertionError: ('YELLOW', 'RED') != 'BOOM!!!'
SUBFAILED(
red_phase='cross', current_parallel='YELLOW',
current_cross='RED', timer_done=False
) ...AssertionError: ('YELLOW', 'RED') != 'BOOM!!!'
SUBFAILED(
red_phase='cross', current_parallel='YELLOW',
current_cross='RED', timer_done=True
) ...AssertionError: ('RED', 'RED') != 'BOOM!!!'
SUBFAILED(
red_phase='cross', current_parallel='RED',
current_cross='RED', timer_done=False
) ...AssertionError: ('RED', 'RED') != 'BOOM!!!'
SUBFAILED(
red_phase='cross', current_parallel='RED',
current_cross='RED', timer_done=True
) ...AssertionError: ('RED', 'GREEN') != 'BOOM!!!'
SUBFAILED(
red_phase='parallel', current_parallel='RED',
current_cross='GREEN', timer_done=False
) ...AssertionError: ('RED', 'GREEN') != 'BOOM!!!'
SUBFAILED(
red_phase='parallel', current_parallel='RED',
current_cross='GREEN', timer_done=True
) ...AssertionError: ('RED', 'YELLOW') != 'BOOM!!!'
SUBFAILED(
red_phase='parallel', current_parallel='RED',
current_cross='YELLOW', timer_done=False
) ...AssertionError: ('RED', 'YELLOW') != 'BOOM!!!'
SUBFAILED(
red_phase='parallel', current_parallel='RED',
current_cross='YELLOW', timer_done=True
) ...AssertionError: ('RED', 'RED') != 'BOOM!!!'
SUBFAILED(
red_phase='parallel', current_parallel='RED',
current_cross='RED', timer_done=False
) ...AssertionError: ('RED', 'RED') != 'BOOM!!!'
SUBFAILED(
red_phase='parallel', current_parallel='RED',
current_cross='RED', timer_done=True
) ...AssertionError: ('GREEN', 'RED') != 'BOOM!!!'
GREEN: make it pass
I change the expectation of the assertion in test_traffic_light_control
36 self.assertEqual(
37 src.traffic_light.control(
38 red_phase=red_phase,
39 current_parallel=current_parallel,
40 current_cross=current_cross,
41 timer_done=timer_done,
42 ),
43 row[4]
44 )
45
46 def test_parallel_green_cross_red_timer_not_done(self):
the test passes.
REFACTOR: make it better
I leave test_traffic_light_failsafe and remove the other tests because they are now covered by test_traffic_light_control
I add a git commit message
test_traffic_light_failsafe with for loops
RED: make it fail
I add for loops to test_traffic_light_failsafe
46 def test_traffic_light_failsafe(self):
47 inputs = (GREEN, YELLOW, 'bad input')
48 for red_phase in ('cross', 'parallel', 'bad input'):
49 for current_parallel in inputs:
50 for current_cross in inputs:
51 for timer_done in (False, True):
52 with self.subTest(
53 red_phase=red_phase,
54 current_parallel=current_parallel,
55 current_cross=current_cross,
56 timer_done=timer_done,
57 ):
58 self.assertEqual(
59 src.traffic_light.control(
60 red_phase=red_phase,
61 current_parallel=current_parallel,
62 current_cross=current_cross,
63 timer_done=timer_done,
64 ),
65 'BOOM!!!'
66 )
67
68 self.assertEqual(
69 src.traffic_light.control(
70 red_phase='cross',
71 current_parallel='BAP',
72 current_cross=RED,
73 timer_done=False,
74 ),
75 (RED, RED)
76 )
the terminal is my friend, and shows AssertionError for fifty four sub tests.
GREEN: make it pass
I change the expectation of the new assertion in test_traffic_light_failsafe
58 self.assertEqual(
59 src.traffic_light.control(
60 red_phase=red_phase,
61 current_parallel=current_parallel,
62 current_cross=current_cross,
63 timer_done=timer_done,
64 ),
65 (RED, RED)
66 )
67
68 self.assertEqual(
69 src.traffic_light.control(
70 red_phase='cross',
71 current_parallel='BAP',
72 current_cross=RED,
73 timer_done=False,
74 ),
75 (RED, RED)
76 )
the test passes.
REFACTOR: make it better
I remove the other assertions from test_traffic_light_failsafe because they are now covered by the for loops
58 self.assertEqual( 59 src.traffic_light.control( 60 red_phase=red_phase, 61 current_parallel=current_parallel, 62 current_cross=current_cross, 63 timer_done=timer_done, 64 ), 65 (RED, RED) 66 ) 67 68 69# Exceptions seen 70# AssertionError 71# NameError 72# AttributeError 73# TypeErrorI add a git commit message
git commit -am \ 'test_traffic_light_failsafe with for loops'
test traffic light with walk signal
What if the Traffic Light has a walk signal to let people know that it is safe to cross the street, the truth table would then be
red |
current |
current |
next |
next |
walk |
|
|---|---|---|---|---|---|---|
phase |
parallel |
cross |
timer |
parallel |
cross |
|
‘cross’ |
GREEN |
RED |
NOT done |
GREEN |
RED |
WALK |
‘cross’ |
GREEN |
RED |
done |
YELLOW |
RED |
DO NOT WALK |
‘cross’ |
YELLOW |
RED |
NOT done |
YELLOW |
RED |
DO NOT WALK |
‘cross’ |
YELLOW |
RED |
done |
safety RED |
safety RED |
DO NOT WALK |
‘cross’ |
RED |
RED |
NOT done |
safety RED |
safety RED |
DO NOT WALK |
‘cross’ |
RED |
RED |
done |
RED |
GREEN |
DO NOT WALK |
red |
current |
current |
next |
next |
walk |
|
|---|---|---|---|---|---|---|
phase |
parallel |
cross |
timer |
parallel |
cross |
|
‘parallel’ |
RED |
GREEN |
NOT done |
RED |
GREEN |
DO NOT WALK |
‘parallel’ |
RED |
GREEN |
done |
RED |
YELLOW |
DO NOT WALK |
‘parallel’ |
RED |
YELLOW |
NOT done |
RED |
YELLOW |
DO NOT WALK |
‘parallel’ |
RED |
YELLOW |
done |
safety RED |
safety RED |
DO NOT WALK |
‘parallel’ |
RED |
RED |
NOT done |
safety RED |
safety RED |
DO NOT WALK |
‘parallel’ |
RED |
RED |
done |
GREEN |
RED |
WALK |
The Traffic Light only shows WALK if the RED phase is 'parallel' AND the current cross is RED AND the timer is done AND the next cross will stay RED OR if the RED phase is 'cross' AND the current parallel is GREEN AND the timer is NOT done AND the next cross will stay RED which means there will be no car moving across the road the person is crossing.
RED: make it fail
I add global variables for
'WALK'and'DO NOT WALK'I add WALK and NOT_WALK to the expectations of test_traffic_light_control
11 def test_traffic_light_control(self): 12 for row in ( 13 ('cross', GREEN, RED, False, (GREEN, RED, WALK)), 14 ('cross', GREEN, RED, True, (YELLOW, RED, NOT_WALK)), 15 ('cross', YELLOW, RED, False, (YELLOW, RED, NOT_WALK)), 16 ('cross', YELLOW, RED, True, (RED, RED, NOT_WALK)), 17 ('cross', RED, RED, False, (RED, RED, NOT_WALK)), 18 ('cross', RED, RED, True, (RED, GREEN, NOT_WALK)), 19 ('parallel', RED, GREEN, False, (RED, GREEN, NOT_WALK)), 20 ('parallel', RED, GREEN, True, (RED, YELLOW, NOT_WALK)), 21 ('parallel', RED, YELLOW, False, (RED, YELLOW, NOT_WALK)), 22 ('parallel', RED, YELLOW, True, (RED, RED, NOT_WALK)), 23 ('parallel', RED, RED, False, (RED, RED, NOT_WALK)), 24 ('parallel', RED, RED, True, (GREEN, RED, WALK)), 25 ):the terminal is my friend, and shows AssertionError
SUBFAILED( red_phase='cross', current_parallel='GREEN', current_cross='RED', timer_done=False ) ...AssertionError: Tuples differ: ('GREEN', 'RED') != ('GREEN', 'RED', 'WALK') SUBFAILED( red_phase='cross', current_parallel='GREEN', current_cross='RED', timer_done=True ) ...AssertionError: Tuples differ: ('YELLOW', 'RED') != ('YELLOW', 'RED', 'DO NOT WALK') SUBFAILED( red_phase='cross', current_parallel='YELLOW', current_cross='RED', timer_done=False ) ...AssertionError: Tuples differ: ('YELLOW', 'RED') != ('YELLOW', 'RED', 'DO NOT WALK')SUBFAILED( red_phase='cross', current_parallel='YELLOW', current_cross='RED', timer_done=True ) ...AssertionError: Tuples differ: ('RED', 'RED') != ('RED', 'RED', 'DO NOT WALK') SUBFAILED( red_phase='cross', current_parallel='RED', current_cross='RED', timer_done=False ) ...AssertionError: Tuples differ: ('RED', 'RED') != ('RED', 'RED', 'DO NOT WALK') SUBFAILED( red_phase='cross', current_parallel='RED', current_cross='RED', timer_done=True ) ...AssertionError: Tuples differ: ('RED', 'GREEN') != ('RED', 'GREEN', 'DO NOT WALK')SUBFAILED( red_phase='parallel', current_parallel='RED', current_cross='GREEN', timer_done=False ) ...AssertionError: Tuples differ: ('RED', 'GREEN') != ('RED', 'GREEN', 'DO NOT WALK') SUBFAILED( red_phase='parallel', current_parallel='RED', current_cross='GREEN', timer_done=True ) ...AssertionError: Tuples differ: ('RED', 'YELLOW') != ('RED', 'YELLOW', 'DO NOT WALK') SUBFAILED( red_phase='parallel', current_parallel='RED', current_cross='YELLOW', timer_done=False ) ...AssertionError: Tuples differ: ('RED', 'YELLOW') != ('RED', 'YELLOW', 'DO NOT WALK')SUBFAILED( red_phase='parallel', current_parallel='RED', current_cross='YELLOW', timer_done=True ) ...AssertionError: Tuples differ: ('RED', 'RED') != ('RED', 'RED', 'DO NOT WALK') SUBFAILED( red_phase='parallel', current_parallel='RED', current_cross='RED', timer_done=False ) ...AssertionError: Tuples differ: ('RED', 'RED') != ('RED', 'RED', 'DO NOT WALK') SUBFAILED( red_phase='parallel', current_parallel='RED', current_cross='RED', timer_done=True ) ...AssertionError: Tuples differ: ('GREEN', 'RED') != ('GREEN', 'RED', 'WALK')
GREEN: make it pass
I open
__init__.pyfrom the traffic_light folder in thesrcfolderI add global variables for
'WALK'and'DO NOT WALK'insrc/traffic_light/__init__.pyI add WALK and NOT_WALK to the next_light function
26def next_light(red_phase, parallel, cross): 27 if red_phase == 'cross': 28 if parallel == GREEN: 29 return YELLOW, RED, NOT_WALK 30 if parallel == RED: 31 return RED, GREEN, NOT_WALK 32 if red_phase == 'parallel': 33 if cross == GREEN: 34 return RED, YELLOW, NOT_WALK 35 if cross == RED: 36 return GREEN, RED, WALK 37 return RED, RED, NOT_WALK 38 39 40def control( 41 timer_done, red_phase='parallel', 42 current_parallel=RED, current_cross=RED, 43 ):the terminal shows AssertionError for six sub tests.
I add NOT_WALK to the if statement for if the timer is NOT done, in the
controlfunction40def control( 41 timer_done, red_phase='parallel', 42 current_parallel=RED, current_cross=RED, 43 ): 44 if triggers_failsafe(current_parallel, current_cross): 45 return RED, RED 46 47 if not timer_done: 48 return current_parallel, current_cross, NOT_WALK 49 50 if timer_done:the terminal shows AssertionError
SUBFAILED( red_phase='cross', current_parallel='GREEN', current_cross='RED', timer_done=False ) ... AssertionError: Tuples differ: ('GREEN', 'RED', 'DO NOT WALK') != ('GREEN', 'RED', 'WALK')I add an if statement for if the RED phase is
'cross'ANDcurrent_parallelis GREEN toif not timer_done:47 if not timer_done: 48 if red_phase == 'cross' and current_parallel == GREEN: 49 return current_parallel, current_cross, WALK 50 return current_parallel, current_cross, NOT_WALK 51 52 if timer_done:the test passes.
REFACTOR: make it better
I add NOT_WALK to test_traffic_light_failsafe in
tests/test_traffic_light.pybecause I cannot predict what light will be next when such events happen, for example a power failure59 self.assertEqual( 60 src.traffic_light.control( 61 red_phase=red_phase, 62 current_parallel=current_parallel, 63 current_cross=current_cross, 64 timer_done=timer_done, 65 ), 66 (RED, RED, NOT_WALK) 67 ) 68 69 70# Exceptions seenthe terminal shows AssertionError for fifty four sub tests.
I add NOT_WALK to
if triggers_failsafe(current_parallel, current_cross):in thecontrolfunction insrc/traffic_light/__init__.py40def control( 41 timer_done, red_phase='parallel', 42 current_parallel=RED, current_cross=RED, 43 ): 44 if triggers_failsafe(current_parallel, current_cross): 45 return RED, RED, NOT_WALK 46 47 if not timer_done:the test passes.
I add a global variable for
RED, RED, NOT_WALKI use the variable in the default return statement of the next_light function
27def next_light(red_phase, parallel, cross): 28 if red_phase == 'cross': 29 if parallel == GREEN: 30 return YELLOW, RED, NOT_WALK 31 if parallel == RED: 32 return RED, GREEN, NOT_WALK 33 if red_phase == 'parallel': 34 if cross == GREEN: 35 return RED, YELLOW, NOT_WALK 36 if cross == RED: 37 return GREEN, RED, WALK 38 return FAILSAFE 39 40 41def control( 42 timer_done, red_phase='parallel', 43 current_parallel=RED, current_cross=RED, 44 ):the tests are still green.
I use the variable in the
controlfunction40def control( 41 timer_done, red_phase='parallel', 42 current_parallel=RED, current_cross=RED, 43 ): 44 if triggers_failsafe(current_parallel, current_cross): 45 return FAILSAFE 46 47 if not timer_done: 48 if red_phase == 'cross' and current_parallel == GREEN: 49 return current_parallel, current_cross, WALK 50 return current_parallel, current_cross, NOT_WALK 51 52 if timer_done: 53 return next_light( 54 red_phase, current_parallel, current_cross 55 ) 56 57 return FAILSAFEstill green.
I add a git commit message
git commit -am \ 'test traffic_light with walk signal'
close the project
review
I used for loops to make less tests which is simpler and at the same time more complex
I used a tuple to represent all the states of the Traffic Light and its outputs, which is not as easy to read, there has to be a better way
I ran tests for a Traffic Light that changes lights based on what RED phase the traffic is in ('cross' or 'parallel') AND if a timer is done or NOT done
The outputs are the next lights for Parallel and Cross Traffic which gave me this truth table
red |
current |
current |
next |
next |
|
|---|---|---|---|---|---|
phase |
parallel |
cross |
timer |
parallel |
cross |
‘cross’ |
GREEN |
RED |
NOT done |
GREEN |
RED |
‘cross’ |
GREEN |
RED |
done |
YELLOW |
RED |
‘cross’ |
YELLOW |
RED |
NOT done |
YELLOW |
RED |
‘cross’ |
YELLOW |
RED |
done |
safety RED |
safety RED |
‘cross’ |
RED |
RED |
NOT done |
safety RED |
safety RED |
‘cross’ |
RED |
RED |
done |
RED |
GREEN |
red |
current |
current |
next |
next |
|
|---|---|---|---|---|---|
phase |
parallel |
cross |
timer |
parallel |
cross |
‘parallel’ |
RED |
GREEN |
NOT done |
RED |
GREEN |
‘parallel’ |
RED |
GREEN |
done |
RED |
YELLOW |
‘parallel’ |
RED |
YELLOW |
NOT done |
RED |
YELLOW |
‘parallel’ |
RED |
YELLOW |
done |
safety RED |
safety RED |
‘parallel’ |
RED |
RED |
NOT done |
safety RED |
safety RED |
‘parallel’ |
RED |
RED |
done |
GREEN |
RED |
It also makes sure that there is never a case where the lights allow cars to move through the intersection at the same time (to try to avoid accidents).
What if the Traffic Light has a walk button and I push it? What if the Traffic Light changes based on if there is an emergency vehicle? What would the inputs be and what truth table do I get?
code from the chapter
what is next?
I know how to make a Python Test Driven Development environment manually.
I know how to make a Python Test Driven Development environment automatically.
I know how to make a Python Test Driven Development environment automatically with variables.
Would you like to test binary operations from the truth_table project with loops?
rate pumping python
If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.