2016-11-17 55 views
1

这个应用程序是由kivy编写的。 我想测试通过pytest的功能,但为了测试功能,我需要先initalize对象,但initalizing当对象需要从UI的东西,但我在测试阶段,所以不知道如何从UI中检索某些内容。如何在测试由kivy编写的应用程序时与UI进行交互?

这是具有一个错误并已处理

class SaltConfig(GridLayout): 
    def check_phone_number_on_first_contact(self, button): 
     s = self.instanciate_ServerMsg(tt) 

     try: 
      s.send() 
     except HTTPError as err: 
      print("[HTTPError] : " + str(err.code)) 
      return 

     # some code when running without error 

    def instanciate_ServerMsg(): 
     return ServerMsg() 

这是助手类,其生成由所述前级中使用的ServerMsg对象的类。

class ServerMsg(OrderedDict): 
    def send(self,answerCallback=None): 
     #send something to server via urllib.urlopen 

这是我的测试代码:

class TestSaltConfig: 
    def test_check_phone_number_on_first_contact(self): 
     myError = HTTPError(url="http://127.0.0.1", code=500, 
         msg="HTTP Error Occurs", hdrs="donotknow", fp=None) 

    mockServerMsg = mock.Mock(spec=ServerMsg) 
    mockServerMsg.send.side_effect = myError 

    sc = SaltConfig(ds_config_file_missing.data_store) 

    def mockreturn(): 
     return mockServerMsg 

    monkeypatch.setattr(sc, 'instanciate_ServerMsg', mockreturn) 
    sc.check_phone_number_on_first_contact() 

我不能初始化对象,它会initialzing时,因为它需要从UI的一些值抛出一个AttributeError。

所以我会被卡住。

我试图嘲弄那么对象修补功能,原来的一个,但不会工作,要么与UI,因为函数本身具有逻辑。

如何解决?由于

+1

听起来像一个设计缺陷。逻辑不应该依赖于UI。其中一个原因是,你可以单独测试它。 –

回答

1

我做了有关测试的文章Kivy用一个简单的亚军在一起的应用程序 - KivyUnitTest。它适用于unittest,而不是pytest,但它不应该很难重写它,以便它符合您的需求。在这篇文章中我将解释如何“渗透” UI的主循环,并通过这种方式,你可以愉快地去和按钮做这:

button = <button you found in widget tree> 
button.dispatch('on_release') 

等等。基本上你可以用这样的测试来做任何事情,而且你不需要独立地测试每个功能。我的意思是......这是一个很好的做法,但有时(主要是在测试用户界面时),你不能把这些东西撕掉,并把它放到一个很好的50行测试中。

这样你就可以做同样的事,作为一个普通用户使用你的应用程序时,会做,所以你甚至可以赶上你必须测试的休闲方式例如当麻烦问题一些奇怪/意外的用户行为。

这里的骨架:

import unittest 

import os 
import sys 
import time 
import os.path as op 
from functools import partial 
from kivy.clock import Clock 

# when you have a test in <root>/tests/test.py 
main_path = op.dirname(op.dirname(op.abspath(__file__))) 
sys.path.append(main_path) 

from main import My 


class Test(unittest.TestCase): 
    def pause(*args): 
     time.sleep(0.000001) 

    # main test function 
    def run_test(self, app, *args): 
     Clock.schedule_interval(self.pause, 0.000001) 

     # Do something 

     # Comment out if you are editing the test, it'll leave the 
     # Window opened. 
     app.stop() 

    def test_example(self): 
     app = My() 
     p = partial(self.run_test, app) 
     Clock.schedule_once(p, 0.000001) 
     app.run() 

if __name__ == '__main__': 
    unittest.main() 

然而,正如托马斯说,你应该尽可能更好地分离UI和逻辑,或者说,当它是一个高效的事情。您不想嘲笑整个大型应用程序只是为了测试需要与UI进行通信的单个功能。

0

终于成功了,只是把事情做好,我觉得必须有一个更好的解决方案。这个想法很简单,因为除了s.send()声明之外,所有行都只是简单的赋值。

然后,我们只是嘲笑原始对象,每当测试阶段出现一些错误(因为对象缺少来自UI的一些值),我们嘲笑它,我们重复这一步直到测试方法最终可以测试如果该功能可以处理HTTPError或不。

在这个例子中,我们只需要模拟一个PhoneNumber这个幸运的类,但有时我们可能需要处理更多,所以显然@KeyWeeUsr的回答对于生产环境来说是一个更理想的选择。但是,我只是想把我的想法列在一个想要快速解决方案的人身上。

@pytest.fixture 
def myHTTPError(request): 
    """ 
    Generating HTTPError with the pass-in parameters 
    from pytest_generate_tests(metafunc) 
    """ 
    httpError = HTTPError(url="http://127.0.0.1", code=request.param, 
          msg="HTTP Error Occurs", hdrs="donotknow", fp=None) 
    return httpError 

class TestSaltConfig: 
    def test_check_phone_number(self, myHTTPError, ds_config_file_missing): 
     """ 
     Raise an HTTP 500 error, and invoke the original function with this error. 
     Test to see if it could pass, if it can't handle, the test will fail. 
     The function locates in configs.py, line 211 
     This test will run 2 times with different HTTP status code, 404 and 500 
     """ 

     # A setup class used to cover the runtime error 
     # since Mock object can't fake properties which create via __init__() 
     class PhoneNumber: 
      text = "610274598038" 

     # Mock the ServerMsg class, and apply the custom 
     # HTTPError to the send() method 
     mockServerMsg = mock.Mock(spec=ServerMsg) 
     mockServerMsg.send.side_effect = myHTTPError 

     # Mock the SaltConfig class and change some of its 
     # members to our custom one 
     mockSalt = mock.Mock(spec=SaltConfig) 
     mockSalt.phoneNumber = PhoneNumber() 
     mockSalt.instanciate_ServerMsg.return_value = mockServerMsg 
     mockSalt.dataStore = ds_config_file_missing.data_store 

     # Make the check_phone_number_on_first_contact() 
     # to refer the original function 
     mockSalt.check_phone_number = SaltConfig.check_phone_number 

     # Call the function to do the test 
     mockSalt.check_phone_number_on_first_contact(mockSalt, "button") 
相关问题