【问题标题】:Pytest does not seem to match a raised exception in a unit testPytest 似乎与单元测试中引发的异常不匹配
【发布时间】:2020-05-21 18:40:33
【问题描述】:

上下文

我正在尝试使用密码库进行 pytest。在测试中,我使用故意损坏的身份验证标签对一些数据进行解密和身份验证。这样做应该会引发一个“InvalidTag”exception,如下面的example 所述。

我正在使用following way 通过 pytest 断言异常:

with pytest.raises(Exception, match='a_string'):
    myfunc()

我的代码示例

#!/usr/bin/env python3

import pytest

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

key = bytes.fromhex(
"1111111111111111111111111111111111111111111111111111111111111111")
iv = bytes.fromhex("222222222222222222222222")
aad = bytes.fromhex("33333333333333333333333333333333")
pt = bytes.fromhex("4444444444444444")


def myfunc():
    raise ValueError("Exception 123 raised")


def test_match():
    with pytest.raises(ValueError, match=r".* 123 .*"):
        myfunc()


 def decrypt():
    # encrypt
    encryptor = Cipher(
        algorithms.AES(key),
        modes.GCM(iv),
        backend=default_backend()
    ).encryptor()
    encryptor.authenticate_additional_data(aad)
    ct = encryptor.update(pt) + encryptor.finalize()
    tag = encryptor.tag

    # let us corrupt the tag
    corrupted_tag = bytes.fromhex("55555555555555555555555555555555")

    # decrypt
    decryptor = Cipher(
        algorithms.AES(key),
        modes.GCM(iv, corrupted_tag),
        backend=default_backend()
    ).decryptor()
    decryptor.authenticate_additional_data(aad)

    return decryptor.update(ct) + decryptor.finalize()


 def test_decrypt():
     with pytest.raises(Exception, match='InvalidTag'):
         decrypt()

这里真正重要的是函数 test_decrypt()。目前,如果我使用前面的代码运行 pytest,我会得到以下输出:

> ▶ pytest
> 
> ============================================== test session starts ===============================================
> platform darwin -- Python 3.8.2, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
> rootdir: somewhere
> collected 2 items                                                                                                
> 
> tests/test_pytest_exception.py .F                                                                          [100%]
> 
> ==================================================== FAILURES ====================================================
> __________________________________________________ test_decrypt __________________________________________________
> 
>     def test_decrypt():
>         with pytest.raises(Exception, match='InvalidTag'):
> >           decrypt()
> 
> tests/test_pytest_exception.py:52: 
> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
> 
>     def decrypt():
>         # encrypt
>         encryptor = Cipher(
>             algorithms.AES(key),
>             modes.GCM(iv),
>             backend=default_backend()
>         ).encryptor()
>         encryptor.authenticate_additional_data(aad)
>         ct = encryptor.update(pt) + encryptor.finalize()
>         tag = encryptor.tag
>     
>         # let us corrupt the tag
>     
>         corrupted_tag = bytes.fromhex("55555555555555555555555555555555")
>     
>         # decrypt
>         decryptor = Cipher(
>             algorithms.AES(key),
>             modes.GCM(iv, corrupted_tag),
>             backend=default_backend()
>         ).decryptor()
>         decryptor.authenticate_additional_data(aad)
>     
> >       return decryptor.update(ct) + decryptor.finalize()
> 
> tests/test_pytest_exception.py:47: 
> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
> 
> self = <cryptography.hazmat.primitives.ciphers.base._AEADCipherContext object at 0x10d2d53a0>
> 
>     def finalize(self):
>         if self._ctx is None:
>             raise AlreadyFinalized("Context was already finalized.")
> >       data = self._ctx.finalize()
> 
> ../../../.local/share/virtualenvs/a_virtual_env/lib/python3.8/site-packages/cryptography/hazmat/primitives/ciphers/base.py:198: 
> _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
> 
> self = <cryptography.hazmat.backends.openssl.ciphers._CipherContext object at 0x10d2d5040>
> 
>     def finalize(self):
>         # OpenSSL 1.0.1 on Ubuntu 12.04 (and possibly other distributions)
>         # appears to have a bug where you must make at least one call to update
>         # even if you are only using authenticate_additional_data or the
>         # GCM tag will be wrong. An (empty) call to update resolves this
>         # and is harmless for all other versions of OpenSSL.
>         if isinstance(self._mode, modes.GCM):
>             self.update(b"")
>     
>         if (
>             self._operation == self._DECRYPT and
>             isinstance(self._mode, modes.ModeWithAuthenticationTag) and
>             self.tag is None
>         ):
>             raise ValueError(
>                 "Authentication tag must be provided when decrypting."
>             )
>     
>         buf = self._backend._ffi.new("unsigned char[]", self._block_size_bytes)
>         outlen = self._backend._ffi.new("int *")
>         res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen)
>         if res == 0:
>             errors = self._backend._consume_errors()
>     
>             if not errors and isinstance(self._mode, modes.GCM):
> >               raise InvalidTag
> E               cryptography.exceptions.InvalidTag
> 
> ../../../.local/share/virtualenvs/a_virtual_env/lib/python3.8/site-packages/cryptography/hazmat/backends/openssl/ciphers.py:170: InvalidTag
> 
> During handling of the above exception, another exception occurred:
> 
>     def test_decrypt():
>         with pytest.raises(Exception, match='InvalidTag'):
> >           decrypt()
> E           AssertionError: Pattern 'InvalidTag' does not match ''
> 
> tests/test_pytest_exception.py:52: AssertionError
> ============================================ short test summary info =============================================
> FAILED tests/test_pytest_exception.py::test_decrypt - AssertionError: Pattern 'InvalidTag' does not match ''
> ========================================== 1 failed, 1 passed in 0.12s ===========================================

从这个输出看,似乎引发了“InvalidTag”异常,但第二个异常也是出于我不明白的原因。 如果我不运行测试,而只运行函数 decrypt(),我会得到以下输出:

>   python3 tests/test_pytest_exception.py
> Traceback (most recent call last):
>   File "tests/test_pytest_exception.py", line 54, in <module>
>     decrypt()
>   File "tests/test_pytest_exception.py", line 47, in decrypt
>     return decryptor.update(ct) + decryptor.finalize()
>   File "somewhere/a_virtual_env/lib/python3.8/site-packages/cryptography/hazmat/primitives/ciphers/base.py", line 198, in finalize
>     data = self._ctx.finalize()
>   File "somewhere/a_virtual_env/lib/python3.8/site-packages/cryptography/hazmat/backends/openssl/ciphers.py", line 170, in finalize
>     raise InvalidTag
> cryptography.exceptions.InvalidTag

从这个输出中,我毫不怀疑引发了“InvalidTag”异常。

问题

我想使用 pytest 检查单元测试中是否引发了“InvalidTag”异常以通过测试。 根据我之前的解释,我做错了什么?

【问题讨论】:

    标签: python unit-testing exception pytest


    【解决方案1】:

    pytest.raises()match 参数对异常的字符串表示形式执行正则表达式匹配,这不是您想要的。

    相反,只需将 cryptography.exceptions.InvalidTag 类型作为第一个参数传递:

    from cryptography.exceptions import InvalidTag
    
    ...
    
    def test_decrypt():
        with pytest.raises(InvalidTag):
            decrypt()
    

    【讨论】:

      【解决方案2】:

      你检查的方式不对。您正在验证通用异常并尝试匹配错误消息,而不是实际异常。我认为它应该是这样的:

      from cryptography.exceptions import InvalidTag
      
      def test_decrypt():
           with pytest.raises(InvalidTag, match=''):
               decrypt()
      

      【讨论】:

        猜你喜欢
        • 2023-04-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-04-19
        • 2015-04-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多