【问题标题】:What is the right approach to unittest this method in Python?在 Python 中对这种方法进行单元测试的正确方法是什么?
【发布时间】:2022-01-06 08:35:51
【问题描述】:

我的应用程序中有一个抓取模块,它使用 Beautiful Soup 和 Selenium 通过此功能获取网站信息:

def get_page(user: str) -> Optional[BeautifulSoup]:
    """Get a Beautiful Soup object that represents the user profile page in some website"""

    try:
        browser = webdriver.Chrome(options=options)
        wait = WebDriverWait(browser, 10)
        browser.get('https://somewebsite.com/' + user)
        wait.until(EC.presence_of_element_located((By.TAG_NAME, 'article')))
    except TimeoutException:
        print("User hasn't been found. Try another user.")
        return None
    return BeautifulSoup(browser.page_source, 'lxml')

我需要通过两种方式来测试这个功能:

  • 如果是获取页面(成功案例);
  • 如果它正在打印警告并在没有获取任何页面时返回 None(失败情况)。

我试着这样测试:

class ScrapeTests(unittest.TestCase):
    def test_get_page_success(self):
        """
        Test if get_page is getting a page
        """
        self.assertEqual(isinstance(sc.get_page('myusername'), BeautifulSoup), True)

    def test_get_page_not_found(self):
        """
        Test if get_page returns False when looking for a user
        that doesn't exist
        """
        self.assertEqual(sc.get_page('iwçl9239jaçklsdjf'), None)


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

这样做会使测试变慢一些,因为get_page 本身在成功的情况下很慢,在失败的情况下,我在寻找不存在的用户时强制出现超时错误。我的印象是我测试这些功能的方法不是正确的。可能最好的测试方法是伪造响应,所以get_page 不需要连接到服务器并要求任何东西。

所以我有两个问题:

  1. 这种“虚假网络响应”的想法是测试此功能的正确方法吗?
  2. 如果是这样,我该如何实现该功能?我是否需要重写 get_page 函数以使其“可测试”?

编辑:

我尝试像这样为get_page 创建一个测试:

class ScrapeTests(TestCase):
    def setUp(self) -> None:
        self.driver = mock.patch(
            'scrape.webdriver.Chrome',
            autospec=True
        )
        self.driver.page_source.return_value = "<html><head></head><body><article>Yes</article></body></html>"
        self.driver.start()

    def tearDown(self) -> None:
        self.driver.stop()

    def test_get_page_success(self):
        """
        Test if get_page is getting a page
        """
        self.assertEqual(isinstance(sc.get_page('whatever'), BeautifulSoup), True)

我面临的问题是driver.page_source 属性仅在wait.until 函数调用之后创建。我需要wait.until,因为我需要 Selenium 浏览器等待 javascript 在 HTML 中创建 article 标记,以便我抓取它们。

当我尝试在setUp 中定义页面源的返回值时,我收到错误:AttributeError: '_patch' object has no attribute 'page_source'

我尝试了很多方法来使用 mock. patch 模拟 webdriver 属性,但据我所知,这似乎很难。我认为实现我想要的(无需连接到服务器即可测试get_page 功能)的最佳方法可能是模拟整个Web 服务器连接。但这只是猜测。

【问题讨论】:

标签: python unit-testing testing beautifulsoup architecture


【解决方案1】:

我认为你在#1 上是正确的。查看mock 库,它允许您模拟(伪造)函数、方法和类并控制方法调用的结果。这也将消除实际调用的延迟。

根据我的经验,最好专注于测试本地逻辑并模拟任何外部依赖项。如果你这样做,你将有很多小的单元测试,它们将一起测试你的大部分代码。

根据您的更新,尝试:

self.driver = mock.patch_object(webdriver.Chrome, 'page_source', return_value="<html><head></head><body><article>Yes</article></body></html>"

如果这不起作用,那么不幸的是我没有想法。我不可能 Selenium 代码更难模拟。

【讨论】:

  • 感谢您的回复。问题是:我试图模拟 webdriver.Chrome.get 函数,但没有成功。阅读这个问题 (stackoverflow.com/questions/29061125/…) 我看到建议的方法是使用 pytest-localserver 模块来伪造网络服务器连接,而不是直接模拟 Selenium 函数,这似乎更困难。如何使用unittest 而不是pytest 来做到这一点?还是我真的需要依靠pytest 才能做到这一点?
  • 根据我的经验,在模拟时,找到正确的模拟位置可能会有些挑战。我不熟悉 Selenium 框架,所以我不能确切地说出如何去做。在您链接到的 SO 问题中,我没有看到他们实际使用 mock 库 (docs.python.org/3/library/unittest.mock.html),它是 unittest 的一部分。您是否使用了 mock 库,如果使用了,您看到的错误的堆栈跟踪是什么?
  • 是的,我使用 mock.patch 创建了一个测试。请参阅我的问题中的编辑。
  • 在我链接的问题中,他们使用的是pytest,它有一个名为pytest.localserver 的模块,他们伪造了一个服务器并对 Selenium webdriver 提供模拟响应。我认为这可能是一个解决方案,但我现在真的不想将我的项目切换到pytest
  • 在上面查看我的更新答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-01
  • 2019-11-08
  • 1970-01-01
相关资源
最近更新 更多