-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtest_odesolvers.py
172 lines (118 loc) · 4.63 KB
/
test_odesolvers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
'''
test_odesolvers.py
Requires PyTest and PyTest-Mock to run
Unit tests for the odesolvers module. These are not meant to be exhaustive,
but for illustrative purposes
Written by JScabich
Integration tests would require a "ground truth" to compare against and this
is difficult to attain with numerical methods.
License: MIT
'''
import random
import odesolvers as ods
MIN, MAX = -10_000.0, 10_000.0
def randomNumberFactory(seed=None):
'''
Parameters:
seed (use a constant value for repeatable results,
otherwise seeded from the clock on Windows)
Returns:
tuple of Random values for x, v, deltat, x_returned, v_returned
'''
rand = random.Random()
rand.seed(seed)
x = rand.uniform(MIN, MAX)
v = rand.uniform(MIN, MAX)
deltat = rand.uniform(0, MAX)
x_returned = rand.uniform(MIN, MAX)
v_returned = rand.uniform(MIN, MAX)
return x, v, deltat, x_returned, v_returned
def test_odesolvers_forwardstep_calls_xprime_and_vprime(mocker):
'''
A more robust test would see if v_prime would take in another
value other than the default, but for our purposes, the frictionless
case of v = 0 is acceptable
This could also be split out into two tests, each with a single
assert, but would repeat a lot of code
'''
x, v, deltat, *_ = randomNumberFactory()
mocker.patch('odesolvers.v_prime')
mocker.patch('odesolvers.x_prime')
ods.forward_step(x, v, deltat)
ods.v_prime.assert_called_with(x)
ods.x_prime.assert_called_with(v)
def test_odesolvers_backwardstep_calls_xprime_and_vprime(mocker):
'''
As above, splitting this into a two tests might be easier to read but
repeats a lot of code
'''
x, v, deltat, x_returned, v_returned = randomNumberFactory()
mocker.patch('odesolvers.v_prime')
mocker.patch('odesolvers.x_prime')
mocker.patch('odesolvers.forward_step',
return_value = (x_returned, v_returned))
ods.backward_step(x, v, deltat)
ods.v_prime.assert_called_with(x_returned)
ods.x_prime.assert_called_with(v_returned)
def test_odesolvers_backwardstep_calls_forwardstep(mocker):
'''
frictionless v=0 condition holds here as well
'''
x, v, deltat, x_returned, v_returned = randomNumberFactory()
mocker.patch('odesolvers.forward_step',
return_value=(x_returned, v_returned))
ods.backward_step(x, v, deltat)
ods.forward_step.assert_called_with(x, v, deltat)
def test_odesolvers_combineddstep_calls_xprime_and_vprime_twice(mocker):
'''
Frictionless, v=0
Splitting this into multiple tests may have eased readability only slightly
but at the expense of repeating a lot of boilerplate
Assert types are not perfect. I really want to be able to find out if they
are called in the right order, but has_calls with a list was not producing
the expected result
'''
x, v, deltat, x_returned, v_returned = randomNumberFactory()
mocker.patch('odesolvers.x_prime')
mocker.patch('odesolvers.v_prime')
mocker.patch('odesolvers.forward_step',
return_value = (x_returned, v_returned))
ods.combined_step(x, v, deltat)
ods.v_prime.assert_any_call(x)
ods.x_prime.assert_any_call(v)
ods.v_prime.assert_any_call(x_returned)
ods.x_prime.assert_any_call(v_returned)
def test_odesolvers_combineddstep_calls_forwardstep(mocker):
'''
Frictionless, v=0 condition here too
'''
x, v, deltat, x_returned, v_returned = randomNumberFactory()
mocker.patch('odesolvers.forward_step',
return_value=(x_returned, v_returned))
ods.combined_step(x, v, deltat)
ods.forward_step.assert_called_with(x, v, deltat)
def test_odesolvers_iterate_n_calls_forwardstep_n_iter_times(mocker):
'''
Taking a deeper dive with these and making sure all the appropriate
parameters are used in all n_iter calls to the method would be impractical
'''
x0, v0, deltat, x_returned, v_returned = randomNumberFactory()
n_iter = random.randint(0,int(MAX))
mocker.patch('odesolvers.forward_step',
return_value=(x_returned, v_returned))
ods.iterate_n(x0, v0, deltat, n_iter, ods.forward_step)
assert ods.forward_step.call_count, n_iter
def test_odesolvers_iterate_n_calls_backwardstep_n_iter_times(mocker):
x0, v0, deltat, x_returned, v_returned = randomNumberFactory()
n_iter = random.randint(0,int(MAX))
mocker.patch('odesolvers.backward_step',
return_value=(x_returned, v_returned))
ods.iterate_n(x0, v0, deltat, n_iter, ods.backward_step)
assert ods.backward_step.call_count, n_iter
def test_odesolvers_iterate_n_calls_combinedstep_n_iter_times(mocker):
x0, v0, deltat, x_returned, v_returned = randomNumberFactory()
n_iter = random.randint(0,int(MAX))
mocker.patch('odesolvers.combined_step',
return_value=(x_returned, v_returned))
ods.iterate_n(x0, v0, deltat, n_iter, ods.combined_step)
assert ods.combined_step.call_count, n_iter