Skip to content

use pytest-mock to simplify and improve tests, improve test docs #468

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jun 13, 2018

Conversation

wholmgren
Copy link
Member

@wholmgren wholmgren commented May 30, 2018

Rewrites PVSystem tests using pytest-mock and adds a new ModelChain test. Please see the whatsnew diff and the new documentation for more details on how and why.

The changes to test_pvsystem.py are complete (I think). I don't currently have time to go through the entire test_modelchain.py module and improve the tests to use the new methods. I suggest calling it good enough for this pull request. It at least provides an example for people working with the modelchain code in the future.

See discussion in #466 in this comment and below for more context.

  • Closes issue #xxxx N/A
  • Fully tested. Added and/or modified tests to ensure correct behavior for all reasonable inputs. Tests must pass on the TravisCI and Appveyor testing services.
  • Code quality and style is sufficient. Passes git diff upstream/master -u -- "*.py" | flake8 --diff and/or landscape.io linting service.
  • New code is fully documented. Includes sphinx/numpydoc compliant docstrings and comments in the code where necessary.
  • Updates entries to docs/sphinx/source/api.rst for API changes.
  • Adds description and name entries in the appropriate docs/sphinx/source/whatsnew file for all changes.


# 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=0.05)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, why do we pass b=-0.05 instead of module_parameters[b]? If b=0.05 is intended then why is module_parameters needed? Just looking to clarify.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the example above, either b=0.05 or b=module_parameters['b'] would work. What is important is that something dict-like is passed to PVSystem. For example, this would also work:
system = pvsystem.PVSystem(module_parameters={'b': 0.05}) followed by the assert statement as shown. Or this would work:

b = 0.05
system = pvsystem.PVSystem(module_parameters=dict(b=b))
# ...
pvsystem.ashraeiam.assert_called_once_with(thetas, b=b)

Would one of the above be a better example? Or more comments?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would appear more clear to me if we change line 157 to module_parameters = {'b' : 0.05} which avoids recasting the dict as a pandas object. Also, to me it is more consistent to use the dict in the assertion, assert_called_once_with(thetas, module_parameters['b']) instead of assert_called_once_with(thetas, b=0.05)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. That makes sense.

Copy link
Member Author

@wholmgren wholmgren left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cwhanse do you want to review this and merge if acceptable? I know I need to make one change to replace a test I accidentally removed. I'll do that next.

Use the "squash and merge" feature so that we can maintain a simple commit history. If you don't already see it, it's under the arrow button on the right side of green merge button.

@@ -21,38 +21,11 @@

from conftest import needs_numpy_1_10, requires_scipy

latitude = 32.2
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this block of code to make all of tests self-contained. A small amount of it is moved into a couple of functions. None of these changes are completely necessary for pytest-mock improvements, but I was already in "improve the tests" mode.

@@ -9,7 +9,7 @@ contribute.


Easy ways to contribute
-----------------------
~~~~~~~~~~~~~~~~~~~~~~~
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these needed to change to make the new level of organization work below (Functions, PVsystem/Location, ModelChain).


pvlib's unit tests can easily be run by executing ``py.test`` on the
pvlib directory:

``py.test pvlib``
``pytest pvlib``
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Older versions of pytest were called with py.test. Now it's just pytest.

@@ -113,15 +99,14 @@ def test_ashraeiam_scalar():
assert_allclose(iam, expected, equal_nan=True)


@needs_numpy_1_10
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

numpy 1.10 is only needed for tests that use assert_allclose(equal_nan=True)

out = system.sapm_spectral_loss(airmass)


@pytest.mark.parametrize("expected", [1.03173953])
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I accidentally deleted this test in a merge. I'll fix next.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added below.

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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This low-precision test will fail if we change the units of effective irradiance. That could be an argument for or against having any low-precision tests at all, depending on your perspective.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we are testing that pvsystem.sapm_effective_irradiance is called and returns a reasonable value, correct? If so, then I would change airmass_absolute = 1.5, aoi = 0 and poa_direct = 900. We should keep reference_irradiance = 1000. With these inputs the method call should return 1.0 exactly. At least until we act on #472

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the input parameters.

Here, we are testing that pvsystem.sapm_effective_irradiance is called and returns a reasonable value, correct?

Yes, so I kept atol=0.1.

system = pvsystem.PVSystem(modules_per_string=2, strings_per_inverter=3)
m = mocker.patch(
'pvlib.pvsystem.scale_voltage_current_power', autospec=True)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test function uses a different pattern than the others. Unlike mocker.spy, mocker.patch can only be used to test that something is called because it completely replaces the function/method under consideration. I chose this pattern here because I think the input data is a little more complicated than is worth maintaining in the method test. Could be changed, though.

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we are testing that pvsystem.sapm_effective_irradiance is called and returns a reasonable value, correct? If so, then I would change airmass_absolute = 1.5, aoi = 0 and poa_direct = 900. We should keep reference_irradiance = 1000. With these inputs the method call should return 1.0 exactly. At least until we act on #472

@cwhanse cwhanse merged commit a3e05f2 into pvlib:master Jun 13, 2018
@wholmgren wholmgren deleted the mock branch June 13, 2018 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants