【问题标题】:Testing Flask WTForms validators without repeating code在不重复代码的情况下测试 Flask WTForms 验证器
【发布时间】:2018-10-27 15:11:39
【问题描述】:

我使用 Flask 和 WTForms 以及标准和自定义表单验证器编写了一个相对简单的 Web 应用程序。我现在正在尝试学习 Python 测试。我编写了以下可与 unittest 配合使用的 tests.py 文件,但具有冗余性。有没有更好、更有效、更 Pythonic (DNRY) 的方法来做到这一点?我看过一些 pytest 教程,想知道固定装置是否会有所帮助,但我不太理解它们。而且我认为有一种更 Pythonic 的方式可以使用 unittest 来做到这一点。看来我需要一个可以传递不同 dict 参数的方法,但我不知道该怎么做。

from fly_app import app
import unittest

class FlaskTestCase(unittest.TestCase):

    def test_city_code(self):
        tester = app.test_client(self)
        response = tester.post('/flight_search/extensive', data=dict(origin="xxxx"))
        self.assertIn(b'That does not appear to be a valid city code', response.data)

    def test_code_pairs(self):
        tester = app.test_client(self)
        response = tester.post('/flight_search/extensive', data=dict(origin="HFD", destination="CAS"))
        self.assertIn(b'This origin-destination pair is not in searchable cache', response.data)

    def test_duration_range(self):
        tester = app.test_client(self)
        response = tester.post('/flight_search/extensive', data=dict(origin="MSP", destination="NYC", min_duration=20))
        self.assertIn(b'Number must be between 1 and 15', response.data)

    def test_duration_integer(self):
        tester = app.test_client(self)
        response = tester.post('/flight_search/extensive', data=dict(origin="MSP", destination="NYC", min_duration='abc'))
        self.assertIn(b'Not a valid integer value', response.data)

    def test_duration_pair(self):
        tester = app.test_client(self)
        response = tester.post('/flight_search/extensive', data=dict(origin="MSP", destination="NYC", min_duration=10, max_duration=7))
        self.assertIn(b'Maximum trip length cannot be less than minimum', response.data)

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

【问题讨论】:

    标签: python flask python-unittest flask-wtforms wtforms


    【解决方案1】:

    我建议您创建一个自定义断言方法 (assertExtensiveFlightSearchGivesError(self, data, expected_error_message)) 以删除一些重复项:

    class FlaskTestCase(unittest.TestCase):
        def assertExtensiveFlightSearchGivesError(self, data, expected_error_message):
            response = test_client.post("/flight_search/extensive", data=data)
            self.assertEqual(4xx, response.status_code)
            self.assertIn(expected_error_message, response.data, "extensive flight search did not contain expected error message")
    
        def test_city_code(self):
            self.assertExtensiveFlightSearchGivesError(
                {'origin': 'xxx'},
                b'That does not appear to be a valid city code'
            )
    
        def test_code_pairs(self):
            self.assertExtensiveFlightSearchGivesError(
                {'origin': "HFD", 'destination': "CAS"},
                b'This origin-destination pair is not in searchable cache'
            )
    
        # ... and so on
    

    您也可以将所有测试用例收集在一起并使用.subTest()

    TEST_CASES = [
        ({'origin': 'xxx'}, b'That does not appear to be a valid city code'),
        ({'origin': "HFD", 'destination': "CAS"}, b'This origin-destination pair is not in searchable cache')
    ]
    
    class FlaskTestCase2(unittest.TestCase):
        def assertExtensiveFlightSearchGivesError(self, data, expected_error_message):
            response = test_client.post("/flight_search/extensive", data=data)
            self.assertEqual(4xx, response.status_code)
            self.assertIn(expected_error_message, response.data, "extensive flight search did not contain expected error message")
    
        def test_all_error_cases(self):
            for data, expected_error_message in TEST_CASES:
                with self.subTest():
                    self.assertExtensiveFlightSearchGivesError(data, expected_error_message)
    

    但在您的情况下,IMO 最上面的代码示例更清晰。

    【讨论】:

    • 我在第一个示例中实现了您的代码,并且只需稍作更改即可工作:response = app.test_client().post(...) self.assertEqual(200, response.status_code) #I do not understand your intent with '4xx'. This seemed to break code, even if I changed it to 404 你能解释一下'4xx'吗? {'origin': 'xxxX'} # 4th 'x' is needed although not in caps because 'XXX' is a valid IATA city code 谢谢。我也可以玩你的第二个答案。知道为什么自定义断言方法不在unittest documentation 中吗?
    • 4xx 仅表示“一些 http 客户端错误”,因为这很可能是您的应用在这些情况下返回的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-05
    相关资源
    最近更新 更多