测试入门
Masonite 测试非常简单。 你可以很轻松地测试很复杂的代码,只需要通过 Masonite 的测试类来扩展你的类。
虽然 Masonite 使用 pytest
来运行测试用例, 但是 Masonite 的测试套件是基于 unittest
的。 所以你将使用 unittest
语法,但是通过 pytest
运行测试用例。 因此,所有语法将采用驼峰大小写,而不是PEP 8 的 小写加下划线用例。只是需要知道所有测试用例断言都是驼峰形式,以符合单元测试标准。
测试环境
当进行测试时,Masonite 会自动设置环境 environment 来测试( testing
)。需要的时候,你可以自定义其他测试环境配置信息。
你可以创建一个 .env.testing
文件,在其中可以随意加载任何测试环境变量,默认情况下,他们是不会被提交的。 当 pytest
运行时, 这个文件会被额外加载并重写所有额外的环境变量。
创建测试
你可以简单地创建一个以 test_
开头的文件,然后创建一个测试类继承自 masonite 的 TestCase
类。
你也可以直接使用命令
$ python craft test SomeFeatureTest
来创建 tests/unit/test_some_feature.py
:
from masonite.tests import TestCase
class SomeFeatureTest(TestCase):
def setUp(self):
super().setUp()
def test_something(self):
self.assertTrue(True)
就是这样了! 你已经准备开始测试了。 请继续阅读以学习如何构建并运行你的测试用例。
运行测试
你可以运行测试用例,通过执行:
$ python -m pytest
这将自动发现(搜索)你的测试用例, 依据 pytest 的搜索规则: automatic tests discovery.
你也可以运行指定的测试类:
$ python -m pytest tests/unit/test_my_feature.py
或者指定一个测试方法:
$ python -m pytest tests/unit/test_my_feature.py::MyFeatureTest::test_feature_is_working
最后,你还可以自动重跑上次失败的用例:
$ python -m pytest --last-failed
构建测试
测试的生命周期
当你运行一个测试类时, 测试类中的每个方法将会依据一个特定的生命周期来运行。
class TestFeatures(TestCase):
@classmethod
def setUpClass(cls):
"""在所有测试执行前运行一次"""
print("Setting up test class")
@classmethod
def tearDownClass(cls):
"""在所有测试结束后运行一次"""
print("Cleaning up test class")
def setUp(self):
"""在每个测试执行前均运行一次"""
super().setUp()
print("Setting up individual unit test")
def tearDown(self):
"""在每个测试执行后均运行一次"""
super().tearDown()
print("Cleaning up individual unit test")
def test_1(self):
print("Running test 1")
def test_2(self):
print("Running test 2")
运行以上的测试类将会得到这样的输出:
Setting up test class
Setting up individual unit test
Running test 2
Cleaning up individual unit test
Setting up individual unit test
Running test 1
Cleaning up individual unit test
Cleaning up test class
注意:测试方法并不总是根据类中指定的顺序运行的。你应该试着让你的测试独立,而不要假定测试会根据给定的顺序运行。
链接断言
所有以 assert
开始的方法会通过许多断言被链接到一起来运行。所有其他方法会返回某种布尔值或 你可以用来自行断言的值。
断言异常
有时候你需要断言某个给定的代码片断会抛出异。 为此你可以使用标准的 assertRaises()
上下文管理器:
with self.assertRaises(ValidationError) as e:
# 这里写一些代码
raise ValidationError("An error occured !")
self.assertEqual(str(e.exception), "An error occured !")
捕获输出
有时候你需要测试某个函数打印到控制台的输出。 为此你可以在测试中使用 captureOutput()
上下文管理器:
with self.captureOutput() as output:
# 这里写一些代码
print("Hello World !")
self.assertEqual(output.getvalue().strip(), "Hello World !")
重写调试模式
有时候你需要在测试期间修改调试模式的值。 为此你可使用 debugMode()
上下文管理器:
# 开启调试模式运行代码内容
with self.debugMode() as output:
self.get("/").assertError()
# 关闭调试模式运行代码内容
with self.debugMode(False) as output:
self.get("/").assertError()
转储数据
在测试执行期间, print()
语句将不可用。 你可以使用 dump()
测试辅助器在测试时将数据转储到控制台:
def test_can_create_user(self):
user = User.find(1)
self.get("/register").assertRedirect()
self.dump("Hello")
self.dump(user, "User with ID 1")
注意你可以传入第二个参数来命名转储控制台。
停止测试
如果你想在测试期间停止测试,你可以使用 stop()
辅助器。你甚至可以提供下原因。
def test_can_create_user(self):
user = User.find(1)
self.get("/register").assertRedirect()
self.stop("for debugging") #== 测试将会在此处停止。
self.post("/login", {"email": user.email, "password": "secret"})
测试是通过返回 pytest 2 的停止代码(用户中断)来停止的。
测试辅助
Masonite 有不同的辅助器可以轻松地编写代码。 其中一些已经在上面的章节中进行了解释。
- withExceptionsHandling
- withoutExceptionsHandling
- withCsrf
- withoutCsrf
- withCookies
- withHeaders
- fakeTime
- fakeTimeTomorrow
- fakeTimeYesterday
- fakeTimeInFuture
- fakeTimeInPast
- restoreTime
使用异常处理器
测试期间会进行异常处理。
self.withExceptionsHandling()
不使用异常处理器
测试期间不会进行异常处理。
self.withoutExceptionsHandling()
注意:测试期间是默认禁用异常处理。
使用Csrf
测试期间会采用 CSRF 保护机制。
self.withCsrf()
不使用Csrf
测试期间不会采用 CSRF 保护机制。
self.withoutCsrf()
注意:测试期间是默认禁用 CSRF 保护机制。
使用Cookies
添加的 Cookies 将会在下一次请求时使用。 这个方法接收键值对形式的字典。 Cookies 字典在每个用例间都会重置。
self.withCookies(data)
使用Headers
添加的 Headers 将会在下一次请求时使用。这个方法接收键值对形式的字典。 Headers 字典在每个用例间都会重置。
self.withHeaders(data)
假定时间
当 now
(或 today
, tomorrow
,yesterday
) 实例被创建后,设置要返回的给定时钟实例。对于要在测试期间检查时间戳逻辑,它真的很有用。
这允许控制返回的日期时间来让用例始终是符合预期行为。
given_date = pendulum.datetime(2021, 2, 5)
self.fakeTime(given_date)
self.assertEqual(pendulum.now(), given_date)
注意:当使用这些辅助器时,别忘了使用
restoreTime()
辅助器来重置默认的pendulum
行为 ,以避免破坏了其他用例。 它可以直接在这个测试中完成或者在tearDown()
方法中完成。
假定明天的时间
将模拟时间设置为明天。(这是一条捷径,避免使用 self.fakeTime(pendulum.tomorrow())
)。
tomorrow = pendulum.tomorrow()
self.fakeTimeTomorrow()
self.assertEqual(pendulum.now(), tomorrow)
假定昨天的时间
将模拟时间设置为昨天。
假定未来的时间
将模拟时间设置为将来给定时间单位的偏移量。 可以在时钟单位中指定单位: seconds
, minutes
, hours
, day
(默认), weeks
, months
, years
。
self.fakeTimeInFuture(offset, unit="days")
real_now = pendulum.now()
self.fakeTimeInFuture(1, "months")
self.assertEqual(pendulum.now().diff(real_now).in_months(), 1)
假定过去的时间
将模拟时间设置为过去给定时间单位的偏移量。 可以在时钟单位中指定单位: seconds
, minutes
,hours
, days
(默认),weeks
,months
, years
。
self.fakeTimeInPast(offset, unit="days")
恢复时间
恢复模拟时间成默认的 pendulum
时间。 当使用 fake
时间辅助器时, 别忘了在最后执行这个。
它可以直接在这个测试中完成或者在 tearDown()
方法中完成。
def tearDown(self):
super().tearDown()
self.restoreTime()
def test_creation_date(self):
self.fakeTimeYesterday()
# 从现在直到这个单元测试完成, 时间将会模拟成昨天的时间。
当你运行测试类时, 这个测试类中的每个测试方法将会在指定生命期间运行。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。