flask 第十章执行单元测试报错 AssertionError: <Flask 'watchlist'> is not None

希望大家能够帮帮我,已经被这个问题搞得彻夜难眠了 Orz
这是我的程序目录结构:

微信图片_20230304092539

程序执行没有问题

但就是在执行单元测试 test.py 的时候报以下错误:

C:\Users\Administrator\AppData\Local\Programs\Python\Python39\python.exe "E:\PyCharm 2021.1.1\plugins\python\helpers\pycharm\_jb_unittest_runner.py" --target test.WatchList
Testing started at 9:21 ...
Launching unittests with arguments python -m unittest test.WatchList in E:\PycharmProjects\projects\my_flask


Failure
Traceback (most recent call last):
  File "E:\PycharmProjects\projects\my_flask\test.py", line 40, in test_app_exist
    self.assertIsNone(app)
AssertionError: <Flask 'watchlist'> is not None


Failure
Traceback (most recent call last):
  File "E:\PycharmProjects\projects\my_flask\test.py", line 79, in test_createitem
    self.assertNotIn('Item created.', data)
AssertionError: 'Item created.' unexpectedly found in '<!DOCTYPE html>\n<html lang="en">\n<head>\n    \n    <meta charset="utf-8">\n    <!--添加了一个新的 <meta> 元素,这个元素会设置页面的视口,让页面根据设备的宽度来自动缩放页面,\n    这样会让移动设备拥有更好的浏览体验\n    -->\n    <meta name="viewport" content="width=device-width, initial-scale=1.0">\n    <title>Watchlist</title>\n    <link rel="icon" href="/static/favicon.ico">\n    <link rel="stylesheet" href="/static/style.css" type="text/css">\n    \n</head>\n<body>\n<!-- 插入到页面标题上方 -->\n    \n    <div class="alert">Item created.</div>\n    \n    <h2>\n        \n            <img alt="Avatar" class="avatar" src="/static/images/avatar.png">\n            Test\'s Watchlist\n        \n    </h2>\n    <!--\n    添加了一个导航栏\n    -->\n    <nav>\n        <ul>\n            \n                <li><a href="/">Home</a></li>\n                <li><a href="/settings">Settings</a></li>\n                <li><a href="/logout">Logout</a></li>\n\n        </ul>\n    </nav>\n    \n<p>2 Titles</p>\n\n<form method="post">\n    Name <input type="text" name="title" autocomplete="off" required>\n    Year <input type="text" name="year" autocomplete="off" required>\n    <input class="btn" type="submit" name="submit" value="Add">\n</form>\n\n<ul class="movie-list">\n    \n    <li>Test Movie Title - 2019\n        <span class="float-right">\n            <a class="imdb" href="https://movie.douban.com/subject_search?search_text=Test Movie Title" target="_blank" title="Find this movie on 豆瓣">search</a>\n        </span>\n        <span class="float-right">\n            \n            <a class="btn" href="/movie/edit/1">Edit</a>\n            <form class="inline-form" method="post" action="/movie/delete/1">\n                <input class="btn" type="submit" name="delete" value="Delete" onclick="return confirm(\'Are you sure?\')">\n            </form>\n            \n        </span>\n\n\n    </li>\n    \n    <li>New Movie - 2019\n        <span class="float-right">\n            <a class="imdb" href="https://movie.douban.com/subject_search?search_text=New Movie" target="_blank" title="Find this movie on 豆瓣">search</a>\n        </span>\n        <span class="float-right">\n            \n            <a class="btn" href="/movie/edit/2">Edit</a>\n            <form class="inline-form" method="post" action="/movie/delete/2">\n                <input class="btn" type="submit" name="delete" value="Delete" onclick="return confirm(\'Are you sure?\')">\n            </form>\n            \n        </span>\n\n\n    </li>\n    \n</ul>\n<img alt="Walking Totoro" class="totoro" src="/static/images/totoro.gif" title="to~to~ro~">\n\n\n</body>\n</html>'


Failure
Traceback (most recent call last):
  File "E:\PycharmProjects\projects\my_flask\test.py", line 58, in test_index_page
    self.assertIn('Test\'s Watchlist', data)
AssertionError: "Test's Watchlist" not found in '<!DOCTYPE html>\n<html lang="en">\n<head>\n    \n    <meta charset="utf-8">\n    <!--添加了一个新的 <meta> 元素,这个元素会设置页面的视口,让页面根据设备的宽度来自动缩放页面,\n    这样会让移动设备拥有更好的浏览体验\n    -->\n    <meta name="viewport" content="width=device-width, initial-scale=1.0">\n    <title>Watchlist</title>\n    <link rel="icon" href="/static/favicon.ico">\n    <link rel="stylesheet" href="/static/style.css" type="text/css">\n    \n</head>\n<body>\n<!-- 插入到页面标题上方 -->\n    \n    <h2>\n        \n            <img alt="Avatar" class="avatar" src="/static/images/avatar.png">\n            Admin\'s Watchlist\n        \n    </h2>\n    <!--\n    添加了一个导航栏\n    -->\n    <nav>\n        <ul>\n            \n                <li><a href="/login">Login</a></li>\n            \n        </ul>\n    </nav>\n    \n<p>1 Titles</p>\n\n<ul class="movie-list">\n    \n    <li>Test Movie Title - 2019\n        <span class="float-right">\n            <a class="imdb" href="https://movie.douban.com/subject_search?search_text=Test Movie Title" target="_blank" title="Find this movie on 豆瓣">search</a>\n        </span>\n        <span class="float-right">\n            \n        </span>\n\n\n    </li>\n    \n</ul>\n<img alt="Walking Totoro" class="totoro" src="/static/images/totoro.gif" title="to~to~ro~">\n\n\n</body>\n</html>'


Failure
Traceback (most recent call last):
  File "E:\PycharmProjects\projects\my_flask\test.py", line 139, in test_login
    self.assertIn('Login success.', data)
AssertionError: 'Login success.' not found in '<!DOCTYPE html>\n<html lang="en">\n<head>\n    \n    <meta charset="utf-8">\n    <!--添加了一个新的 <meta> 元素,这个元素会设置页面的视口,让页面根据设备的宽度来自动缩放页面,\n    这样会让移动设备拥有更好的浏览体验\n    -->\n    <meta name="viewport" content="width=device-width, initial-scale=1.0">\n    <title>Watchlist</title>\n    <link rel="icon" href="/static/favicon.ico">\n    <link rel="stylesheet" href="/static/style.css" type="text/css">\n    \n</head>\n<body>\n<!-- 插入到页面标题上方 -->\n    \n    <div class="alert">Login success</div>\n    \n    <h2>\n        \n            <img alt="Avatar" class="avatar" src="/static/images/avatar.png">\n            Test\'s Watchlist\n        \n    </h2>\n    <!--\n    添加了一个导航栏\n    -->\n    <nav>\n        <ul>\n            \n                <li><a href="/">Home</a></li>\n                <li><a href="/settings">Settings</a></li>\n                <li><a href="/logout">Logout</a></li>\n\n        </ul>\n    </nav>\n    \n<p>1 Titles</p>\n\n<form method="post">\n    Name <input type="text" name="title" autocomplete="off" required>\n    Year <input type="text" name="year" autocomplete="off" required>\n    <input class="btn" type="submit" name="submit" value="Add">\n</form>\n\n<ul class="movie-list">\n    \n    <li>Test Movie Title - 2019\n        <span class="float-right">\n            <a class="imdb" href="https://movie.douban.com/subject_search?search_text=Test Movie Title" target="_blank" title="Find this movie on 豆瓣">search</a>\n        </span>\n        <span class="float-right">\n            \n            <a class="btn" href="/movie/edit/1">Edit</a>\n            <form class="inline-form" method="post" action="/movie/delete/1">\n                <input class="btn" type="submit" name="delete" value="Delete" onclick="return confirm(\'Are you sure?\')">\n            </form>\n            \n        </span>\n\n\n    </li>\n    \n</ul>\n<img alt="Walking Totoro" class="totoro" src="/static/images/totoro.gif" title="to~to~ro~">\n\n\n</body>\n</html>'



Ran 15 tests in 2.988s

FAILED (failures=4)




Process finished with exit code 1

以下是我的单元测试的源码:

import unittest

from watchlist import app, db
from watchlist.models import Movie, User
from watchlist.commands import forge, dbinit


class WatchList(unittest.TestCase):
    app.app_context().push()  # 推送应用上下文环境

    def setUp(self):

        # 更新配置
        app.config.update(
            TESTING=True,
            SQLALCHEMY_DATABASE_URI='sqlite:///:memory:'
        )

        # 创建数据库和表
        db.create_all()

        # 创建测试数据,一个用户一个电影条目
        user = User(name='Test', username='test')
        user.set_password('123')
        movie = Movie(title='Test Movie Title', year='2019')

        # 使用 add_all() 方法一次添加多个模型类实例,传入列表
        db.session.add_all([user, movie])
        db.session.commit()

        self.client = app.test_client() # 创建测试客户端
        self.runner = app.test_cli_runner()  # 创建测试命令运行器

    def tearDown(self):
        db.session.remove()  # 清除数据库会话
        db.drop_all()  # 删除数据库表

    # 测试程序实例是否存在
    def test_app_exist(self):
        self.assertIsNone(app)

    # 测试程序是否处于测试模式
    def test_app_is_testing(self):
        self.assertTrue(app.config['TESTING'])

    # 测试 404 页面
    def test_404page(self):
        response = self.client.get('/errorpage')  # 传入目标 URL
        data = response.get_data(as_text=True)
        self.assertIn('Page Not Found - 404', data)
        self.assertIn('Go Back', data)
        self.assertEqual(response.status_code, 404)  # 判断响应状态码

    # 测试主页
    def test_index_page(self):
        response = self.client.get('/')
        data = response.get_data(as_text=True)
        self.assertIn('Test\'s Watchlist', data)
        self.assertIn('Test Movie Title', data)
        self.assertEqual(response.status_code, 200)

    # 辅助方法,用于登入用户
    def login(self):
        self.client.post('/login',
            data=dict(username='test',
                      password='123'),
                         follow_redirects=True)

    # 测试创建条目操作
    def test_createitem(self):
        self.login()
        response = self.client.post('/', data=dict(title='New Movie', year='2019'), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertIn('Item created.', data)
        self.assertIn('New Movie', data)

        # 电影标题为空情况
        response = self.client.post('/', data=dict(title='', year='2019'), follow_redirects=True)
        self.assertNotIn('Item created.', data)
        self.assertIn('Invalid input.', data)

        # 电影年份为空情况
        response = self.client.post('/', data=dict(title='New Movie', year=''), follow_redirects=True)
        self.assertNotIn('Item created.', data)
        self.assertIn('Invalid input.', data)

    # 更新条目操作
    def test_updateitem(self):
        self.login()

        # 测试更新页面
        response = self.client.get('/movie/edit/1')
        data = response.get_data(as_text=True)
        self.assertIn('Edit item', data)
        self.assertIn('Test Movie Title', data)
        self.assertIn('2019', data)

        # 更新条目
        response = self.client.post('/movie/edit/1', data=dict(title='New Movie Edited', year='2019'), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertIn('Item updated.', data)
        self.assertIn('New Movie Edited', data)

        # 更新条目但电影标题为空
        response = self.client.post('/movie/edit/1', data=dict(title='', year='2019'), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertNotIn('Item updated.', data)
        self.assertIn('Invalid input.', data)

        # 更新条目但电影年份为空
        response = self.client.post('/movie/edit/1', data=dict(title='New Movie Edited', year=''), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertNotIn('Item updated.', data)
        self.assertNotIn('New Movie Edited Again', data)
        self.assertIn('Invalid input.', data)

    # 测试删除条目
    def test_deleteitem(self):
        self.login()
        response = self.client.post('/movie/delete/1', follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertIn('Item deleted.', data)
        self.assertNotIn('Test Movie Title', data)

    # 测试登录保护
    def test_login_protect(self):
        response = self.client.get('/')
        data = response.get_data(as_text=True)
        self.assertNotIn('Logout', data)
        self.assertNotIn('Settings', data)
        self.assertNotIn('<form method="post">', data)
        self.assertNotIn('Delete', data)
        self.assertNotIn('Edit', data)

    # 测试登录
    def test_login(self):
        response = self.client.post('/login',data=dict(username='test', password='123'), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertIn('Login success.', data)
        self.assertNotIn('Logout', data)
        self.assertIn('Settings', data)
        self.assertIn('Delete', data)
        self.assertIn('Edit', data)
        self.assertIn('<form method="post">', data)

        # 测试使用错误的密码登录
        response = self.client.post('/login',data=dict(username='test', password='456'), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertNotIn('Login success.', data)
        self.assertIn('Invalid username or password.', data)

        # 测试使用错误的用户名登录
        response = self.client.post('/login',data=dict(username='hehe', password='123'), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertNotIn('Login success.', data)
        self.assertIn('Invalid username or password.', data)

        # 测试使用空用户名登录
        response = self.client.post('/login',data=dict(username='', password='123'), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertNotIn('Login success.', data)
        self.assertIn('Invalid username or password.', data)

        # 测试使用空密码登录
        response = self.client.post('/login', data=dict(username='test', password=''), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertNotIn('Login success.', data)
        self.assertIn('Invalid username or password.', data)

    # 测试登出
    def test_logout(self):
        self.login()
        response = self.client.get('/logout', follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertIn('Bye!', data)
        self.assertNotIn('Logout', data)
        self.assertNotIn('Settings', data)
        self.assertNotIn('Delete', data)
        self.assertNotIn('Edit', data)
        self.assertNotIn('<form method="post">', data)

    # 测试设置
    def test_settingd(self):
        self.login()

        # 测试设置页面
        response = self.client.get('/settings')
        data = response.get_data(as_text=True)
        self.assertIn('Settings', data)
        self.assertIn('Your Name', data)

        # 测试更新设置
        response = self.client.post('/settings',data=dict(name='Edison'), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertIn('Settings updated.', data)
        self.assertIn('Edison', data)

        # 测试更新设置,名称为空
        response = self.client.post('/settings', data=dict(name=''), follow_redirects=True)
        data = response.get_data(as_text=True)
        self.assertNotIn('Settings updated.', data)
        self.assertIn('Invalid input.', data)


    # 测试虚拟数据增加
    def test_forge(self):
        result = self.runner.invoke(forge)
        self.assertIn('Done.', result.output)
        self.assertNotEqual(Movie.query.count(), 0)

    # 测试初始化数据库
    def test_dbinit(self):
        result = self.runner.invoke(dbinit)
        self.assertIn('Initialized database.', result.output)

    # 测试生成管理员账户
    def test_admin(self):
        db.drop_all()
        db.create_all()

        result = self.runner.invoke(args=['admin', '--username', 'root', '--password', '123'])
        self.assertIn('Creating user...', result.output)
        self.assertIn('Done.', result.output)
        self.assertEqual(User.query.count(), 1)

    # 测试更新管理员账户
    def test_admin_command_update(self):
        # 使用 args 参数给出完整的命令参数列表
        result = self.runner.invoke(args=['admin', '--username', 'peter', '--password', '456'])
        self.assertIn('Updating user...', result.output)
        self.assertIn('Done.', result.output)
        self.assertEqual(User.query.count(), 1)
        self.assertEqual(User.query.first().username, 'peter')
        self.assertTrue(User.query.first().validate_password('456'))


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

只是几个测试没过而已,用不着失眠吧……

你的问题主要是不够细心。四个测试,分别问题如下:

  1. AssertionError: <Flask ‘watchlist’> is not None…

你的测试代码使用了 self.assertIsNone(app),是在验证 app 为 None 哎。应该改成 self.assertIsNotNone(app)

  1. AssertionError: ‘Item created.’ unexpectedly found…

我理解你是想测试这句提示存在,那就应该用 self.assertIn('Item created.', data) 而不是 self.assertNotIn('Item created.', data)

  1. AssertionError: “Test’s Watchlist” not found…

从截图可以看出来页面上是 Admin's Watchlist

  1. AssertionError: ‘Login success.’ not found…

从错误输出的页面内容可以看出来你 flash 的消息是 Login success,没有句点。

谢谢 Grey!
在帖子过审的这段时间里我仔细检查了一下自己的代码,是我代码敲错了,在帖子发出之前已经成功解决啦

1 个赞