#12211 closed enhancement (fixed)
Base test case classes with helper methods
| Reported by: | Ryan J Ollos | Owned by: | Ryan J Ollos |
|---|---|---|---|
| Priority: | normal | Milestone: | 1.0.11 |
| Component: | general | Version: | |
| Severity: | normal | Keywords: | test |
| Cc: | Branch: | ||
| Release Notes: |
Refactored test cases to use |
||
| API Changes: |
Added |
||
| Internal Changes: | |||
Description
I've been considering ways to reduce the code duplication of helper methods in the test code. For example, there are at least a half-dozen instances of _insert_ticket and _create_request. One possibility would be to create a mixin-like base class with the helper methods.
Attachments (0)
Change History (30)
follow-up: 2 comment:1 by , 10 years ago
comment:2 by , 10 years ago
Replying to Ryan J Ollos:
I'm very interested to know if someone sees a better way to do this.
I've considered a few alternative approaches:
- Create helper functions in
trac.test. - Create helper class(es) in
trac.test. The classes could be instantiated as needed in test cases. This would minimize the number of methods relative to approach (1). - Attach the helper functions to
trac.test.EnvironmentStub. One downside is that the number of methods on the class could grow large very quickly.
My main aim for this ticket is to find an approach that avoids defining _create_request on numerous TestCases.
comment:3 by , 10 years ago
Replaced uses of _insert_user with EnvironmentStub.insert_known_users in [14524]. Additional proposed changes in log:rjollos.git:t12211_known_users. insert_known_users was added in 1.1.x, so it wouldn't be a breaking API change to rename the method.
Continuing the thought in comment:2, it might make sense to define insert_users on a class other than EnvironmentStub.
comment:5 by , 10 years ago
Proposed changes in log:rjollos.git:t12211_mock_request. I'll add class documentation before committing.
comment:7 by , 10 years ago
Thanks, I added TicketTestCase to the changes ([9763655/rjollos.git]) and I'll spend more time reviewing the code in trac.ticket.tests to see what other methods can be added to the class.
comment:8 by , 10 years ago
I modified MockRequest to use Request as a base class and populate the callbacks: log:rjollos.git:t12211_mock_request.1.
comment:9 by , 10 years ago
comment:10 by , 10 years ago
| Resolution: | → fixed |
|---|---|
| Status: | assigned → closed |
comment:11 by , 10 years ago
| Resolution: | fixed |
|---|---|
| Status: | closed → reopened |
After [14635], a TypeError is raised when accessing project index page.
Exception happened during processing of request from ('192.168.11.17', 58881)
Traceback (most recent call last):
File "/usr/lib/python2.6/SocketServer.py", line 560, in process_request_thread
self.finish_request(request, client_address)
File "/usr/lib/python2.6/SocketServer.py", line 322, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python2.6/SocketServer.py", line 617, in __init__
self.handle()
File "/usr/lib/python2.6/BaseHTTPServer.py", line 329, in handle
self.handle_one_request()
File "/home/jun66j5/src/tracdev/git/trac/web/wsgi.py", line 192, in handle_one_request
gateway.run(self.server.application)
File "/home/jun66j5/src/tracdev/git/trac/web/wsgi.py", line 92, in run
response = application(self.environ, self._start_response)
File "trac/web/standalone.py", line 59, in __call__
return self.application(environ, start_response)
File "trac/web/standalone.py", line 91, in __call__
return self.application(environ, start_response)
File "/home/jun66j5/src/tracdev/git/trac/web/main.py", line 461, in dispatch_request
env_paths)
File "/home/jun66j5/src/tracdev/git/trac/web/main.py", line 704, in send_project_index
'time': user_time(req, format_datetime)},
File "/home/jun66j5/src/tracdev/git/trac/util/datefmt.py", line 874, in user_time
kwargs['tzinfo'] = getattr(req, 'tz', None)
File "/home/jun66j5/src/tracdev/git/trac/web/api.py", line 370, in __getattr__
value = self.callbacks[name](self)
TypeError: 'NoneType' object is not callable
We don't set None to Request.callbacks. That must be a callable instance.
comment:12 by , 10 years ago
| Resolution: | → fixed |
|---|---|
| Status: | reopened → closed |
comment:14 by , 10 years ago
Replacement of Mock with MockRequest in [14666,14667,14668]. PEP8 changes in [14669].
comment:15 by , 9 years ago
comment:16 by , 9 years ago
comment:17 by , 8 years ago
Replying to Ryan J Ollos:
I've been considering ways to reduce the code duplication of helper methods in the test code. For example, there are at least a half-dozen instances of
_insert_ticketand_create_request. One possibility would be to create a mixin-like base class with the helper methods.
_insert_ticket will be addressed in #12812.
comment:18 by , 8 years ago
Someone might pass a non-string value in args like {'version': 1}. I propose that we convert all values in the args dict to string:
-
trac/test.py
diff --git a/trac/test.py b/trac/test.py index ed3f1e3d3..381565109 100755
a b def MockRequest(env, **kwargs): 155 155 156 156 if 'arg_list' in kwargs: 157 157 arg_list = kwargs['arg_list'] 158 args = arg_list_to_args( arg_list)158 args = arg_list_to_args([(k, unicode(v)) for k, v in arg_list]) 159 159 else: 160 160 args = _RequestArgs() 161 args.update(kwargs.get('args', {})) 161 args.update({k: unicode(v) 162 for k, v in kwargs.get('args', {}).iteritems()}) 162 163 arg_list = [(name, value) for name in args 163 164 for value in args.getlist(name)]
comment:20 by , 8 years ago
Thanks. Minor change to proposed patch:
-
trac/test.py
commit ab5504de15f2019b36d0af909d0dff93b909cd06 Author: Ryan J Ollos <ryan.j.ollos@gmail.com> Date: Tue Apr 17 15:05:14 2018 -0700 Convert args to string diff --git a/trac/test.py b/trac/test.py index ed3f1e3d3..5d82f7eed 100755a b def MockRequest(env, **kwargs): 154 154 perm = PermissionCache(env, authname) 155 155 156 156 if 'arg_list' in kwargs: 157 arg_list = kwargs['arg_list']157 arg_list = [(k, unicode(v)) for k, v in kwargs['arg_list']] 158 158 args = arg_list_to_args(arg_list) 159 159 else: 160 160 args = _RequestArgs() 161 args.update(kwargs.get('args', {})) 161 args.update({k: unicode(v) 162 for k, v in kwargs.get('args', {}).iteritems()}) 162 163 arg_list = [(name, value) for name in args 163 164 for value in args.getlist(name)] 164 165
comment:21 by , 8 years ago
comment:20 change committed to 1.0-stable in r16498, but there's a problem with the patch. The argument value could be list type, and several tests pass a boolean value. I'll create another patch to handle those cases.
follow-ups: 23 24 comment:22 by , 8 years ago
Travis CI build failed at https://travis-ci.org/edgewall/trac/builds/368426764 but I didn't receive notification mail.
Anyaway, r16498 leads syntax errors on Python 2.5 and 2.6.
...
python ./trac/test.py --skip-functional-tests
File "./trac/test.py", line 162
for k, v in kwargs.get('args', {}).iteritems()})
^
SyntaxError: invalid syntax
make: *** [unit-test] Error 1
comment:23 by , 8 years ago
comment:24 by , 8 years ago
Replying to Jun Omae:
Travis CI build failed at https://travis-ci.org/edgewall/trac/builds/368426764 but I didn't receive notification mail.
A few months back the notifications were delayed for some time (comment:12:ticket:12839). Last notification from trac-builds@ was April 6th. I might suspect the change in comment:13:ticket:12839, but that was working for more than a month.
comment:25 by , 8 years ago
Thanks for comment:23 fix. Here is a patch to handle cases described in comment:21:
-
trac/test.py
commit 92ce4268ea52d2a7685c6ab887bdf42703450ea8 Author: Ryan J Ollos <ryan.j.ollos@gmail.com> Date: Thu Apr 19 15:12:40 2018 -0700 Convert args to string diff --git a/trac/test.py b/trac/test.py index 97df0c345..61e6c6070 100755a b def MockRequest(env, **kwargs): 153 153 else: 154 154 perm = PermissionCache(env, authname) 155 155 156 def convert(val): 157 if isinstance(val, bool): 158 return unicode(int(val)) 159 elif isinstance(val, (list, tuple)): 160 return [unicode(v) for v in val] 161 else: 162 return unicode(val) 163 156 164 if 'arg_list' in kwargs: 157 arg_list = [(k, unicode(v)) for k, v in kwargs['arg_list']]165 arg_list = [(k, convert(v)) for k, v in kwargs['arg_list']] 158 166 args = arg_list_to_args(arg_list) 159 167 else: 160 168 args = _RequestArgs() 161 args.update((k, unicode(v))169 args.update((k, convert(v)) 162 170 for k, v in kwargs.get('args', {}).iteritems()) 163 171 arg_list = [(name, value) for name in args 164 172 for value in args.getlist(name)]
comment:26 by , 8 years ago
comment:25 and additional change committed to 1.0-stable in [16525,16527], merged in [16526,16528], r16530.
comment:27 by , 8 years ago
In [16527], numbers module is not available on Python 2.5.
$ python2.5 Python 2.5.6 (r256:88840, Oct 21 2014, 22:49:55) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import numbers Traceback (most recent call last): File "<stdin>", line 1, in <module> ImportError: No module named numbers
At least, I think we should use numbers.Real rather than numbers.Number because complex number is a numbers.Number (use numbers.Integral if float is not expected).
Python 2.6.9 (default, Oct 22 2014, 19:56:52) [GCC 4.8.2] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import numbers >>> isinstance(complex(1, 2), numbers.Number) True
-
trac/test.py
diff --git a/trac/test.py b/trac/test.py index 001d45107..d5b297d70 100755
a b 20 20 from __future__ import with_statement 21 21 22 22 import doctest 23 import numbers24 23 import os 25 24 import shutil 26 25 import sys … … from trac.web.api import _RequestArgs, Request, arg_list_to_args 49 48 from trac.web.session import Session 50 49 51 50 51 try: 52 from numbers import Real as number_type 53 except ImportError: 54 number_type = (int, long, float) 55 56 52 57 def Mock(bases=(), *initargs, **kw): 53 58 """ 54 59 Simple factory for dummy classes that can be used as replacement for the … … def MockRequest(env, **kwargs): 157 162 def convert(val): 158 163 if isinstance(val, bool): 159 164 return unicode(int(val)) 160 elif isinstance(val, number s.Number):165 elif isinstance(val, number_type): 161 166 return unicode(val) 162 167 elif isinstance(val, (list, tuple)): 163 168 return [convert(v) for v in val]
comment:30 by , 7 years ago
If I was implementing MockRequest now, I would add a property response_sent that returned the value from the buffer, to avoid accessing as req.response_sent.getvalue(). See, for example, what is done here: trunk/trac/web/tests/api.py@16656:106-108#L88. However, Request.response_sent is an attribute and changing it to a property would be an API change.
I suppose we could add an accessor such as get_response_sent(), but I tend to think it is confusing to developers when we have multiple public attributes and functions for accessing the same data.



Example of changes I have in mind can be found in log:rjollos.git:t12211_base_test_classes. I'm very interested to know if someone sees a better way to do this.