diff --git a/appveyor.yml b/appveyor.yml index 9925436c69..b653af1331 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -28,7 +28,7 @@ install: - cmd: conda info -a # install depenencies - - cmd: conda create -n test_env --yes --quiet python=%PYTHON_VERSION% pip numpy scipy pytables pandas nose pytest pytz ephem numba siphon -c conda-forge + - cmd: conda create -n test_env --yes --quiet python=%PYTHON_VERSION% pip numpy scipy pytables pandas nose pytest pytz ephem numba siphon pytest-mock -c conda-forge - cmd: activate test_env - cmd: python --version - cmd: conda list diff --git a/ci/requirements-py27-min.yml b/ci/requirements-py27-min.yml index d132091fdc..ecac0a4857 100644 --- a/ci/requirements-py27-min.yml +++ b/ci/requirements-py27-min.yml @@ -4,6 +4,7 @@ dependencies: - pytz - pytest - pytest-cov + - pytest-mock - nose - pip: - coveralls diff --git a/ci/requirements-py27.yml b/ci/requirements-py27.yml index e6189d073b..81df9d068c 100644 --- a/ci/requirements-py27.yml +++ b/ci/requirements-py27.yml @@ -14,6 +14,7 @@ dependencies: - siphon - pytest - pytest-cov + - pytest-mock - nose - pip: - coveralls diff --git a/ci/requirements-py34.yml b/ci/requirements-py34.yml index 3ad4dd7e65..870ddebd39 100644 --- a/ci/requirements-py34.yml +++ b/ci/requirements-py34.yml @@ -14,6 +14,7 @@ dependencies: - siphon - pytest - pytest-cov + - pytest-mock - nose - pip: - coveralls diff --git a/ci/requirements-py35.yml b/ci/requirements-py35.yml index 8e7678fab0..8f250f1a44 100644 --- a/ci/requirements-py35.yml +++ b/ci/requirements-py35.yml @@ -14,6 +14,7 @@ dependencies: - siphon - pytest - pytest-cov + - pytest-mock - nose - pip: - coveralls diff --git a/ci/requirements-py36.yml b/ci/requirements-py36.yml index ba8e9cdda5..0f17adf978 100644 --- a/ci/requirements-py36.yml +++ b/ci/requirements-py36.yml @@ -14,6 +14,7 @@ dependencies: #- siphon - pytest - pytest-cov + - pytest-mock - nose - pip: - coveralls diff --git a/docs/sphinx/source/contributing.rst b/docs/sphinx/source/contributing.rst index 88eb7aca9e..77af3bc4cc 100644 --- a/docs/sphinx/source/contributing.rst +++ b/docs/sphinx/source/contributing.rst @@ -9,7 +9,7 @@ contribute. Easy ways to contribute ------------------------ +~~~~~~~~~~~~~~~~~~~~~~~ Here are a few ideas for you can contribute, even if you are new to pvlib-python, git, or Python: @@ -33,7 +33,7 @@ pvlib-python, git, or Python: How to contribute new code --------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~~~ Contributors to pvlib-python use GitHub's pull requests to add/modify its source code. The GitHub pull request process can be intimidating for @@ -81,29 +81,142 @@ changes, such as fixing documentation typos. Testing -------- +~~~~~~~ pvlib's unit tests can easily be run by executing ``py.test`` on the pvlib directory: -``py.test pvlib`` +``pytest pvlib`` or, for a single module: -``py.test pvlib/test/test_clearsky.py`` +``pytest pvlib/test/test_clearsky.py`` -While copy/paste coding should generally be avoided, it's a great way -to learn how to write unit tests! +or, for a single test: -Unit test code should be placed in the corresponding test module in the -pvlib/test directory. +``pytest pvlib/test/test_clearsky.py::test_ineichen_nans`` + +Use the ``--pdb`` flag to debug failures and avoid using ``print``. + +New unit test code should be placed in the corresponding test module in +the pvlib/test directory. Developers **must** include comprehensive tests for any additions or modifications to pvlib. +pvlib-python contains 3 "layers" of code: functions, PVSystem/Location, +and ModelChain. Contributors will need to add tests that correspond to +the layer that they modify. + +Functions +--------- +Tests of core pvlib functions should ensure that the function returns +the desired output for a variety of function inputs. The tests should be +independent of other pvlib functions (see :issue:`394`). The tests +should ensure that all reasonable combinations of input types (floats, +nans, arrays, series, scalars, etc) work as expected. Remember that your +use case is likely not the only way that this function will be used, and +your input data may not be generic enough to fully test the function. +Write tests that cover the full range of validity of the algorithm. +It is also important to write tests that assert the return value of the +function or that the function throws an exception when input data is +beyond the range of algorithm validity. + +PVSystem/Location +----------------- +The PVSystem and Location classes provide convenience wrappers around +the core pvlib functions. The tests in test_pvsystem.py and +test_location.py should ensure that the method calls correctly wrap the +function calls. Many PVSystem/Location methods pass one or more of their +object's attributes (e.g. PVSystem.module_parameters, Location.latitude) +to a function. Tests should ensure that attributes are passed correctly. +These tests should also ensure that the method returns some reasonable +data, though the precise values of the data should be covered by +function-specific tests discussed above. + +We prefer to use the ``pytest-mock`` framework to write these tests. The +test below shows an example of testing the ``PVSystem.ashraeiam`` +method. ``mocker`` is a ``pytest-mock`` object. ``mocker.spy`` adds +features to the ``pvsystem.ashraeiam`` *function* that keep track of how +it was called. Then a ``PVSystem`` object is created and the +``PVSystem.ashraeiam`` *method* is called in the usual way. The +``PVSystem.ashraeiam`` method is supposed to call the +``pvsystem.ashraeiam`` function with the angles supplied to the method +call and the value of ``b`` that we defined in ``module_parameters``. +The ``pvsystem.ashraeiam.assert_called_once_with`` tests that this does, +in fact, happen. Finally, we check that the output of the method call is +reasonable. + +.. code-block:: python + def test_PVSystem_ashraeiam(mocker): + # mocker is a pytest-mock object. + # mocker.spy adds code to a function to keep track of how it is called + mocker.spy(pvsystem, 'ashraeiam') + + # set up inputs + module_parameters = {'b': 0.05} + system = pvsystem.PVSystem(module_parameters=module_parameters) + thetas = 1 + + # call the method + iam = system.ashraeiam(thetas) + + # did the method call the function as we expected? + # mocker.spy added assert_called_once_with to the function + pvsystem.ashraeiam.assert_called_once_with(thetas, b=module_parameters['b']) + + # check that the output is reasonable, but no need to duplicate + # the rigorous tests of the function + assert iam < 1. + +Avoid writing PVSystem/Location tests that depend sensitively on the +return value of a statement as a substitute for using mock. These tests +are sensitive to changes in the functions, which is *not* what we want +to test here, and are difficult to maintain. + +ModelChain +---------- +The tests in test_modelchain.py should ensure that +``ModelChain.__init__`` correctly configures the ModelChain object to +eventually run the selected models. A test should ensure that the +appropriate method is actually called in the course of +``ModelChain.run_model``. A test should ensure that the model selection +does have a reasonable effect on the subsequent calculations, though the +precise values of the data should be covered by the function tests +discussed above. ``pytest-mock`` can also be used for testing ``ModelChain``. + +The example below shows how mock can be used to assert that the correct +PVSystem method is called through ``ModelChain.run_model``. + +.. code-block:: python + def test_modelchain_dc_model(mocker): + # set up location and system for model chain + location = location.Location(32, -111) + system = pvsystem.PVSystem(module_parameters=some_sandia_mod_params, + inverter_parameters=some_cecinverter_params) + + # mocker.spy adds code to the system.sapm method to keep track of how + # it is called. use returned mock object m to make assertion later, + # but see example above for alternative + m = mocker.spy(system, 'sapm') + + # make and run the model chain + mc = ModelChain(system, location, + aoi_model='no_loss', spectral_model='no_loss') + times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') + mc.run_model(times) + + # assertion fails if PVSystem.sapm is not called once + # if using returned m, prefer this over m.assert_called_once() + # for compatibility with python < 3.6 + assert m.call_count == 1 + + # ensure that dc attribute now exists and is correct type + assert isinstance(mc.dc, (pd.Series, pd.DataFrame)) + This documentation ------------------- +~~~~~~~~~~~~~~~~~~ If this documentation is unclear, help us improve it! Consider looking at the `pandas diff --git a/docs/sphinx/source/whatsnew/v0.6.0.rst b/docs/sphinx/source/whatsnew/v0.6.0.rst index df86228739..2f205edf04 100644 --- a/docs/sphinx/source/whatsnew/v0.6.0.rst +++ b/docs/sphinx/source/whatsnew/v0.6.0.rst @@ -24,10 +24,16 @@ Bug fixes Documentation ~~~~~~~~~~~~~ +* Expand testing section with guidelines for functions, PVSystem/Location + objects, and ModelChain. Testing ~~~~~~~ +* Add pytest-mock dependency +* Use pytest-mock to ensure that PVSystem methods call corresponding functions + correctly. Removes implicit dependence on precise return values of functions +* Use pytest-mock to ensure that ModelChain DC model is set up correctly. Contributors diff --git a/pvlib/test/test_modelchain.py b/pvlib/test/test_modelchain.py index 405db3e8c5..de8ba049c9 100644 --- a/pvlib/test/test_modelchain.py +++ b/pvlib/test/test_modelchain.py @@ -211,6 +211,23 @@ def test_dc_models(system, cec_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_system, assert_series_equal(ac, expected, check_less_precise=2) +@requires_scipy +@pytest.mark.parametrize('dc_model', ['sapm', 'singlediode', 'pvwatts_dc']) +def test_infer_dc_model(system, cec_dc_snl_ac_system, + pvwatts_dc_pvwatts_ac_system, location, dc_model, + mocker): + dc_systems = {'sapm': system, 'singlediode': cec_dc_snl_ac_system, + 'pvwatts_dc': pvwatts_dc_pvwatts_ac_system} + system = dc_systems[dc_model] + m = mocker.spy(system, dc_model) + mc = ModelChain(system, location, + aoi_model='no_loss', spectral_model='no_loss') + times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') + mc.run_model(times) + assert m.call_count == 1 + assert isinstance(mc.dc, (pd.Series, pd.DataFrame)) + + def acdc(mc): mc.ac = mc.dc diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index d1e0c7b494..289b0f6577 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -21,38 +21,11 @@ from conftest import needs_numpy_1_10, requires_scipy -latitude = 32.2 -longitude = -111 -tus = Location(latitude, longitude, 'US/Arizona', 700, 'Tucson') -times = pd.date_range(start=datetime.datetime(2014,1,1), - end=datetime.datetime(2014,1,2), freq='1Min') -ephem_data = solarposition.get_solarposition(times, - latitude=latitude, - longitude=longitude, - method='nrel_numpy') -am = atmosphere.relativeairmass(ephem_data.apparent_zenith) -irrad_data = clearsky.ineichen(ephem_data['apparent_zenith'], am, - linke_turbidity=3) -aoi = irradiance.aoi(0, 0, ephem_data['apparent_zenith'], - ephem_data['azimuth']) - - -meta = {'latitude': 37.8, - 'longitude': -122.3, - 'altitude': 10, - 'Name': 'Oakland', - 'State': 'CA', - 'TZ': -8} - -pvlib_abspath = os.path.dirname(os.path.abspath(inspect.getfile(tmy))) - -tmy3_testfile = os.path.join(pvlib_abspath, 'data', '703165TY.csv') -tmy2_testfile = os.path.join(pvlib_abspath, 'data', '12839.tm2') - -tmy3_data, tmy3_metadata = tmy.readtmy3(tmy3_testfile) -tmy2_data, tmy2_metadata = tmy.readtmy2(tmy2_testfile) def test_systemdef_tmy3(): + pvlib_abspath = os.path.dirname(os.path.abspath(inspect.getfile(tmy))) + tmy3_testfile = os.path.join(pvlib_abspath, 'data', '703165TY.csv') + tmy3_data, tmy3_metadata = tmy.readtmy3(tmy3_testfile) expected = {'tz': -9.0, 'albedo': 0.1, 'altitude': 7.0, @@ -65,7 +38,12 @@ def test_systemdef_tmy3(): 'surface_tilt': 0} assert expected == pvsystem.systemdef(tmy3_metadata, 0, 0, .1, 5, 5) + def test_systemdef_tmy2(): + pvlib_abspath = os.path.dirname(os.path.abspath(inspect.getfile(tmy))) + tmy2_testfile = os.path.join(pvlib_abspath, 'data', '12839.tm2') + tmy2_data, tmy2_metadata = tmy.readtmy2(tmy2_testfile) + expected = {'tz': -5, 'albedo': 0.1, 'altitude': 2.0, @@ -78,8 +56,17 @@ def test_systemdef_tmy2(): 'surface_tilt': 0} assert expected == pvsystem.systemdef(tmy2_metadata, 0, 0, .1, 5, 5) + def test_systemdef_dict(): - expected = {'tz': -8, ## Note that TZ is float, but Location sets tz as string + meta = {'latitude': 37.8, + 'longitude': -122.3, + 'altitude': 10, + 'Name': 'Oakland', + 'State': 'CA', + 'TZ': -8} + + # Note that TZ is float, but Location sets tz as string + expected = {'tz': -8, 'albedo': 0.1, 'altitude': 10, 'latitude': 37.8, @@ -113,15 +100,14 @@ def test_ashraeiam_scalar(): assert_allclose(iam, expected, equal_nan=True) -@needs_numpy_1_10 -def test_PVSystem_ashraeiam(): +def test_PVSystem_ashraeiam(mocker): + mocker.spy(pvsystem, 'ashraeiam') module_parameters = pd.Series({'b': 0.05}) system = pvsystem.PVSystem(module_parameters=module_parameters) - thetas = np.array([-90. , -67.5, -45. , -22.5, 0. , 22.5, 45. , 67.5, 89., 90. , np.nan]) + thetas = 1 iam = system.ashraeiam(thetas) - expected = np.array([ 0, 0.9193437 , 0.97928932, 0.99588039, 1. , - 0.99588039, 0.97928932, 0.9193437 , 0, 0, np.nan]) - assert_allclose(iam, expected, equal_nan=True) + pvsystem.ashraeiam.assert_called_once_with(thetas, b=0.05) + assert iam < 1. @needs_numpy_1_10 @@ -151,15 +137,14 @@ def test_physicaliam_scalar(): assert_allclose(iam, expected, equal_nan=True) -@needs_numpy_1_10 -def test_PVSystem_physicaliam(): +def test_PVSystem_physicaliam(mocker): module_parameters = pd.Series({'K': 4, 'L': 0.002, 'n': 1.526}) system = pvsystem.PVSystem(module_parameters=module_parameters) - thetas = np.array([-90. , -67.5, -45. , -22.5, 0. , 22.5, 45. , 67.5, 90. , np.nan]) + mocker.spy(pvsystem, 'physicaliam') + thetas = 1 iam = system.physicaliam(thetas) - expected = np.array([ 0, 0.8893998 , 0.98797788, 0.99926198, 1, - 0.99926198, 0.98797788, 0.8893998 , 0, np.nan]) - assert_allclose(iam, expected, equal_nan=True) + pvsystem.physicaliam.assert_called_once_with(thetas, **module_parameters) + assert iam < 1. # if this completes successfully we'll be able to do more tests below. @@ -232,14 +217,15 @@ def test_sapm(sapm_module_params): sapm_module_params.to_dict()) -def test_PVSystem_sapm(sapm_module_params): +def test_PVSystem_sapm(sapm_module_params, mocker): + mocker.spy(pvsystem, 'sapm') system = pvsystem.PVSystem(module_parameters=sapm_module_params) - - times = pd.DatetimeIndex(start='2015-01-01', periods=5, freq='12H') - effective_irradiance = pd.Series([-1, 0.5, 1.1, np.nan, 1], index=times) - temp_cell = pd.Series([10, 25, 50, 25, np.nan], index=times) - + effective_irradiance = 0.5 + temp_cell = 25 out = system.sapm(effective_irradiance, temp_cell) + pvsystem.sapm.assert_called_once_with(effective_irradiance, temp_cell, + sapm_module_params) + assert_allclose(out['p_mp'], 100, atol=100) @pytest.mark.parametrize('airmass,expected', [ @@ -257,23 +243,37 @@ def test_sapm_spectral_loss(sapm_module_params, airmass, expected): assert_allclose(out, expected, atol=1e-4) -def test_PVSystem_sapm_spectral_loss(sapm_module_params): +def test_PVSystem_sapm_spectral_loss(sapm_module_params, mocker): + mocker.spy(pvsystem, 'sapm_spectral_loss') system = pvsystem.PVSystem(module_parameters=sapm_module_params) - - times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H') - airmass = pd.Series([1, 10], index=times) - + airmass = 2 out = system.sapm_spectral_loss(airmass) - - -@pytest.mark.parametrize("expected", [1.03173953]) -def test_PVSystem_first_solar_spectral_loss(sapm_module_params, expected): - system = pvsystem.PVSystem(module_parameters=sapm_module_params) + pvsystem.sapm_spectral_loss.assert_called_once_with(airmass, + sapm_module_params) + assert_allclose(out, 1, atol=0.5) + + +# this test could be improved to cover all cell types. +# could remove the need for specifying spectral coefficients if we don't +# care about the return value at all +@pytest.mark.parametrize('module_parameters,module_type,coefficients', [ + ({'Technology': 'mc-Si'}, 'multisi', None), + ({'Material': 'Multi-c-Si'}, 'multisi', None), + ({'first_solar_spectral_coefficients': ( + 0.84, -0.03, -0.008, 0.14, 0.04, -0.002)}, + None, + (0.84, -0.03, -0.008, 0.14, 0.04, -0.002)) + ]) +def test_PVSystem_first_solar_spectral_loss(module_parameters, module_type, + coefficients, mocker): + mocker.spy(atmosphere, 'first_solar_spectral_correction') + system = pvsystem.PVSystem(module_parameters=module_parameters) pw = 3 airmass_absolute = 3 out = system.first_solar_spectral_loss(pw, airmass_absolute) - - assert_allclose(out, expected, atol=1e-4) + atmosphere.first_solar_spectral_correction.assert_called_once_with( + pw, airmass_absolute, module_type, coefficients) + assert_allclose(out, 1, atol=0.5) @pytest.mark.parametrize('aoi,expected', [ @@ -303,13 +303,13 @@ def test_sapm_aoi_loss_limits(): assert pvsystem.sapm_aoi_loss(1, module_parameters) == 0 -def test_PVSystem_sapm_aoi_loss(sapm_module_params): +def test_PVSystem_sapm_aoi_loss(sapm_module_params, mocker): system = pvsystem.PVSystem(module_parameters=sapm_module_params) - - times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H') - aoi = pd.Series([45, 10], index=times) - + mocker.spy(pvsystem, 'sapm_aoi_loss') + aoi = 0 out = system.sapm_aoi_loss(aoi) + pvsystem.sapm_aoi_loss.assert_called_once_with(aoi, sapm_module_params) + assert_allclose(out, 1.0, atol=0.01) @pytest.mark.parametrize('test_input,expected', [ @@ -342,18 +342,23 @@ def test_sapm_effective_irradiance(sapm_module_params, test_input, expected): assert_allclose(out, expected, atol=1e-4) -def test_PVSystem_sapm_effective_irradiance(sapm_module_params): +def test_PVSystem_sapm_effective_irradiance(sapm_module_params, mocker): system = pvsystem.PVSystem(module_parameters=sapm_module_params) + mocker.spy(pvsystem, 'sapm_effective_irradiance') - poa_direct = np.array([np.nan, 1000, 1000]) - poa_diffuse = np.array([100, np.nan, 100]) - airmass_absolute = np.array([1.1, 1.1, 1.1]) - aoi = np.array([10, 10, 10]) + poa_direct = 900 + poa_diffuse = 100 + airmass_absolute = 1.5 + aoi = 0 reference_irradiance = 1000 out = system.sapm_effective_irradiance( poa_direct, poa_diffuse, airmass_absolute, aoi, reference_irradiance=reference_irradiance) + pvsystem.sapm_effective_irradiance.assert_called_once_with( + poa_direct, poa_diffuse, airmass_absolute, aoi, sapm_module_params, + reference_irradiance=reference_irradiance) + assert_allclose(out, 1, atol=0.1) def test_calcparams_desoto(cec_module_params): @@ -381,24 +386,32 @@ def test_calcparams_desoto(cec_module_params): assert_allclose(nNsVth, 0.473) -def test_PVSystem_calcparams_desoto(cec_module_params): +def test_PVSystem_calcparams_desoto(cec_module_params, mocker): + mocker.spy(pvsystem, 'calcparams_desoto') module_parameters = cec_module_params.copy() module_parameters['EgRef'] = 1.121 module_parameters['dEgdT'] = -0.0002677 system = pvsystem.PVSystem(module_parameters=module_parameters) - times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H') - effective_irradiance = pd.Series([0.0, 800.0], index=times) + effective_irradiance = np.array([0, 800]) temp_cell = 25 - IL, I0, Rs, Rsh, nNsVth = system.calcparams_desoto(effective_irradiance, temp_cell) - - assert_series_equal(np.round(IL, 3), pd.Series([0.0, 6.036], index=times)) - # changed value in GH 444 for 2017-6-5 module file - assert_allclose(I0, 1.94e-9) - assert_allclose(Rs, 0.094) - assert_series_equal(np.round(Rsh, 3), pd.Series([np.inf, 19.65], index=times)) - assert_allclose(nNsVth, 0.473) + pvsystem.calcparams_desoto.assert_called_once_with( + effective_irradiance, + temp_cell, + alpha_sc=cec_module_params['alpha_sc'], + a_ref=cec_module_params['a_ref'], + I_L_ref=cec_module_params['I_L_ref'], + I_o_ref=cec_module_params['I_o_ref'], + R_sh_ref=cec_module_params['R_sh_ref'], + R_s=cec_module_params['R_s'], + EgRef=module_parameters['EgRef'], + dEgdT=module_parameters['dEgdT']) + assert_allclose(IL, np.array([0.0, 6.036]), atol=1) + assert_allclose(I0, 2.0e-9, atol=1.0e-9) + assert_allclose(Rs, 0.1, atol=0.1) + assert_allclose(Rsh, np.array([np.inf, 20]), atol=1) + assert_allclose(nNsVth, 0.5, atol=0.1) @pytest.fixture(params=[ @@ -636,10 +649,12 @@ def test_i_from_v(fixture_i_from_v): @requires_scipy -def test_PVSystem_i_from_v(): +def test_PVSystem_i_from_v(mocker): system = pvsystem.PVSystem() - output = system.i_from_v(20, .1, .5, 40, 6e-7, 7) - assert_allclose(output, -299.746389916, atol=1e-5) + m = mocker.patch('pvlib.pvsystem.i_from_v', autospec=True) + args = (20, .1, .5, 40, 6e-7, 7) + system.i_from_v(*args) + m.assert_called_once_with(*args) @requires_scipy @@ -768,19 +783,16 @@ def test_scale_voltage_current_power(sam_data): columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx'], index=[0]) out = pvsystem.scale_voltage_current_power(data, voltage=2, current=3) + assert_frame_equal(out, expected, check_less_precise=5) -def test_PVSystem_scale_voltage_current_power(): - data = pd.DataFrame( - np.array([[2, 1.5, 10, 8, 12, 0.5, 1.5]]), - columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx'], - index=[0]) - expected = pd.DataFrame( - np.array([[6, 4.5, 20, 16, 72, 1.5, 4.5]]), - columns=['i_sc', 'i_mp', 'v_oc', 'v_mp', 'p_mp', 'i_x', 'i_xx'], - index=[0]) +def test_PVSystem_scale_voltage_current_power(mocker): + data = None system = pvsystem.PVSystem(modules_per_string=2, strings_per_inverter=3) - out = system.scale_voltage_current_power(data) + m = mocker.patch( + 'pvlib.pvsystem.scale_voltage_current_power', autospec=True) + system.scale_voltage_current_power(data) + m.assert_called_once_with(data, voltage=2, current=3) def test_sapm_celltemp(): @@ -816,20 +828,19 @@ def test_sapm_celltemp_with_index(): assert_frame_equal(expected, pvtemps) -def test_PVSystem_sapm_celltemp(): - system = pvsystem.PVSystem(racking_model='roof_mount_cell_glassback') - times = pd.DatetimeIndex(start='2015-01-01', end='2015-01-02', freq='12H') - temps = pd.Series([0, 10, 5], index=times) - irrads = pd.Series([0, 500, 0], index=times) - winds = pd.Series([10, 5, 0], index=times) +def test_PVSystem_sapm_celltemp(mocker): + racking_model = 'roof_mount_cell_glassback' - pvtemps = system.sapm_celltemp(irrads, winds, temps) - - expected = pd.DataFrame({'temp_cell':[0., 30.56763059, 5.], - 'temp_module':[0., 30.06763059, 5.]}, - index=times) - - assert_frame_equal(expected, pvtemps) + system = pvsystem.PVSystem(racking_model=racking_model) + mocker.spy(pvsystem, 'sapm_celltemp') + temps = 25 + irrads = 1000 + winds = 1 + out = system.sapm_celltemp(irrads, winds, temps) + pvsystem.sapm_celltemp.assert_called_once_with( + irrads, winds, temps, model=racking_model) + assert isinstance(out, pd.DataFrame) + assert out.shape == (1, 2) def test_adrinverter(sam_data): @@ -1118,36 +1129,57 @@ def make_pvwatts_system_kwargs(): return system -def test_PVSystem_pvwatts_dc(): +def test_PVSystem_pvwatts_dc(mocker): + mocker.spy(pvsystem, 'pvwatts_dc') system = make_pvwatts_system_defaults() - irrad_trans = pd.Series([np.nan, 900, 900]) - temp_cell = pd.Series([30, np.nan, 30]) - expected = pd.Series(np.array([ nan, nan, 88.65])) - out = system.pvwatts_dc(irrad_trans, temp_cell) - assert_series_equal(expected, out) + irrad = 900 + temp_cell = 30 + expected = 90 + out = system.pvwatts_dc(irrad, temp_cell) + pvsystem.pvwatts_dc.assert_called_once_with(irrad, temp_cell, + **system.module_parameters) + assert_allclose(expected, out, atol=10) + +def test_PVSystem_pvwatts_dc_kwargs(mocker): + mocker.spy(pvsystem, 'pvwatts_dc') system = make_pvwatts_system_kwargs() - expected = pd.Series(np.array([ nan, nan, 87.3])) - out = system.pvwatts_dc(irrad_trans, temp_cell) - assert_series_equal(expected, out) + irrad = 900 + temp_cell = 30 + expected = 90 + out = system.pvwatts_dc(irrad, temp_cell) + pvsystem.pvwatts_dc.assert_called_once_with(irrad, temp_cell, + **system.module_parameters) + assert_allclose(expected, out, atol=10) -def test_PVSystem_pvwatts_losses(): +def test_PVSystem_pvwatts_losses(mocker): + mocker.spy(pvsystem, 'pvwatts_losses') system = make_pvwatts_system_defaults() - expected = pd.Series([nan, 14.934904]) - age = pd.Series([nan, 1]) + expected = 15 + age = 1 out = system.pvwatts_losses(age=age) - assert_series_equal(expected, out) + pvsystem.pvwatts_losses.assert_called_once_with(age=age) + assert out < expected -def test_PVSystem_pvwatts_ac(): +def test_PVSystem_pvwatts_ac(mocker): + mocker.spy(pvsystem, 'pvwatts_ac') system = make_pvwatts_system_defaults() - pdc = pd.Series([np.nan, 50, 100]) - expected = pd.Series(np.array([ nan, 48.1095776694, 96.0])) + pdc = 50 + pdc0 = system.module_parameters['pdc0'] out = system.pvwatts_ac(pdc) - assert_series_equal(expected, out) + pvsystem.pvwatts_ac.assert_called_once_with(pdc, pdc0, + **system.inverter_parameters) + assert out < pdc + +def test_PVSystem_pvwatts_ac_kwargs(mocker): + mocker.spy(pvsystem, 'pvwatts_ac') system = make_pvwatts_system_kwargs() - expected = pd.Series(np.array([ nan, 45.88025, 91.5515])) + pdc = 50 + pdc0 = system.module_parameters['pdc0'] out = system.pvwatts_ac(pdc) - assert_series_equal(expected, out) + pvsystem.pvwatts_ac.assert_called_once_with(pdc, pdc0, + **system.inverter_parameters) + assert out < pdc