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
Refactor SQL queries in ajax.py to Django queries #232
Conversation
Pull Request Test Coverage Report for Build 749
💛 - Coveralls |
1da30f4
to
a2092c3
Compare
Codecov Report
@@ Coverage Diff @@
## master #232 +/- ##
==========================================
+ Coverage 63.81% 63.96% +0.14%
==========================================
Files 139 143 +4
Lines 10970 11284 +314
Branches 1664 1713 +49
==========================================
+ Hits 7001 7218 +217
- Misses 3494 3570 +76
- Partials 475 496 +21
Continue to review full report at Codecov.
|
7e233c1
to
0f2e327
Compare
tcms/core/ajax.py
Outdated
@@ -225,6 +225,21 @@ def remove(self): | |||
tag = Tag.objects.get(name=self.tag) | |||
self.obj.remove_tag(tag) | |||
|
|||
class TagCounter: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this class out of here and add tests for it.
tcms/core/ajax.py
Outdated
except StopIteration: | ||
return 0 | ||
|
||
return self.counter[self.key] if tag.pk == self.counter['tag'] else 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better use explicit if-then-else construct
86a8b33
to
53a2f80
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is starting to get better but still needs more work. There are also some missing tests:
When given a tag ID which doesn't match the current record from counter iterator,
e.g. trying to get the count for object which isn't inside the query
but the query isn't empty
No test simulating real example, e.g. counting several tags
which may be present inside Case, Plan, Run but not necessarily inside all of them.
Edge case is when counting several tags, e.g. 3 and tag1 is present for
Plan, Case and Run, tag2 only for Plan and Case, and tag3 only for Plan.
this will validate the counter class logic since it deals with iterators
and these iterators can be of different length.
A variation of the above is a test where we have more than 3 tags and they
are not present for all types of objects, thus the counting iterators are
not the same length. However every other tag happens to be assigned to
multiple objects. This will validate that we properly advance to the next
object inside the counting iterator. Indeed this will expose a bug in the current implementation.
tcms/core/ajax.py
Outdated
context_data = { | ||
'tags': tags, | ||
'object': obj, | ||
} | ||
return render(request, template_name, context_data) | ||
|
||
|
||
class _TagCounter: | ||
""" Used for counting the number of times a tags is assigned to TestRun/TestCase/TestPlan """ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tags -> tag
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While at it add doc-strings for both the init method and the calculate_tag_count method. See xmlrpc/api
for examples how to format the description of parameters.
tcms/core/tests/test_ajax.py
Outdated
from tcms.tests import BasePlanCase | ||
from tcms.core.ajax import _TagCounter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move that above the imports of TestCase and TestCaseTag. Tests for the most part follow the convention to import classes and functions from the Python standard library first, then from Django and various other dependencies, then tcms and finally all testing related classes.
tcms/core/tests/test_ajax.py
Outdated
from tcms.tests.factories import TestRunFactory | ||
from tcms.tests.factories import TestCaseFactory | ||
from tcms.tests.factories import TestPlanFactory | ||
from tcms.tests.factories import TagFactory, TestRunFactory, TestCaseFactory, TestPlanFactory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please next time don't make formatting changes if not necessary.
tcms/core/ajax.py
Outdated
|
||
if tag.pk == self.counter['tag']: | ||
return self.counter[self.key] | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the else block and leave the return as the last return statement in the function.
FYI there's a pylint checker that will catch this.
tcms/core/tests/test_ajax.py
Outdated
self.assertEqual(count_for_tag_one, 3) | ||
self.assertEqual(count_for_tag_two, 2) | ||
|
||
def test_tag_count_empty_query(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip: if you are going to test only 1 class/main method as the test class name suggests then you can try naming the testing methods using test_description_of_conditions
. This will help you avoid longer names and repeat the MUT name inside. In this case test_with_empty_query
is shorter and more natural. Try doing this to see how it feels.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip: try using doc-strings with Given-When-Then or simply describe what do you expect this test to do and under what conditions.
In this particular example you are validating that the result is 0 when the QuerySet is empty. In other words code execution terminates after the StopIteration exception on line 274.
Is this what you really wanted ?
tcms/core/tests/test_ajax.py
Outdated
|
||
def test_tag_count_empty_query(self): | ||
test_case_tags = TestCaseTag.objects.filter( | ||
tag__in=[self.tag_one, self.tag_two]).values('tag').annotate( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case you may use tag=-1
or even objects.none()
to save on some typing and multiline indentations.
If the query is empty it doesn't matter how much annotations you make, it is still going to be empty.
tcms/core/ajax.py
Outdated
self.counter = {'tag': 0} | ||
|
||
def calculate_tag_count(self, tag): | ||
if not self.counter['tag'] == tag.id: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use tag.pk instead of id
For the record, I have no idea why coverage doesn't want to include the new class in its reports and this is driving me crazy. |
0803305
to
c3ab772
Compare
tcms/core/ajax.py
Outdated
context_data = { | ||
'tags': tags, | ||
'object': obj, | ||
} | ||
return render(request, template_name, context_data) | ||
|
||
|
||
class _TagCounter: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inherits from (object). Again pylint will catch this I believe.
tcms/core/tests/test_ajax.py
Outdated
tag_one = TagFactory() | ||
tag_two = TagFactory() | ||
tag_three = TagFactory() | ||
tags = [tag_one, tag_two, tag_three] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are already created as class attributes in the setup above, why duplicate the setup ?
tcms/core/ajax.py
Outdated
:return: the number of times a tag is assigned to object | ||
:rtype: int | ||
""" | ||
if not self.counter['tag'] == tag.pk: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not ==
-> !=
c3ab772
to
05a9726
Compare
Removed useless line from ajax.py
05a9726
to
5dd3883
Compare
As discussed in #222 the SQL queries in
ajax.py
needed to be refactored into Django queries.