测试

未匹配的标注
本文档最新版为 4.0,旧版本可能放弃维护,推荐阅读最新版!

测试#

介绍#

Masonite 测试非常简单。您只需使用 Masonite 单元测试类扩展您的类,即可轻松测试代码中非常复杂的部分。

Masonite 的测试集合是基于 unittest 框架的,尽管它使用 pytest 来运行测试用例。所以您可以使用 unittest 语法进行编写用例,使用 pytest 运行测试用例。所有语法遵循 camelCase 而不是 PEP 8 under_score。为保持与 unittest 标准一致, 运行中调用的所有 TestCase 方法遵循 camelCase 形式。

正常测试应该使用下划线连接并以 test_开头, 如下所示:

def test_user_can_login(self):
    pass

配置#

首先,在测试目录下创建一个新的测试。您可以通过以下命名创建测试:

$ craft test User

这将为我们创建一个可以运行的用户测试,您可以将测试拖到您喜欢的任何目录中。

以下为上述命令创建的基本用例:

tests/test\user.py

"""Example Testcase."""

from masonite.testing import TestCase

class TestUser(TestCase):

    transactions = True

    def setUp(self):
        super().setUp()

    def setUpFactories(self):
        pass

用例准备就绪,您可以开始进行测试了。继续阅读以了解如何开始构建测试用例。

环境#

大多数时候,您想在不同的数据库上进行开发和测试。也许您是在本地 MySQL 数据库上开发的,但是您的测试要在 SQLlite 数据库中运行。

您可以创建一个.env.testing 文件,并将所有数据库配置放入其中。当 Pytest 运行时,它将另外加载并覆盖任何其他环境变量。

.env.testing 配置文件看起来类似如下:

DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_DATABASE=masonite.db
DB_LOG=False

STRIPE_CLIENT=test_sk-9uxaxixjsxjsin
STRIPE_SECRET=test_sk-suhxs87cen88h7

你可以加载任何测试环境变量。默认它们并不会被版本控制提交。

断言方法#

以下是可以用来断言的方法。

所有的方法均以 assert 开发,可以进行链式调用来多次断言。所有其他方法都将返回布尔值或者值,以便你可以用来自行验证。

assertContains(value) assertHasAmount(amount) assertHeaderIs(key, value)
assertNotFound() assertNotHasAmount(amount) assertPathIs(value)
assertHasJson(key, value) assertParameterIs(parameter, value) isNamed(name)
assertJsonContains(key, value) assertIsStatus(code) hasMiddleware(*middleware)
assertCount(number) assertHasHeader(name) assertNotHasHeader(name) hasController(name)
contains(value) ok() canView()
hasJson(key, value) count(number) amount(number)
isGet() isPost() isPut()
isPatch() isDelete() hasSession(key, value)
parameterIs() headerIs()

执行路由#

存有几个方式用来测试路由。

测试路由是否存在#

要检查路由是否存在,我们可以见到使用 get 或者 post 方法:

tests/test_unit.py

def test_route_exists(self):
    self.assertTrue(self.get('/testing'))
    self.assertTrue(self.post('/testing'))

获取 Request 或者 Response 对象#

通过访问请求路由的 request 或者 response 属性来访问请求和响应对象。response 属性呈现为字符串内容:

def test_request_and_response(self):
    request = self.get('/testing').request # <masonite.request.Request>
    response = self.get('/testing').response # HTML code

请求方法#

你可以选择任意请求方法:

def test_route_exists(self):
    self.get('/testing')
    self.post('/testing')
    self.put('/testing')
    self.patch('/testing')
    self.delete('/testing')

JSON 请求#

你可以使用标准的 JSON 请求,通过 json() 方法实现:

def test_route_exists(self):
    self.json('POST', '/testing', {'user': 'Joe'})

测试路由名字#

tests/test_unit.py

def test_route_has_the_correct_name(self):
    self.assertTrue(self.get('/testing').isNamed('testing.route'))

测试路由是否包含中间件#

tests/test_unit.py

def test_route_has_route_middleware(self):
    assert self.get('/testing').hasMiddleware('auth', 'owner')

测试路由内容是否包含字符串#

它可以用来检查模板是否返回了指定的内容。

tests/test_unit.py

def test_view_contains(self):
    assert self.get('/login').contains('Login Here')

也可以使用:

self.get('/login').assertContains('Login Here')

检查是否返回 200 状态码#

你可以通过 ok 方法来检查相应是否正常:

tests/test_unit.py

def test_view_is_ok(self):
    assert self.get('/testing').ok()

CSRF 保护#

默认,所有的路由调用都会进行 CSRF 保护。测试代码会绕过该保护。

这很有必要,这样你就不用操心每次请求设置 CSRF tokens 了,如果你需要打开该特性的话,通过在测试中用 withCsrf() 方法来实现。

def test_csrf(self):
    self.withCsrf()

    self.post('/unit/test/json', {'test': 'testing'})

这将会在指定测试方法中打开,如果你想在所有测试方法打开。你可以通过在 setUp() 方法调用该方法实现:

def setUp(self):
    super().setUp()
    self.withCsrf()

def test_csrf(self):
    self.post('/unit/test/json', {'test': 'testing'})

异常处理#

正如您所注意到的,Masonite 具有异常处理功能,可用于在开发过程中显示有用的信息。

在测试过程中你可能想看到更加详细的异常内容。所以测试时,默认会关闭 Masonite 异常处理机制。如果你想打开 Masonite 的内置处理机制,通过如下实现:

def setUp(self):
    super().setUp()
    self.withExceptionHandling()

def test_csrf(self):
    self.post('/unit/test/json', {'test': 'testing'})

获取输出#

你可以在单元测试中调用 captureOutput 方法来捕获输出内容:

def test_get_output(self):
    with self.captureOutput() as o:
        print('hello world!')

    self.assertEqual(o, 'hello world!')

测试 JSON#

经常我们需要测试 API,提供了相关方法来实现。

测试数量#

你确保返回的内容为指定数量。例如 5 篇文章:

def test_has_articles(self):
    self.assertTrue(
        self.json('GET', '/api/articles').count(5)
    )

也可以使用 assertCount(5):

def test_has_articles(self):
    self.json('GET', '/api/articles').assertCount(5)

你也可以使用 amount 方法,它仅仅是 count 的别名:

def test_has_articles(self):
    self.assertTrue(
        self.json('GET', '/api/articles').amount(5)
    )

    self.json('GET', '/api/articles').assertHasAmount(5)
    self.json('GET', '/api/articles').assertNotHasAmount(10)

指定键元素数量#

你可以检查 JSON 指定键其数量。例如:

"""
{
    "name": "Joe",
    "tags": ['python', 'framework'],
    "age": 25
}
"""

def test_has_several_tags(self):
    self.assertTrue(
        self.json('GET', '/api/user').hasAmount('tags', 2)
    )

测试指定内容#

有时候你想测试返回 JSON 包含确切的值:

"""
{
    "name": "Joe",
    "title": "creator",
    "age": 25
}
"""

def test_has_age(self):
    self.assertTrue(
        self.json('GET', '/api/user').hasJson('age', 25)
    )

还可以通过提供字典来检查多个值:

"""
{
    "name": "Joe",
    "title": "creator",
    "age": 25
}
"""

def test_has_age(self):
    self.assertTrue(
        self.json('GET', '/api/user').hasJson({
            "name": "Joe",
            "age": 25
        })
    )

你无需检查所有的元素。仅传递你想验证的元素。

还可以使用:

self.assertJsonHas('key', 'value')

检查集合内部值#

你还可以检查响应内容里面列表的值:

"""
[
    {
    "name": "Joe",
    "title": "creator",
    "age": 25
    },
    {
    "name": "Bob",
    "title": "Co-Founder",
    "age": 26
    }
]
"""

def test_bob_in_result(self):
    self.json('GET', '/api/user').assertJsonContains('name', 'Bob')

点标记#

你还可以使用点标记来访问嵌套结构:

"""
{
    "profile": {
        "name": "Joe",
        "title": "creator",
        "age": 25
    }
}
"""

def test_has_name(self):
    self.assertTrue(
        self.json('GET', '/api/user').hasJson('profile.name', 'Joe')
    )

转换为字典#

有时候你不想使用点标记方式,你想通过转换为字典来检查其元素值。也方便你在终端打印出来诊断。你可以使用如下方式:

"""
{
    "profile": {
        "name": "Joe",
        "title": "creator",
        "age": 25
    }
}
"""

def test_has_name(self):
    dictionary = self.json('GET', '/api/user').asDictonary()
    self.assertEqual(dictionary['profile']['name'], 'Joe')

测试参数#

你还可以测试参数中的值。例如,你想检查参数 id 的值是否为 5

def test_has_name(self):
    # Route is: /dashboard/user/@id
    self.assertTrue(
        self.get('GET', '/dashboard/user/5').parameterIs('id', '5')
    )

    self.get('GET', '/dashboard/user/5').assertParameterIs('id', '5')

测试头部内容#

你可以测试协议头部内容是否包含指定值。例如,你想检查 Content-Type 是否值为 text/html

def test_has_name(self):
    # Route is: /dashboard/user/@id
    self.assertTrue(
        self.get('GET', '/dashboard/user/5').headerIs('Content-Type', 'text/html')
    )

    self.get('GET', '/dashboard/user/5').assertHeaderIs('Content-Type', 'text/html')

测试状态码#

你可以通过使用 isStatusassertIsStatus 方法来断言返回状态码:

self.assertTrue(
        self.get('GET', '/dashboard/user/5').isStatus(200)
)

self.get('GET', '/dashboard/user/5').assertIsStatus(200)

你可以提供便利方法来断言 404

self.get('GET', '/dashboard/not/exists').assertNotFound()

这里断言其输出状态码为 404 效果一致。

子域名#

默认,Masonite 会关闭子域名,放置部署在一些 PaaS 平台会有问题,例如 sunny-land-176892.herokuapp.com

在测试中要打开子域名功能,需要调用 withSubdomains() 方法。然后,就可以设置其 wsgi 属性来配置 host。

def test_subdomains(self):
    self.withSubdomains().get('/view', wsgi={
            'HTTP_HOST': 'subb.domain.com'
        }).assertIsStatus(404)

测试数据库#

数据库#

默认,为防止弄乱运行中的数据库。测试用例只使用 sqlite 数据库。你可以通过将 sqlite 属性设置为 False 关闭。

from masonite.testing import TestCase

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = True
    sqlite = False

    def setUp(self):
        super().setUp()

这样你就可以使用任意数据库驱动了。

事务和刷新#

默认,所有的测试通过在一个事务中执行,所以所有的数据只会在测试生存周期中创建。一旦测试完成,数据库会回滚到上一个状态。这保持数据库整洁的有效方法。

虽然这样对于很多测试情况下较好方式,有时你可能真想将操作更新到数据库中。可以通过设置 refreshes_database 属性为 True 来实现。

from masonite.testing import TestCase

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = False
    refreshes_database = True

    def setUp(self):
        super().setUp()

现在就会迁移和刷新数据库了。

小心有可能会破坏你现有数据库

工厂#

工厂是用来生成一些虚拟数据到数据库中。你可以创建一个方法用来接收 faker 参数来构建数据。

Masonite 提供一个便利方法 seUpFactories,它会测试启动时执行。它只会运行一次,而不是每个测试

让我们创建工厂方法,并使用 setUpFactories 方法来运行它。

以下创建了 100 个用户:

from masonite.testing import TestCase
from app.User import User

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = True

    def setUp(self):
        super().setUp()

    def setUpFactories(self):
        self.make(User, self.user_factory, 100)

    def user_factory(self, faker):
        return {
            'name': faker.name(),
            'email': faker.email(),
            'password': '$2b$12$WMgb5Re1NqUr.uSRfQmPQeeGWudk/8/aNbVMpD1dR.Et83vfL8WAu',  
            # == 'secret'
        }

    def test_creates_users(self):
        pass

你不一定需要依赖工厂来构建数据,可以通过如下方式来新增记录:

from masonite.testing import TestCase
from app.User import User

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = True

    def setUp(self):
        super().setUp()

    def setUpFactories(self):
        User.create({
            'name': 'Joe',
            'email': 'user@example.com',
            'password': '$2b$12$WMgb5Re1NqUr.uSRfQmPQeeGWudk/8/aNbVMpD1dR.Et83vfL8WAu',  # == 'secret'
        })

    def test_creates_users(self):
        pass

用户#

我们可以加载用户到路由中,然后检查它是否可以查看路由。可用来检查中间件是否有效处理不同用户。使用 acting_as() 方法来实现:

tests/test_unit.py

from app.User import User
...

    def setUpFactories(self):
        User.create({
            'name': 'Joe',
            'email': 'user@example.com',
            'password': '$2b$12$WMgb5Re1NqUr.uSRfQmPQeeGWudk/8/aNbVMpD1dR.Et83vfL8WAu',  # == 'secret'
        })

    def test_user_can_see_dashboard(self):
        self.assertTrue(
            self.actingAs(User.find(1)).get('/dashboard').ok()
        )

传递数据#

也许你需要提供类似提交表单并指定传入数据。你可以通过 传递字典给 get 或者 post 方法第二参数来实现:

def test_user_can_see_dashboard(self):
    self.assertTrue(
        self.actingAs(User.find(1)).post('/dashboard', {
            'name': 'Joe',
            'active': 1
        })
    )

get 方法同样可以接收参数,它将以查询参数的方式实现。

断言数据库值#

不用非得通过模型来检查是否在数据库存在值,我们可以使用 assertDatabaseHas 断言来执行对数据库进行检查:

def test_create_article(self):
    self.assertTrue(
        self.post('/articles', {
            'name': 'Masonite is great',
            'author_id': 1
        }).assertDatabaseHas('articles.name', 'Masonite is great')
    )

也可以使用相对的 assertDatabaseNotHas 断言:

def test_create_article(self):
    self.assertTrue(
        self.post('/articles', {
            'name': 'Masonite is great',
            'author_id': 1
        }).assertDatabaseNotHas('articles.name', 'Masonite is bad')
    )

测试范例#

我们来举个完整的例子,检查用户是否成功创建:

from masonite.testing import TestCase
from app.User import User

class TestUser(TestCase):

    """Start and rollback transactions for this test
    """
    transactions = True

    def setUp(self):
        super().setUp()

    def setUpFactories(self):
        User.create({
            'name': 'Joe',
            'email': 'user@example.com',
            # == 'secret'
            'password': '$2b$12$WMgb5Re1NqUr.uSRfQmPQeeGWudk/8/aNbVMpD1dR.Et83vfL8WAu',  
        })

    def test_creates_users(self):
        self.assertTrue(User.find(1))

就这样!该测试会检查用户是否成功创建.

运行测试#

通过如下方式执行测试:

$ python -m pytest

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/masonite/2.3/us...

译文地址:https://learnku.com/docs/masonite/2.3/us...

上一篇 下一篇
贡献者:3
讨论数量: 0
发起讨论 只看当前版本


暂无话题~