帮助更好的测试。
exam的Python项目详细描述
考试
exam是一个用于编写更好的测试的python工具包。它的目标是删除一个人经常编写的大量锅炉板测试代码,同时仍然遵循python惯例并坚持单元测试接口。
安装
一个简单的pip安装考试就可以了。
基本原理
除了显而易见的"代码有效吗?"",写作测试还有许多附加目标和好处:
- 如果按语义编写,阅读测试可以帮助演示代码应该如何为其他开发人员工作。
- 如果快速运行,测试会在开发过程中提供反馈,表明您的更改正在起作用或没有副作用。
- 如果它们易于正确编写,开发人员将编写更多的测试,并且它们的质量将更高。
不幸的是,编写python单元测试的通用模式往往没有提供这些优点。常常会导致测试代码效率低下和不必要的迟钝。此外,
mock
库的常见用法通常会导致重复的锅炉板代码或在测试运行期间效率低下。
exam
旨在通过提供一个有用的功能工具包来改进python测试的编写状态,使编写测试变得快速、正确和有用,并且尽可能地轻松。
用法
考试包含一系列有用的模块:
检查装饰工
考试有一些有用的修饰工具,使你的考试更容易编写和理解。要使用 @before 、 @after 、 @around 和 @patcher 装饰程序,您必须将 exam.cases.exam 类混入您的测试用例中。它实现了使装饰器工作所必需的适当的 setup() 和 teardown() 方法。
注意, @fixture 装饰器不需要在考试类中定义就可以工作。不过,在测试用例中添加 考试 混合是一种最佳实践。
exam.decorators中的所有decorators以及exam测试用例也可以从main exam 包中导入。即:
fromexamimportExamfromexamimportfixture,before,after,around,patcher
检查.装饰.固定装置
fixture decorator将方法转换为属性(类似于 @property decorator,但也会记住返回值)。这允许您在测试中引用属性,即 self.grounds ,并且每次都会引用完全相同的实例。
fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):@fixturedefuser(self):returnUser(name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'
如您所见, self.user 用于引用上面定义的 user 属性。
如果fixture方法所做的只是构造一个新的类型实例或调用一个类方法,那么exam为构造fixture对象提供了一个简单的内联fixture语法。只需将类变量设置为 fixture(type_或_class_method) 并且exam将自动调用您的type或class method。
fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):user=fixture(User,name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'
当调用时,传递给 fixture(type_or_class_method) 的任何 *args 或 **kwargs 都将传递给 type_or_class_method 。
检查装饰工之前
前ore decorator将方法添加到作为类的 setup() 例程的一部分运行的方法列表中。
fromexam.decoratorsimportbeforefromexam.casesimportExamclassMyTest(Exam,TestCase):@beforedefreset_database(self):mydb.reset()
@before
钩子也可以通过子类工作,也就是说,如果父类在钩子中有一个
@before
钩子,并且在钩子中定义了第二个
@before
钩子,那么将调用这两个
@before
钩子。考试先运行家长的钩子,然后运行孩子的钩子。此外,如果在子类中的hook之前重写a
@before,则在运行其他子类
@before
hook时运行重写的方法。
例如,使用这样定义的钩子:
fromexam.decoratorsimportbeforefromexam.casesimportExamclassMyTest(Exam,TestCase):@beforedefreset_database(self):print'parent reset_db'@beforedefparent_hook(self):print'parent hook'classRedisTest(MyTest):@beforedefreset_database(self):print'child reset_db'@beforedefchild_hook(self):print'child hook'
当exam运行这些挂钩时,输出为:
"prent hook""child reset_db""child hook"
正如您所看到的,即使父类定义了一个reset_数据库,因为子类重写了它,子类的版本也会运行,同时也会运行子类的其他
@钩子之前的版本。
@before
钩子也可以与测试用例中的其他函数一起构造,修饰实际的测试方法。当使用此策略时,exam将在运行该特定测试方法之前运行函数
@before
。
fromexam.decoratorsimportbefore,fixturefromexam.casesimportExamfrommyappimportUserclassMyTest(Exam,TestCase):user=fixture(User)@beforedefcreate_user(self):self.user.create()defconfirm_user(self):self.user.confirm()@before(confirm_user)deftest_confirmed_users_have_no_token(self):self.assertFalse(self.user.token)deftest_user_display_name_exists(self):self.assertTrue(self.user.display_name)
在上面的示例中,
confirm_user
方法在
test_confirm_users_have_no_token
方法之前运行,但是
不
test_user_display_name_exists
方法存在。全局修饰的create-user方法仍然在每个测试方法之前运行。
@before
也可以构造为在运行测试方法之前要调用的多个函数:
classMyTest(Exam,TestCase):@before(func1,func2)deftest_does_things(self):does_things()
在上面的例子中,在运行test does之前,按顺序调用
func1
和
func2
。
检查.装饰.之后
对
@before
,
@after
的称赞将该方法添加到作为类的
teardown()
例程的一部分运行的方法列表中。像
@before
,
@after
在运行子类中定义的类之前运行父类
@after
挂钩。
fromexam.decoratorsimportafterfromexam.casesimportExamclassMyTest(Exam,TestCase):@afterdefremove_temp_files(self):myapp.remove_temp_files()
检查.装饰.大约
用
@around
修饰的方法充当每个测试方法的上下文管理器。在around方法中,您负责调用
yield
您希望测试用例运行的位置:
fromexamimportExamfromexamimportfixture,before,after,around,patcher
0
@around
也遵循与
@before
和
@after
相同的父/子排序规则,因此父
@around s
将运行(直到
yield
语句),然后子
@around``s
将运行。但是,在测试方法完成之后,孩子的其余部分将运行,然后是家长。这样做是为了保持与上下文管理器嵌套的正常行为。
检查、装饰、修补
@patcher
装饰器是以下锅炉板代码的简写:
fromexamimportExamfromexamimportfixture,before,after,around,patcher
1
通常,手动控制修补程序的开始/停止是为了为您正在修补的模拟对象提供测试用例属性(这里,
self.stats
)。如果您希望mock在大多数测试中都有默认行为,但对某些测试稍微更改一下,这是很方便的,即大部分时间吸收所有调用,但对于某些测试,它会引发异常。
使用
@patcher
装饰符,上面的代码可以简单地写成:
fromexamimportExamfromexamimportfixture,before,after,around,patcher
2
exam负责适当地启动和停止修补程序,以及使用修饰方法的返回值构造
修补程序
对象。
如果您对修补程序的默认构造的模拟对象感到满意(
magicmock
),那么可以将补丁程序作为类主体内的内联函数使用。这种方法仍然在需要时启动和停止修补程序,并返回构造的magicmock对象,您可以将其设置为类属性。exam将自动将magicmock对象作为实例属性添加到测试用例中。
fromexamimportExamfromexamimportfixture,before,after,around,patcher
3
检查.装饰.修补程序.对象
patcher.object
decorator提供了与patcher
decorator相同的特性,但它使用对象的修补属性(类似于mock的mock.patch.object
)。例如,下面是如何使用patcher修补
用户类的
对象
属性:
fromexamimportExamfromexamimportfixture,before,after,around,patcher
4
与普通的修补程序一样,在您的测试用例中,
self.manager
将是
user.objects
修补的模拟对象。
考试助手
helpers模块提供了一组用于常见测试模式的helper方法:
考试。助手。曲目
track帮助器旨在帮助跟踪独立模拟对象的调用顺序。
track
用kwargs调用,其中键是模拟名称(字符串),值是要跟踪的模拟对象。
track
返回一个新构造的magicmock对象,每个mock对象都附加在一个以mock名称命名的属性上。
例如,下面的track()
创建了一个新的模拟,tracker.cool`作为"cool\u mock",tracker.heat作为"heat\u mock"
fromexamimportExamfromexamimportfixture,before,after,around,patcher
5
检查.助手.rm_f
这是一个简单的助手,只需删除路径中的所有文件夹和文件:
fromexamimportExamfromexamimportfixture,before,after,around,patcher
6
检查.助手.模拟导入
删除模拟导入所需的大部分boiler plate代码,通常包括从
sys.modules
生成
补丁.dict
。相反,当导入某些模块时,
补丁程序导入助手可以简单地用作装饰器或上下文管理器。
fromexamimportExamfromexamimportfixture,before,after,around,patcher
7
mock_import也可以用作decorator,它将mock值传递给
测试方法(就像一个普通的
@patch
)decorator:
fromexamimportExamfromexamimportfixture,before,after,around,patcher
8
检查.助手.效果
helper类本身是可调用的,其调用时的返回值是通过传递给构造函数的元组配置的。有助于为模拟对象构建
副作用的可调用项。如果使用未配置的参数调用typeerror,则引发typeerror:
< Buff行情>
fromexamimportExamfromexamimportfixture,before,after,around,patcher
9
调用参数相等是通过
调用`
对象的相等(=)检查的,该对象是传递给
effect
构造函数的配置元组的第0项。默认情况下,
调用
对象只是模拟对象。调用
对象。
如果要自定义此行为,请子类化
effect
并重新定义自己的
调用类
类变量。即
fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):@fixturedefuser(self):returnUser(name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'
0
考试模拟
exam有一个子类normal
mock.mock
对象,它向mock对象添加了一些更有用的方法。用它代替普通的模拟对象:
fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):@fixturedefuser(self):returnUser(name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'
1
子类有以下额外方法:
-
assert_called()
-断言至少调用了一次模拟。
-
assert_not_called()
-断言从未调用过模拟。
-
assert_not_called_with(*args,
**kwargs)
-断言最近没有使用指定的
*args
和
**kwargs
调用模拟。
-
assert_not_called_once_with(*args,
**kwargs)
-断言模型只被指定的
*args
和
**kwargs
调用过一次。
assert_not_any_call(*args,**kwargs)
-断言从未使用指定的
*args
和
**kwargs
调用模拟
检查夹具
您可能希望在测试中使用的有用设备:
-
检查.固定装置.两个像素正方形图像
-图像数据作为一个2px正方形图像的字符串。
-
检查.固定装置.一个像素间隔
-图像数据作为一个1px正方形间隔图像串。
检查对象
用于测试的有用对象:
exam.objects.noop
-始终返回
none
的可调用对象。不管怎么称呼。
检查断言
asserts
模块包含一个
asserts mixin
类,该类被混合到主
检查中。除了python的
unittest
中的断言之外,它还包含其他断言。
资产变动
当您想断言某段代码更改了某个值时使用。例如,假设您有一个改变士兵军衔的函数。
要正确地测试这一点,您应该将该士兵的军衔保存为一个临时变量,然后运行该函数来更改军衔,最后断言军衔是新的预期值,而不是旧的值:
fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):@fixturedefuser(self):returnUser(name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'
2
检查旧的等级不一样是新的等级很重要。如果由于某种原因,在创建了名为
general
的self.soldier
而
promote
不起作用的地方出现了一个bug或其他东西,那么这个测试仍然会通过!
要解决这个问题,可以使用exam的assertchanges
fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):@fixturedefuser(self):returnUser(name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'
3
这个断言正在做一些事情。
-
它断言上下文运行后的秩是预期的
常规值
-
它断言上下文改变了self.soldier.rank的值。
-
它实际上并不关心self.soldier.rank的旧值是什么,只要它在运行上下文时发生了变化。
资产变动的定义是:
fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):@fixturedefuser(self):returnUser(name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'
4
-
你给它一个可调用的东西。
-
assertchanges
然后用额外传入的
*args
和
**kwargs
调用您的
对象,并将该值捕获为"before"值。
-
运行上下文,然后将可调用的再次捕获为"after"值。
-
如果前后不不同,则会引发一个断言错误。
-
此外,如果传递了
之前或
之后的特殊kwarg
,则提取并保存这些值。在这种情况下,如果提供的"before"和/或"after"值与其提取的值不匹配,也可以引发断言错误。
资产不发生变化
类似于
assertchanges
,
assertdoesnotchange
断言上下文中的代码不会更改可调用的值:
fromexam.decoratorsimportfixturefromexam.casesimportExamclassMyTest(Exam,TestCase):@fixturedefuser(self):returnUser(name='jeff')deftest_user_name_is_jeff(self):assertself.user.name=='jeff'
5
与
assertchanges
不同,
assertdoesnotchange
不在
之前或
之后进行。它只是断言当上下文运行时,callable的值没有改变。
许可证
考试是麻省理工学院授权的。有关详细信息,请参见
许可证
文件。