test_networkutil: Implement tests for download_file
#541
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Depends on #529 for the same reason as #535; we need
pyfakefs
,responses
, andpytest-mock
,Overview
This PR implements tests for
download_file
. We test what I believe are all of the non-happy-path scenarios, and most of the permutations of the happy-path scenario.Since we're moving more of our ctmods over to use
download_file
, I think it's useful to have tests for this function to give us confidence that various cases work and behave as expected beyond "it works for other ctmods". 😄With these tests, we have 88% coverage of
networkutil.py
(which only hasdownload_file
util function at this time) according topytest-cov
.Implementation
To test the happy path scenarios we make extensive use of PyTest's Parametrize functionality. If I have missed a test scenario, let me know and it can be added.
In order to test downloading a file, we have some steps:
pyfakefs
to load a sample file as a PyTest Fixture that can be used across tests. This is used as an example response from aGET
request. This also allowsdownload_file
to write to a real file at our destination path that we can load and compare against. We can simulate real filesystem operations and compare them against our real file.pyfakefs
mocks out theos
module but notPathlib
, so it's safe to usePathlib
to load the file.FakeFilesystem
fixturefs
does have some wrapper methods for ouros
functions, since it mocks outos
it's safe to use the regular methods. This is also what our tested components would do anyway :-)pytest-mock
'sMockerFixture
to force certain operations inside ofdownload_file
to raise exceptions. We use this to raise anOSError
when callingos.makedirs
, and we also use it to spy on ourprogress_callback
to check that it was called a given number of times and with expected arguments (the progress of the download).responses
to mock ourGET
requests, since we don't want to make any real network calls. In combination withpyfakefs
this allows us to load a file and set its contents as the returned body from the request to the given URL we call withdownload_file
.Structure
For the docstrings in these tests, I decided to experiment with "Given-When-Then" syntax to describe the tests. I'm a fan, and happy to revise other tests to use this structure as well if we want to use it as a go-forward and think it's a useful way to describe our tests!
Concerns
There is duplicated logic to send a mocked request across a couple of test scenarios. For example in
test_download_file_cannot_create_destination
we have to do the same set up as we do intest_download_file
, because we still want to send a request but we want to check that it fails right before trying to write the file.Maybe this isn't a huge deal, but it is something I wanted to call out.
Future Work
This PR only adds a few test functions, but because we use Parametrize, we end up with this PR effectively adding 19 tests. This on top of our existing tests, and the opened PRs that also use Parametrize, we could end up with a fair amount of tests. It may be worth looking into parallelizing some of our tests.
We should be able to do this fairly easily with
pytest-xdist
:-)Opening as draft as this PR is dependent on another to be merged, but since it's standalone in a new
test_networkutil.py
file and sincedownload_file
is the only function in that file, I thought it was worth working on in parallel.As always, all feedback is welcome. Thanks! 😄