@@ -9,7 +9,7 @@ contribute.
9
9
10
10
11
11
Easy ways to contribute
12
- -----------------------
12
+ ~~~~~~~~~~~~~~~~~~~~~~~
13
13
14
14
Here are a few ideas for you can contribute, even if you are new to
15
15
pvlib-python, git, or Python:
@@ -33,7 +33,7 @@ pvlib-python, git, or Python:
33
33
34
34
35
35
How to contribute new code
36
- --------------------------
36
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
37
37
38
38
Contributors to pvlib-python use GitHub's pull requests to add/modify
39
39
its source code. The GitHub pull request process can be intimidating for
@@ -81,29 +81,142 @@ changes, such as fixing documentation typos.
81
81
82
82
83
83
Testing
84
- -------
84
+ ~~~~~~~
85
85
86
86
pvlib's unit tests can easily be run by executing ``py.test `` on the
87
87
pvlib directory:
88
88
89
- ``py.test pvlib ``
89
+ ``pytest pvlib ``
90
90
91
91
or, for a single module:
92
92
93
- ``py.test pvlib/test/test_clearsky.py ``
93
+ ``pytest pvlib/test/test_clearsky.py ``
94
94
95
- While copy/paste coding should generally be avoided, it's a great way
96
- to learn how to write unit tests!
95
+ or, for a single test:
97
96
98
- Unit test code should be placed in the corresponding test module in the
99
- pvlib/test directory.
97
+ ``pytest pvlib/test/test_clearsky.py::test_ineichen_nans ``
98
+
99
+ Use the ``--pdb `` flag to debug failures and avoid using ``print ``.
100
+
101
+ New unit test code should be placed in the corresponding test module in
102
+ the pvlib/test directory.
100
103
101
104
Developers **must ** include comprehensive tests for any additions or
102
105
modifications to pvlib.
103
106
107
+ pvlib-python contains 3 "layers" of code: functions, PVSystem/Location,
108
+ and ModelChain. Contributors will need to add tests that correspond to
109
+ the layer that they modify.
110
+
111
+ Functions
112
+ ---------
113
+ Tests of core pvlib functions should ensure that the function returns
114
+ the desired output for a variety of function inputs. The tests should be
115
+ independent of other pvlib functions (see :issue: `394 `). The tests
116
+ should ensure that all reasonable combinations of input types (floats,
117
+ nans, arrays, series, scalars, etc) work as expected. Remember that your
118
+ use case is likely not the only way that this function will be used, and
119
+ your input data may not be generic enough to fully test the function.
120
+ Write tests that cover the full range of validity of the algorithm.
121
+ It is also important to write tests that assert the return value of the
122
+ function or that the function throws an exception when input data is
123
+ beyond the range of algorithm validity.
124
+
125
+ PVSystem/Location
126
+ -----------------
127
+ The PVSystem and Location classes provide convenience wrappers around
128
+ the core pvlib functions. The tests in test_pvsystem.py and
129
+ test_location.py should ensure that the method calls correctly wrap the
130
+ function calls. Many PVSystem/Location methods pass one or more of their
131
+ object's attributes (e.g. PVSystem.module_parameters, Location.latitude)
132
+ to a function. Tests should ensure that attributes are passed correctly.
133
+ These tests should also ensure that the method returns some reasonable
134
+ data, though the precise values of the data should be covered by
135
+ function-specific tests discussed above.
136
+
137
+ We prefer to use the ``pytest-mock `` framework to write these tests. The
138
+ test below shows an example of testing the ``PVSystem.ashraeiam ``
139
+ method. ``mocker `` is a ``pytest-mock `` object. ``mocker.spy `` adds
140
+ features to the ``pvsystem.ashraeiam `` *function * that keep track of how
141
+ it was called. Then a ``PVSystem `` object is created and the
142
+ ``PVSystem.ashraeiam `` *method * is called in the usual way. The
143
+ ``PVSystem.ashraeiam `` method is supposed to call the
144
+ ``pvsystem.ashraeiam `` function with the angles supplied to the method
145
+ call and the value of ``b `` that we defined in ``module_parameters ``.
146
+ The ``pvsystem.ashraeiam.assert_called_once_with `` tests that this does,
147
+ in fact, happen. Finally, we check that the output of the method call is
148
+ reasonable.
149
+
150
+ .. code-block :: python
151
+ def test_PVSystem_ashraeiam (mocker ):
152
+ # mocker is a pytest-mock object.
153
+ # mocker.spy adds code to a function to keep track of how it is called
154
+ mocker.spy(pvsystem, ' ashraeiam' )
155
+
156
+ # set up inputs
157
+ module_parameters = {' b' : 0.05 }
158
+ system = pvsystem.PVSystem(module_parameters = module_parameters)
159
+ thetas = 1
160
+
161
+ # call the method
162
+ iam = system.ashraeiam(thetas)
163
+
164
+ # did the method call the function as we expected?
165
+ # mocker.spy added assert_called_once_with to the function
166
+ pvsystem.ashraeiam.assert_called_once_with(thetas, b = module_parameters[' b' ])
167
+
168
+ # check that the output is reasonable, but no need to duplicate
169
+ # the rigorous tests of the function
170
+ assert iam < 1 .
171
+
172
+ Avoid writing PVSystem/Location tests that depend sensitively on the
173
+ return value of a statement as a substitute for using mock. These tests
174
+ are sensitive to changes in the functions, which is *not * what we want
175
+ to test here, and are difficult to maintain.
176
+
177
+ ModelChain
178
+ ----------
179
+ The tests in test_modelchain.py should ensure that
180
+ ``ModelChain.__init__ `` correctly configures the ModelChain object to
181
+ eventually run the selected models. A test should ensure that the
182
+ appropriate method is actually called in the course of
183
+ ``ModelChain.run_model ``. A test should ensure that the model selection
184
+ does have a reasonable effect on the subsequent calculations, though the
185
+ precise values of the data should be covered by the function tests
186
+ discussed above. ``pytest-mock `` can also be used for testing ``ModelChain ``.
187
+
188
+ The example below shows how mock can be used to assert that the correct
189
+ PVSystem method is called through ``ModelChain.run_model ``.
190
+
191
+ .. code-block :: python
192
+ def test_modelchain_dc_model (mocker ):
193
+ # set up location and system for model chain
194
+ location = location.Location(32 , - 111 )
195
+ system = pvsystem.PVSystem(module_parameters = some_sandia_mod_params,
196
+ inverter_parameters = some_cecinverter_params)
197
+
198
+ # mocker.spy adds code to the system.sapm method to keep track of how
199
+ # it is called. use returned mock object m to make assertion later,
200
+ # but see example above for alternative
201
+ m = mocker.spy(system, ' sapm' )
202
+
203
+ # make and run the model chain
204
+ mc = ModelChain(system, location,
205
+ aoi_model = ' no_loss' , spectral_model = ' no_loss' )
206
+ times = pd.date_range(' 20160101 1200-0700' , periods = 2 , freq = ' 6H' )
207
+ mc.run_model(times)
208
+
209
+ # assertion fails if PVSystem.sapm is not called once
210
+ # if using returned m, prefer this over m.assert_called_once()
211
+ # for compatibility with python < 3.6
212
+ assert m.call_count == 1
213
+
214
+ # ensure that dc attribute now exists and is correct type
215
+ assert isinstance (mc.dc, (pd.Series, pd.DataFrame))
216
+
104
217
105
218
This documentation
106
- ------------------
219
+ ~~~~~~~~~~~~~~~~~~
107
220
108
221
If this documentation is unclear, help us improve it! Consider looking
109
222
at the `pandas
0 commit comments