Skip to content
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

Fix #4829: Reduce code redundancy in backend testing #5838

Merged
merged 14 commits into from
Jan 6, 2019

Conversation

lilithxxx
Copy link
Contributor

@lilithxxx lilithxxx commented Nov 8, 2018

Explanation

Fixes #4829

A new function is introduced get_html to get a HTML response analogous to get_json

Replaces instances of self.testapp.get with either self.get_json or self.get_html to reduce code redundancy.

Checklist

  • The PR title starts with "Fix #bugnum: ", followed by a short, clear summary of the changes. (If this PR fixes part of an issue, prefix the title with "Fix part of #bugnum: ...".)
  • The PR explanation includes the words "Fixes #bugnum: ..." (or "Fixes part of #bugnum" if the PR only partially fixes an issue).
  • The linter/Karma presubmit checks have passed.
    • These should run automatically, but if not, you can manually trigger them locally using python scripts/pre_commit_linter.py and bash scripts/run_frontend_tests.sh.
  • The PR is made from a branch that's not called "develop".
  • The PR follows the style guide.
  • The PR is assigned to an appropriate reviewer.
    • If you're a new contributor, please ask on Gitter for someone to assign a reviewer.
    • If you're not sure who the appropriate reviewer is, please assign to the issue's "owner" -- see the "talk-to" label on the issue.

@codecov-io
Copy link

codecov-io commented Nov 8, 2018

Codecov Report

Merging #5838 into develop will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff            @@
##           develop    #5838   +/-   ##
========================================
  Coverage    45.21%   45.21%           
========================================
  Files          523      523           
  Lines        30722    30722           
  Branches      4605     4605           
========================================
  Hits         13892    13892           
  Misses       16830    16830

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 718d4d0...08b1223. Read the comment docs.

@oppiabot
Copy link

oppiabot bot commented Nov 18, 2018

Hi @lilithxxx, I'm going to mark this PR as stale because it hasn't had any updates for 10 days. If no further activity occurs within 7 days, it will be automatically closed so that others can take up the issue.
If you are still working on this PR, please make a follow-up commit within 7 days (and submit it for review, if applicable). Please also let us know if you are stuck so we can help you!

@oppiabot oppiabot bot added the stale label Nov 18, 2018
@lilithxxx
Copy link
Contributor Author

Hi @seanlip @apb7 review please :)

@oppiabot oppiabot bot removed the stale label Nov 19, 2018
Copy link
Member

@seanlip seanlip left a comment

Choose a reason for hiding this comment

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

This looks great in general, thanks @lilithxxx! Left some inline comments.

Also, does this remove all occurrences of self.testapp.get in the codebase?

@@ -38,26 +38,28 @@ def setUp(self):
def test_admin_page_rights(self):
"""Test access rights to the admin page."""

response = self.testapp.get('/admin')
response = self.get_html('/admin', expect_errors=True,
Copy link
Member

Choose a reason for hiding this comment

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

Here and elsewhere, status codes beginning with 2 or 3 should not be considered errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If that's the case then should'nt the condition here change to assert both 2xx and 3xx codes?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I think so. Thanks for catching that!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@@ -38,26 +38,28 @@ def setUp(self):
def test_admin_page_rights(self):
"""Test access rights to the admin page."""

response = self.testapp.get('/admin')
response = self.get_html('/admin', expect_errors=True,
Copy link
Member

Choose a reason for hiding this comment

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

Here and below, prefer breaking after '(' if contents of parens span multiple lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

# https://github.com/Pylons/webtest/blob/
# bf77326420b628c9ea5431432c7e171f88c5d874/webtest/app.py#L1119 .
self.assertEqual(html_response.status_int, expected_status_int)
if not expect_errors:
Copy link
Member

Choose a reason for hiding this comment

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

expect_errors is only for status checks starting with 4 or 5.

Note that you should also check the converse, i.e. that if expect_errors is True then the status_int starts with 4 or 5.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@lilithxxx
Copy link
Contributor Author

@seanlip this does not remove all occurrences of self.testapp.get. Some functions are having issues:

There are a few other functions with similar problems(some has attribute audio etc)

@seanlip
Copy link
Member

seanlip commented Nov 24, 2018

Thanks @lilithxxx. I don't understand the first one, I don't see 'status_code' in the response? (And in any case I don't understand why that behaves differently from similar json responses.)

For the content-type attrs maybe add an extra arg in the test_utils function to overwrite the default check?

I think if we are going to get rid of self.testapp.get() we should do it properly (i.e. completely), so please don't hesitate to ask me if there are any other cases which you'd like me to suggest a solution for.

Thanks!

@lilithxxx
Copy link
Contributor Author

Hi @seanlip. There's another conflict in base.py -> Here the error codes can be 2xx, 3xx, or 4xx so the assertion fails in get_json where only a particular status code is checked.

topic_editor_test -> This json object does not have attribute status_code

There is one more error like this in collection_editor_test. What should I do about them?

@seanlip
Copy link
Member

seanlip commented Nov 26, 2018

For the second one, since you're making individual requests, can't you pass the expected status code in with each request?

For the first one, you have a couple of options -- either generalize the method signature of get_json to accept a list of allowed expected status codes, or create a new method in test_utils that is similar to get_json but accepts a list of allowed codes instead of just a single one. The second might be better in order to keep the default get_json() simple.

@oppiabot
Copy link

oppiabot bot commented Nov 28, 2018

Hi @lilithxxx. Due to recent changes in the "develop" branch, this PR now has a merge conflict. Please follow this link if you need help resolving the conflict, so that the PR can be merged. Thanks!

@lilithxxx
Copy link
Contributor Author

@seanlip The get_json method returns the body of the response. You can see that here. So we can't check status codes with this function as it doesn't have any attribute like that. I was thinking we can have a function called get_response and another function get_json that just loads the response obtained from the former method(get_response). The method get_response can be used for all content-type like html, image, audio, etc and thus we don't need get_html, we just can put content-type attr as parameter which will check for all cases. What do you think about this?

@seanlip
Copy link
Member

seanlip commented Dec 1, 2018

The get_json method returns the body of the response. You can see that here. So we can't check status codes with this function as it doesn't have any attribute like that.

I looked at the code for get_json(). It is doing a status verification check: https://github.com/oppia/oppia/blob/develop/core/tests/test_utils.py#L512

So, not sure what you mean by "we can't check status codes with this function". I'm talking about modifying the logic of get_json itself, or writing a function that is similar to get_json but that accepts a list of expected status codes instead of just one.

I feel like I might be missing something in your response, though, since the original discussion wasn't about generalizing to html/image/audio/etc.

@lilithxxx
Copy link
Contributor Author

I think I was not very clear, pardon me. Yes it does a status verification check but that is on the response object which we get from get.testapp but what the function ultimately returns is the body of response ( after calling _parse_json_response) and what we get from get_json is not a response object but a json dict which does not have a status attribute. I was not talking about the function that accepts a list of status codes( I understand that fully and I will implement it) but I was talking about the problem with the responses not having status_code attributes

@seanlip
Copy link
Member

seanlip commented Dec 1, 2018

Thanks for explaining. I think the problem you're encountering is one I addressed in my previous comment, though?

I.e. you wouldn't change get_json to return the status code. You'd pass the expected status code(s) into get_json or similar and get_json (or the other function) would check those codes internally.

@lilithxxx
Copy link
Contributor Author

@seanlip I renamed the function get_html to get_response as it just returns the get response after doing some tests. The other file formats are also called with this function. PTAL

html_response.content_type, 'text/html')

return html_response
if not can_have_multiple_responses:
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 can_have_multiple_responses is a bit of a misleading variable name. I think what you're trying to convey is more like "can be an error or not", i.e. something that contradicts expect_errors being a boolean.

So I think maybe it's worth leaving this method signature as is (I am fine with changing get_html to get_response) and creating a separate method along the lines of get_response_without_checking_for_errors(). Then have the base.py test that runs through all the URLs use that latter method instead. In fact, maybe this method here could use that method too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@lilithxxx
Copy link
Contributor Author

@seanlip made changes. PTAL
(Also I did what you asked and merged develop into my branch and solved the conflicts locally but still it was unable to push so i had to force it)

@oppiabot
Copy link

oppiabot bot commented Dec 16, 2018

Hi @lilithxxx. Due to recent changes in the "develop" branch, this PR now has a merge conflict. Please follow this link if you need help resolving the conflict, so that the PR can be merged. Thanks!

@apb7
Copy link
Contributor

apb7 commented Dec 19, 2018

Hi @lilithxxx, any updates on this? Thanks!

@lilithxxx
Copy link
Contributor Author

I will get back to it tomorrow as I am travelling currently. Thanks

@lilithxxx
Copy link
Contributor Author

Hi @seanlip @apb7 made the changes. PTAL

@oppiabot
Copy link

oppiabot bot commented Dec 22, 2018

Hi @lilithxxx. Due to recent changes in the "develop" branch, this PR now has a merge conflict. Please follow this link if you need help resolving the conflict, so that the PR can be merged. Thanks!

@oppiabot
Copy link

oppiabot bot commented Jan 1, 2019

Hi @lilithxxx, I'm going to mark this PR as stale because it hasn't had any updates for 10 days. If no further activity occurs within 7 days, it will be automatically closed so that others can take up the issue.
If you are still working on this PR, please make a follow-up commit within 7 days (and submit it for review, if applicable). Please also let us know if you are stuck so we can help you!

@oppiabot oppiabot bot added the stale label Jan 1, 2019
@lilithxxx
Copy link
Contributor Author

Hi @seanlip PTAL at this as this PR has been marked stale.Thanks!

@oppiabot oppiabot bot removed the stale label Jan 4, 2019
@seanlip
Copy link
Member

seanlip commented Jan 4, 2019

Sorry @lilithxxx for the delay. Could you please resolve the merge conflicts first?

@lilithxxx
Copy link
Contributor Author

@seanlip done!

Copy link
Member

@seanlip seanlip left a comment

Choose a reason for hiding this comment

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

Thanks @lilithxxx -- did another pass, PTAL.


Args:
url: str. The URL to fetch the response.

Copy link
Member

Choose a reason for hiding this comment

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

Drop line breaks between successive arg descriptions (please follow existing codebase style). In addition, for line descriptions that span more than one line, indent the continuation line by 4 spaces (again, follow the existing style).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

core/tests/test_utils.py Outdated Show resolved Hide resolved
"""
have_errors = False
if expected_status_int >= 400 and (
expected_status_int < 600):
Copy link
Member

Choose a reason for hiding this comment

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

Don't think you need this second part -- just expected_status_int >= 400 should suffice.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

expected_status_int=200,
expected_content_type='text/html'):
"""Get a response, transformed to a Python object."""
def get_response(
Copy link
Member

Choose a reason for hiding this comment

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

Make private (by prepending underscore)? That ensures it doesn't get used elsewhere -- only by get_html_response etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

get_response is being used by other functions where the content-type is different than html or json -- for example png or text/plain.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I see. Then, I suggest: make get_response private, and define a new get_custom_response that first checks that the content type is not html/json, and that then calls _get_response(). External callers should use get_custom_response(). I think that would be a cleaner API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Args:
url: str. The URL to fetch the response.

params: str or dict. A query string, or a dictionary that will be
Copy link
Member

Choose a reason for hiding this comment

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

See above notes on how to write docstrings and on the idea of making the expected usage of params more explicit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

expected_status_int_list: list(int). A list of integer status
code to expect.

params: str or dict. A query string, or a dictionary that will be
Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"""Get a JSON response, transformed to a Python object."""
have_errors = False
Copy link
Member

Choose a reason for hiding this comment

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

Var name should probably be expect_errors. Currently you are trying to have this variable state whether the test expects an error after the response comes back. It doesn't say whether the response actually has an error or not.

Similar comments might apply elsewhere; please check.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@lilithxxx
Copy link
Contributor Author

Hi @seanlip I made some changes, can you PTAL?

csrf_token = self.get_csrf_token_from_response(response)
summaries = self.get_json(
recommendations_url, params=csrf_token)['summaries']
self.get_html_response('/explore/%s' % exploration_id)
Copy link
Member

Choose a reason for hiding this comment

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

Why do you need this call?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For checking status code and content-type?

Copy link
Member

Choose a reason for hiding this comment

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

Hm. The original purpose of the call was to get the response so you can get the CSRF token so that you can make the subsequent call.

If you don't need the CSRF token then do you actually need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm I think you are right we don't need this now.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

if expected_status_int >= 400 and (
expected_status_int < 600):
have_errors = True
if params:
Copy link
Member

Choose a reason for hiding this comment

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

if params is not None

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

response = self.get_response(download_url,
expected_content_type='text/plain')
response = self.get_custom_response(
download_url, expected_content_type='text/plain')
Copy link
Member

Choose a reason for hiding this comment

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

You don't need to specify the argname for expected_content_type, since it has no default value.

The linter should catch this. Maybe file a bug?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Member

Choose a reason for hiding this comment

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

Please address my last paragraph, either with a link to the issue filed or some explanation for why the linter didn't catch this.

Copy link
Contributor Author

@lilithxxx lilithxxx Jan 6, 2019

Choose a reason for hiding this comment

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

So sorry -- I was looking into this and I think pylint reports when we don't use the argname in case of a default value parameter but not the other way around. @apb7 Can you confirm this? So I think this is to fall under the category of a new feature. I am opening an issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Opened #6067

Copy link
Contributor

Choose a reason for hiding this comment

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

@seanlip @lilithxxx, I can confirm this. Thanks for filling the issue, @lilithxxx.

expected_content_type='image/png')
response = self.get_custom_response(
self._get_image_url('0', filename),
expected_content_type='image/png')
Copy link
Member

Choose a reason for hiding this comment

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

Ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@lilithxxx
Copy link
Contributor Author

@seanlip PTAL

Copy link
Member

@seanlip seanlip left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks @lilithxxx!

@kevinlee12 kevinlee12 merged commit 35c2f13 into oppia:develop Jan 6, 2019
@lilithxxx lilithxxx deleted the remove_testapp branch January 7, 2019 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Reduce code redundancy in backend testing
5 participants