2016-11-17 53 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") 
相關問題