pytest之assert断言的具体使用

2023-12-27 16:42:06

背景

本文总结使用pytest编写自动化测试时常用的assert断言。

说明

本文将从以下几点做总结:

  1. 为测试结果作断言
  2. 为断言不通过的结果添加说明信息
  3. 为预期异常作断言
  4. 为失败断言自定义说明信息

为测试结果作断言

在断言方面,pytest框架比其他类似的框架(比如unittest)更加简洁,易用,我想这是我选择pytest作为自动化测试框架之一的原因之一。
pytest的assert断言关键字支持使用python内置的assert表达式。可以理解为pytest的断言就是直接使用python自带的assert关键字。

python assert的概念:

Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。

我们可以在在assert后面添加任何符合python标准的表达式,如果表达式的值通过bool转换后等于False,则意味着断言结果为失败。

以下举例常用的表达式:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

# ./test_case/test_func.py

import pytest

from func import *

class TestFunc:

??

?def test_add_by_class(self):

??assert add(2,3) == 5

def test_add_by_func_aaa():

?assert 'a' in 'abc'

?assert 'a' not in 'bbc'

?something = True

?assert something

?something = False

?assert not something

?assert 1==1

?assert 1!=2

?assert 'a' is 'a'

?assert 'a' is not 'b'

?assert 1 < 2

?assert 2 > 1

?assert 1 <= 1

?assert 1 >= 1

?assert add(3,3) == 6

'''

# 以上全是合法的表达式且表达式的值都为True,所以测试结果为通过

============================= test session starts =============================

platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe

cachedir: .pytest_cache

rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini

plugins: allure-pytest-2.8.9, rerunfailures-8.0

collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED?????????????? [ 50%]

test_case/test_func.py::test_add_by_func_aaa PASSED????????????????????? [100%]

============================== 2 passed in 0.06s ==============================

[Finished in 1.8s]

'''

为断言不通过的结果添加说明信息

在编写测试时,为了提高易用性,我们想知道断言失败时的一些关于失败的原因等说明信息,assert也能满足该功能。
请看示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

# ./test_case/test_func.py

import pytest

from func import *

class TestFunc:

?def test_add_by_class(self):

??assert add(2,3) == 5

def test_add_by_func_aaa():

?assert add(3,3) == 5, "3+3应该等于6"

'''

============================= test session starts =============================

platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe

cachedir: .pytest_cache

rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini

plugins: allure-pytest-2.8.9, rerunfailures-8.0

collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED?????????????? [ 50%]

test_case/test_func.py::test_add_by_func_aaa FAILED????????????????????? [100%]

================================== FAILURES ===================================

____________________________ test_add_by_func_aaa _____________________________

????def test_add_by_func_aaa():

?????

>??? assert add(3,3) == 5, "3+3应该等于6"

E??? AssertionError: 3+3应该等于6

E??? assert 6 == 5

E????? -6

E????? +5

test_case\test_func.py:14: AssertionError

========================= 1 failed, 1 passed in 0.09s =========================

[Finished in 1.4s]

'''

为预期异常作断言

在某些测试用例中,比如异常测试用例,测试的结果必然是失败并应该爆出异常的。这时候自动化测试用例的期望结果就是该异常。如果期望结果等于该异常,那么测试用例执行通过,否则用例结果为失败。pytest提供为为预期异常作断言的方法:pytest.raises()。一般结合with上下文管理器使用。

使用示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

# ./func.py

def add(a,b):

?if isinstance(a,int) and isinstance(b,int):

??return a+b

?else:

??raise NameError('数据类型错误')

# ./test_case/test_func.py

import pytest

from func import *

class TestFunc:

?# 正常测试用例

?def test_add_by_class(self):

??assert add(2,3) == 5

# 异常测试用例,期望结果为爆出TypeError异常

def test_add_by_func_aaa():

?with pytest.raises(TypeError):

??add('3',4)

???

# ./run_test.py

import pytest

if __name__ == '__main__':

?pytest.main(['-v'])

'''

============================= test session starts =============================

platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe

cachedir: .pytest_cache

rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini

plugins: allure-pytest-2.8.9, rerunfailures-8.0

collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED?????????????? [ 50%]

test_case/test_func.py::test_add_by_func_aaa PASSED????????????????????? [100%]

============================== 2 passed in 0.06s ==============================

[Finished in 1.4s]

'''

接下来看看没有爆出预期异常的示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

# ./func.py

def add(a,b):

?# 指定异常

?raise NameError("天降异常")

?if isinstance(a,int) and isinstance(b,int):

??return a+b

?else:

??raise NameError('数据类型错误')

# ./test_case/test_func.py

import pytest

from func import *

'''

class TestFunc:

?# 正常测试用例

?def test_add_by_class(self):

??assert add(2,3) == 5

'''

# 异常测试用例,期望结果为爆出TypeError异常

def test_add_by_func_aaa():

?with pytest.raises(TypeError):

??add('3',4)

???

# ./run_test.py

import pytest

if __name__ == '__main__':

?pytest.main(['-v'])

'''

============================= test session starts =============================

platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe

cachedir: .pytest_cache

rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini

plugins: allure-pytest-2.8.9, rerunfailures-8.0

collecting ... collected 1 item

test_case/test_func.py::test_add_by_func_aaa FAILED????????????????????? [100%]

================================== FAILURES ===================================

____________________________ test_add_by_func_aaa _____________________________

????def test_add_by_func_aaa():

?????with pytest.raises(TypeError):

>???? add('3',4)

test_case\test_func.py:14:

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

a = '3', b = 4

????def add(a,b):

?????# 指定异常

>??? raise NameError("天降异常")

E??? NameError: 天降异常

func.py:4: NameError

============================== 1 failed in 0.09s ==============================

[Finished in 1.4s]

'''

判定用例执行结果为失败。

上面我们只是断言了异常的类型。但有的时候我们想更进一步断言异常的说明信息,pytest也可以做到。with pytest.raises()执行结束后会生成一个ExceptionInfo的实例对象。该对象包含type , value, traceback属性。value属性就是我们需要的异常说明信息。

见示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

# ./func.py

def add(a,b):

?if isinstance(a,int) and isinstance(b,int):

??return a+b

?else:

??raise TypeError('数据类型错误')

??

# ./test_case/test_func.py

import pytest

from func import *

class TestFunc:

?# 正常测试用例

?def test_add_by_class(self):

??assert add(2,3) == 5

# 异常测试用例,期望结果为爆出TypeError异常

def test_add_by_func_aaa():

?with pytest.raises(TypeError) as E:

??add('3',4)

?print(E.type)

?print(E.value)

?print(E.traceback)

?# 加入该不通过断言为了查看stdout

?assert 1 == 2

# ./run_test.py

import pytest

if __name__ == '__main__':

?pytest.main(['-v'])

'''

============================= test session starts =============================

platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe

cachedir: .pytest_cache

rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini

plugins: allure-pytest-2.8.9, rerunfailures-8.0

collecting ... collected 2 items

test_case/test_func.py::TestFunc::test_add_by_class PASSED?????????????? [ 50%]

test_case/test_func.py::test_add_by_func_aaa FAILED????????????????????? [100%]

================================== FAILURES ===================================

____________________________ test_add_by_func_aaa _____________________________

????def test_add_by_func_aaa():

?????with pytest.raises(TypeError) as E:

??????add('3',4)

?????print(E.type)

?????print(E.value)

?????print(E.traceback)

>??? assert 1 == 2

E??? assert 1 == 2

E????? -1

E????? +2

test_case\test_func.py:18: AssertionError

---------------------------- Captured stdout call -----------------------------

<class 'TypeError'>

数据类型错误

[<TracebackEntry D:\Python3.7\project\pytest\test_case\test_func.py:14>, <TracebackEntry D:\Python3.7\project\pytest\func.py:6>]

========================= 1 failed, 1 passed in 0.10s =========================

[Finished in 1.4s]

'''

控制台输出的“Captured stdout call”就是异常的信息,包含类型,异常说明,异常跟踪信息。
可以通过assert断言这些信息。

也可以通过给pytest.raises()传入match关键字参数来完成E.value的断言,这里运用到的是python中正则表达式的原理。

示例:

该示例意味断言通过

1

2

3

def test_add_by_func_aaa():

?with pytest.raises(TypeError, match=r'.*类型错误$') as E:

??add('3',4)

该示例意味断言失败:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

# 异常测试用例,期望结果为爆出TypeError异常

def test_add_by_func_aaa():

?with pytest.raises(TypeError, match=r'.*正确$') as E:

??add('3',4)

'''

During handling of the above exception, another exception occurred:

????def test_add_by_func_aaa():

?????with pytest.raises(TypeError, match=r'.*正确$') as E:

>???? add('3',4)

E???? AssertionError: Pattern '.*正确$' not found in '数据类型错误'

test_case\test_func.py:14: AssertionError

'''

如果,某个测试用例可能出现不同的预期异常,只要爆出的异常在预期的几个异常之内,那么如何断言呢。解决方法很简单,原理和接口都没变,只是在pytest.raises()中传入异常类型的参数,从传入一个异常类型,改变为传入一个异常类型组成的元组。同样只是传入一个参数。

示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

# ./func.py

def add(a,b):

?raise NameError('名字错了')

?if isinstance(a,int) and isinstance(b,int):

??return a+b

?else:

??raise TypeError('数据类型错误')

??

# ./test_case/test_func.py

import pytest

from func import *

'''

class TestFunc:

?# 正常测试用例

?def test_add_by_class(self):

??assert add(2,3) == 5

'''

# 异常测试用例,期望结果为爆出TypeError异常

def test_add_by_func_aaa():

?with pytest.raises((TypeError,NameError),match=r'.*错.*$') as E:

??add('3',4)

??

??

# ./run_test.py

import pytest

if __name__ == '__main__':

?pytest.main(['-v'])

??

'''

============================= test session starts =============================

platform win32 -- Python 3.7.0, pytest-5.3.4, py-1.8.1, pluggy-0.13.1 -- D:\Python3.7\python.exe

cachedir: .pytest_cache

rootdir: D:\Python3.7\project\pytest, inifile: pytest.ini

plugins: allure-pytest-2.8.9, rerunfailures-8.0

collecting ... collected 1 item

test_case/test_func.py::test_add_by_func_aaa PASSED????????????????????? [100%]

============================== 1 passed in 0.04s ==============================

[Finished in 1.4s]

'''

为失败断言自定义说明信息

这种行为,相当于改变了pytest的运行方式,虽然只是一种锦上添花的改变。我们通过编写hook函数来改变pytest的行为。hook函数是pytest提供的,有很多,各个hook函数的详细定义应该参考pytest的官方文档。
为失败断言自定义说明信息是通过pytest_assertrepr_compare这个hook函数完成的。
先看没有编写pytest_assertrepr_compare这个hook函数时,默认的失败断言说明:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

def test_add_by_func_aaa():

?assert 'aaa' == 'bbb'

'''

================================== FAILURES ===================================

____________________________ test_add_by_func_aaa _____________________________

????def test_add_by_func_aaa():

>??? assert 'aaa' == 'bbb'

E??? AssertionError: assert 'aaa' == 'bbb'

E????? - aaa

E????? + bbb

test_case\test_func.py:16: AssertionError

'''

再看编写pytest_assertrepr_compare这个hook函数后:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

# ./conftest.py

def pytest_assertrepr_compare(op, left, right):

????if isinstance(left, str) and isinstance(right, str) and op == "==":

????????return ['两个字符串比较:',

????????????????'?? 值: %s != %s' % (left, right)]

# ./test_case/test_func.py

import pytest

def test_add_by_func_aaa():

?assert 'aaa' == 'bbb'

'''

.F?????????????????????????????????????????????????????????????????????? [100%]

================================== FAILURES ===================================

____________________________ test_add_by_func_aaa _____________________________

????def test_add_by_func_aaa():

>??? assert 'aaa' == 'bbb'

E??? assert 两个字符串比较:

E???????? 值: aaa != bbb

test_case\test_func.py:15: AssertionError

1 failed, 1 passed in 0.09s

[Finished in 1.5s]

'''

pytest还提供其他的hook函数,这些函数的作用就是用来改变pytest的运行方式和运行效果。所以编写第三方插件一般是使用这些hook函数。

?现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:485187702【暗号:csdn11】

最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走!?希望能帮助到你!【100%无套路免费领取】

文章来源:https://blog.csdn.net/m0_70669463/article/details/135246643
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。