测试

未匹配的标注
本文档最新版为 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
发起讨论 只看当前版本


暂无话题~