Opened 9 years ago

Closed 9 years ago

Last modified 9 years ago

#24965 closed Cleanup/optimization (fixed)

Make LiveServerTestCase.live_server_url accessible from class

Reported by: Moritz Sichert Owned by: Moritz Sichert
Component: Testing framework Version: dev
Severity: Normal Keywords: selenium tests live server test case url
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Consider following test:

class MySeleniumTest(LiveServerTestCase):
    @classmethod
    def setUpClass(cls):
        super(MySeleniumTest, cls).setUpClass()
        # initialize selenium driver
        cls.selenium = WebDriver()
        # initialize browser state, live_server_url is needed here
        cls.selenium.get(cls.live_server_url)
        # [...]

Right now this doesn't work, because live_server_url is a property even though it only consists of class attributes.

Since class properties are a bit more complicated I suggest adding a get_live_server_url classmethod and keeping the live_server_url.
An alternative is to deprecate live_server_url but I don't think that's really necessary.

Change History (12)

comment:1 by Moritz Sichert, 9 years ago

Owner: set to Moritz Sichert
Status: newassigned

comment:2 by Moritz Sichert, 9 years ago

Has patch: set

comment:3 by Tim Graham, 9 years ago

Would an approach like this be better than adding a method?

diff --git a/django/test/testcases.py b/django/test/testcases.py
index b17b392..22ab3c1 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -1245,6 +1245,13 @@ class LiveServerThread(threading.Thread):
             self.httpd.server_close()
 
 
+class classproperty(object):
+    def __init__(self, f):
+        self.f = f
+    def __get__(self, obj, owner):
+        return self.f(owner)
+
+
 class LiveServerTestCase(TransactionTestCase):
     """
     Does basically the same as TransactionTestCase but also launches a live
@@ -1259,7 +1266,7 @@ class LiveServerTestCase(TransactionTestCase):
 
     static_handler = _StaticFilesHandler
 
-    @property
+    @classproperty
     def live_server_url(self):
         return 'http://%s:%s' % (
             self.server_thread.host, self.server_thread.port)

Is it okay for the attribute to fail when not using setUpClass? For example:

>>> from django.test.testcases import LiveServerTestCase
>>> LiveServerTestCase.live_server_url
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/home/tim/code/django/django/test/testcases.py", line 1252, in __get__
    return self.f(owner)
  File "/home/tim/code/django/django/test/testcases.py", line 1272, in live_server_url
    self.server_thread.host, self.server_thread.port)
AttributeError: type object 'LiveServerTestCase' has no attribute 'server_thread'

comment:4 by Moritz Sichert, 9 years ago

Yes that would be better and I thought about it, but I didn't want to add a new decorator just for one property.
Should the classproperty go into some utils module then?

I don't see were else it would make sense to use, so I guess that's ok.

comment:5 by Tim Graham, 9 years ago

Patch needs improvement: set
Triage Stage: UnreviewedAccepted

django.utils.decorators seems like a good candidate.

comment:6 by Moritz Sichert, 9 years ago

Patch needs improvement: unset

Updated PR.
Is some additional documentation needed?

comment:7 by Tim Graham, 9 years ago

I don't think so. A simple test for live_server_url being accessible as a class property might be useful if it's easy.

comment:8 by Moritz Sichert, 9 years ago

How should I do that?
I could do something like

class TestLiveServerTestCase(LiveServerTestCase):
    @classmethod
    def setUpClass(cls):
        super(TestLiveServerTestCase, cls).setUpClass()
        if isinstance(cls.live_server_url, str):
            cls._test_worked = True
        else:
            cls._test_worked = False

    def test_worked(self):
        self.assertTrue(self._test_worked)

But that feels really hackish.
And instanciating LiveServerTestCase inside a SimpleTestCase isn't that trivial, I think.

comment:9 by Tim Graham, 9 years ago

Right, I was thinking something like this:

  • tests/servers/tests.py

    diff --git a/tests/servers/tests.py b/tests/servers/tests.py
    index e84b878..0636a13 100644
    a b import socket  
    99
    1010from django.core.exceptions import ImproperlyConfigured
    1111from django.test import LiveServerTestCase, override_settings
     12from django.utils import six
    1213from django.utils._os import upath
    1314from django.utils.http import urlencode
    1415from django.utils.six.moves.urllib.error import HTTPError
    class LiveServerAddress(LiveServerBase):  
    7172        else:
    7273            del os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS']
    7374
     75        cls.live_server_url_type = type(cls.live_server_url)
     76
    7477    @classmethod
    7578    def tearDownClass(cls):
    7679        # skip it, as setUpClass doesn't call its parent either
    class LiveServerAddress(LiveServerBase):  
    9295        # test runner and the overridden setUpClass() method is executed.
    9396        pass
    9497
     98    def test_live_server_url_is_class_property(self):
     99        self.assertTrue(issubclass(self.live_server_url_type, six.text_type))
     100
    95101
    96102class LiveServerViews(LiveServerBase):
    97103    def test_404(self):

comment:10 by Moritz Sichert, 9 years ago

Updated PR, added this test and some documentation.

comment:11 by Tim Graham <timograham@…>, 9 years ago

Resolution: fixed
Status: assignedclosed

In 296919e:

Fixed #24965 -- Made LiveServerTestCase.live_server_url accessible from class

comment:12 by Tim Graham <timograham@…>, 9 years ago

In d58573e6:

Refs #24965 -- Added changes from accidentally reverted file for last commit.

Note: See TracTickets for help on using tickets.
Back to Top