GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — TestCase.assertRaisesString · Issue #123013 · python/cpython

TestCase.assertRaisesString #123013

New issue
Copy link
New issue
Copy link
Closed as not planned
Closed as not planned
TestCase.assertRaisesString#123013
Copy link
Labels
stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement

Description

@dg-pb
dg-pb
opened on Aug 14, 2024
Issue body actions

Feature or enhancement

Proposal:

More often I need to check for exact error message and do not need regular expression.

For pure convenience, this would look like.

def assertRaisesString(self, exc, string, *args, **kwargs):
    escaped = re.escape(string)
    return self.assertRaisesRegex(exc, f'^{escaped}$', *args, **kwds)

However, as using regular expression is overkill, could just add pure string case to _AssertRaisesBaseContext:

class _AssertRaisesBaseContext(_BaseTestCaseContext):

    def __init__(self, expected, test_case, expected_string=None, regex=True):
        ...

class _AssertRaisesContext(_AssertRaisesBaseContext):
    def __exit__(self, exc_type, exc_value, tb):
        expected_string = self.expected_string
        exc_string = str(exc_value)
        if self.regex:
            # perform regex
        else:
            # compare strings

Has this already been discussed elsewhere?

This is a minor feature, which does not need previous discussion elsewhere

Links to previous discussion of this feature:

No response

Linked PRs

  • gh-123013: TestCase.assertRaisesString #123023

Activity

  1. dg-pb
    added
    type-featureA feature request or enhancementA feature request or enhancement
    on Aug 14, 2024
  2. Eclips4
    added
    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
    on Aug 14, 2024
  3. sobolevn commented on Aug 15, 2024

    @sobolevn
    sobolevn
    on Aug 15, 2024
    Member
    More actions

    I am a bit sceptical about adding features that only take 3 lines of code to implement in your own code (from your example):

    def assertRaisesString(self, exc, string, *args, **kwargs):
        escaped = re.escape(string)
        return self.assertRaisesRegex(exc, f'^{escaped}$', *args, **kwds)

    On the other hand, in our own test code this is a very popular pattern.

    I would vote for direct string compare, instead of escaped regex match (if this feature is accepted).

    I think that adding performance numbers here can help: how fast it is to run a test case with self.assertRaisesRegex(re.escape(...)) and how fast it is to run the same with direct string compare. It might be helpful when there are a lot of such tests.

    Reacted by Kirill Podoprigora and Bénédikt Tran
  4. picnixz commented on Aug 15, 2024

    @picnixz
    picnixz
    on Aug 15, 2024
    Member
    More actions

    I'm also of the same mind. What I could however suggest is that we extend the unittest.TestCase class by adding test.support.TestCase with some methods like that. It would help us keep the same syntax and we won't need to have to pass self to the function.

  5. dg-pb commented on Aug 15, 2024

    @dg-pb
    dg-pb
    on Aug 15, 2024
    ContributorAuthor
    More actions

    To me it probably would be most natural to have only single method assertRaises(exc, expected_string=None, regex=False, callback=None). I quite like APIs where by default it is not regex, but there is a flag for it, such as ripgrep.

    However, there is an issue of callback with full *args and **kwds mixed up into the signature.

    I used to use similar patterns in the past, but don't do this anymore, instead just have callback argument and if I need args or kwds, I just use partial, otherwise it makes extensions difficult, such as this case.

  6. serhiy-storchaka
    added this to Unittest & doctest issueson Aug 15, 2024
  7. github-project-automation
    moved this to Todo in Unittest & doctest issueson Aug 15, 2024
  8. serhiy-storchaka commented on Aug 15, 2024

    @serhiy-storchaka
    serhiy-storchaka
    on Aug 15, 2024
    Member
    More actions

    There are many more required unittest features on the list. I am skeptical about this one.

  9. dg-pb commented on Aug 15, 2024

    @dg-pb
    dg-pb
    on Aug 15, 2024
    ContributorAuthor
    More actions

    Benchmark results (100_000 runs):

    regex-convenient: 0.4 s
    regex-manual: 0.24 s
    string: 0.17 s
    change-convenient: -57%
    change-manual: -29%

    Relative difference is non-trivial, however aggregate impact is small. Could have a slight impact far far in the future, but now it is tiny. CPython - 2041 hits of assertRaisesRegex.

    I am +1 for convenience. I think it is useful to have this for CPython tests.

    And +0.1 on implementation. Alternatively, could just add 3 lines of Python, but I am still slightly biased towards not computing more than needed given it is a simple addition.

    import re, time
    import unittest as utt
    
    RNG = list(range(100_000))
    
    class TestCase(utt.TestCase):
        def bench_string(self):
            for i in RNG:
                with self.assertRaisesString(TypeError, 'Error Message'):
                    raise TypeError('Error Message')
    
        def bench_regex_manual(self):
            for i in RNG:
                with self.assertRaisesRegex(TypeError, '^Error Message$'):
                    raise TypeError('Error Message')
    
        def bench_regex_convenient(self):
            for i in RNG:
                msg = 'Error Message'
                msg = re.escape(msg)
                with self.assertRaisesRegex(TypeError, f'^{msg}$'):
                    raise TypeError('Error Message')
    
        def test_bench(self):
            for f in [self.bench_regex_convenient, self.bench_regex_manual, self.bench_string]:
                t = time.perf_counter()
                f()
                print(f'{(time.perf_counter() - t) * 1000:.2f} ms')
    
    if __name__ == '__main__':
        utt.main()
  10. serhiy-storchaka commented on Aug 16, 2024

    @serhiy-storchaka
    serhiy-storchaka
    on Aug 16, 2024
    Member
    More actions

    Usually, it is enough to only test that some words or sequence of words are occurred in the error message, just to differentiate it from other errors with the same type. Such tests are less fragile, you can slightly rewrite the error message without changing tests.

    If you need to check the exact error message, it may be better to write it as:

    with self.assertRaises(...) as cm:
        ...
    self.assertEqual(str(cm.exception), ...)

    This works also for warnings, and you can test also other attributes, for example the stack level for warnings.

    Reacted by sobolevn, dgpb and Zachary Ware
  11. dg-pb commented on Aug 16, 2024

    @dg-pb
    dg-pb
    on Aug 16, 2024
    ContributorAuthor
    More actions

    If you need to check the exact error message, it may be better to write it as:

    This is sensible. As long as it is clear that this is the recommended pattern for it, I think this is sufficient.

    If everyone is happy with this, I am closing this issue and PR.

  12. dg-pb commented on Aug 16, 2024

    @dg-pb
    dg-pb
    on Aug 16, 2024
    ContributorAuthor
    More actions

    .

  13. dg-pb
    closed this as not plannedon Aug 16, 2024
  14. github-project-automation
    moved this from Todo to Done in Unittest & doctest issueson Aug 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement

    Projects

    • Unittest & doctest issues
      Status
      Done
      Show more project fields

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    You are visitor 01402520 — GeezerVison does not store the pages you look up.
    Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.