在测试由kivy编写的应用程序时如何与UI交互?

时间:2016-11-17 22:03:56

标签: python kivy kivy-language

该申请由kivy撰写。 我想通过pytest测试一个函数,但是为了测试那个函数,我需要首先初始化对象,但是当初始化时对象需要来自UI的东西,但是我处于测试阶段,所以不要知道如何从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()

我无法初始化对象,它会在初始化时抛出AttributeError,因为它需要来自UI的某些值。

所以我卡住了。

我试图模拟对象,然后将函数修补到原始函数,但由于函数本身具有与UI相关的逻辑,因此无法工作。

如何解决?感谢

2 个答案:

答案 0 :(得分:1)

我写了一篇关于使用简单的跑步者KivyUnitTest一起测试Kivy应用程序的文章。它适用于unittest,而不适用于pytest,但它不应该难以重写,以便它符合您的需求。在文章中我解释了如何渗透"用户界面的主要循环,这样你可以愉快地使用按钮:

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()

然而,正如Tomas所说,你应该在可能的情况下分离UI和逻辑,或者更好地说,当它是高效的事情时。您不想模拟整个大型应用程序,只是为了测试需要与UI通信的单个函数。

答案 1 :(得分:0)

最后做到了,只是把事情做好,我认为必须有一个更优雅的解决方案。这个想法很简单,因为除了s.send()语句之外,所有行都只是简单的值赋值。

然后我们只是模拟原始对象,每次在测试阶段弹出一些错误(因为对象缺少UI中的某些值),我们嘲笑它,我们重复这一步,直到测试方法最终测试是否该函数可以处理HTTPError或不处理PhoneNumber

在这个例子中,我们只需要模拟一个幸运的@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") 课程,但有时我们可能需要处理更多,所以很明显@KeyWeeUsr的答案是一个更理想的选择。生产环境。但我只想在这里列出一些想要快速解决方案的人。

<time>