web-dev-qa-db-ja.com

Python unittest and multithreading

私はpythonのunittestを使用しており、いくつかのスレッドを開始してそれらが完了するのを待つテストを作成したいと思います。スレッドは、いくつかのunittestアサーションを持つ関数を実行します。いずれかのアサーションが失敗した場合、テストが失敗することを望みます。これはそうではないようです。

編集:実行可能な最小限の例(python3)

import unittest
import threading

class MyTests(unittest.TestCase):

    def test_sample(self):
        t = threading.Thread(target=lambda: self.fail())
        t.start()
        t.join()

if __name__ == '__main__':
    unittest.main()

そして出力は:

sh-4.3$ python main.py -v                                                                                                                                                                                                              
test_sample (__main__.MyTests) ... Exception in thread Thread-1:                                                                                                                                                                       
Traceback (most recent call last):                                                                                                                                                                                                     
  File "/usr/lib64/python2.7/threading.py", line 813, in __bootstrap_inner                                                                                                                                                             
    self.run()                                                                                                                                                                                                                         
  File "/usr/lib64/python2.7/threading.py", line 766, in run                                                                                                                                                                           
    self.__target(*self.__args, **self.__kwargs)                                                                                                                                                                                       
  File "main.py", line 7, in <lambda>                                                                                                                                                                                                  
    t = threading.Thread(target=lambda: self.fail())                                                                                                                                                                                   
  File "/usr/lib64/python2.7/unittest/case.py", line 450, in fail                                                                                                                                                                      
    raise self.failureException(msg)                                                                                                                                                                                                   
AssertionError: None                                                                                                                                                                                                                   

ok                                                                                                                                                                                                                                     

----------------------------------------------------------------------                                                                                                                                                                 
Ran 1 test in 0.002s                                                                                                                                                                                                                   

OK     
10
Ben RR

Python unittestアサーションは例外によって通信されるため、例外がメインスレッドで終了するようにする必要があります。したがって、.join()を実行する必要があるスレッドの場合、スレッドからメインスレッドに例外がスローされるためです。

_    t = threading.Thread(target=lambda: self.assertTrue(False))
    t.start()
    t.join()
_

また、unittestがそれらを登録する前に、例外を使い果たす可能性がある_try/except_ブロックがないことを確認してください。

編集self.fail()が存在していても、スレッドから呼び出された場合、.join()は実際には通信されません。どうなっているかわからない。

3
Grumbel

このコードが「例外なし」を出力するのと同じ理由で、テストは失敗していません

import threading

def raise_err():
    raise Exception()

try:
    t = threading.Thread(target=raise_err)
    t.start()
    t.join()
    print('no exception')
except:
    print('caught exception')

Unittestがテスト関数を実行すると、コードの実行によって何らかの例外が発生するかどうかを確認することにより、合否を判断します。例外がスレッド内で発生した場合でも、メインスレッドには例外はありません。

スレッドで何かを実行することで合格/不合格の結果を得る必要があると思われる場合は、このようなことを行うことができます。しかし、これは実際にはunittestが機能するように設計されているわけではなく、おそらく達成しようとしていることを実行するはるかに簡単な方法があるでしょう。

import threading
import unittest

def raise_err():
    raise Exception()
def no_err():
    return

class Runner():

    def __init__(self):
        self.threads = {}
        self.thread_results = {}

    def add(self, target, name):
        self.threads[name] = threading.Thread(target = self.run, args = [target, name])
        self.threads[name].start()

    def run(self, target, name):
        self.thread_results[name] = 'fail'
        target()
        self.thread_results[name] = 'pass'

    def check_result(self, name):
        self.threads[name].join()
        assert(self.thread_results[name] == 'pass')

runner = Runner()

class MyTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        runner.add(raise_err, 'test_raise_err')
        runner.add(no_err, 'test_no_err')

    def test_raise_err(self):
        runner.check_result('test_raise_err')

    def test_no_err(self):
        runner.check_result('test_no_err')

if __name__ == '__main__':
    unittest.main()
1
Fred S

concurrent.futures.ThreadPoolExecutorまたは https://docs.python.org/3/library/threading.html#threading.excepthook を使用して、スレッドでスローされた例外を収集します

import unittest
import threading
from concurrent import futures

class catch_threading_exception:
    """
    https://docs.python.org/3/library/test.html#test.support.catch_threading_exception
    Context manager catching threading.Thread exception using
    threading.excepthook.

    Attributes set when an exception is catched:

    * exc_type
    * exc_value
    * exc_traceback
    * thread

    See threading.excepthook() documentation for these attributes.

    These attributes are deleted at the context manager exit.

    Usage:

        with support.catch_threading_exception() as cm:
            # code spawning a thread which raises an exception
            ...

            # check the thread exception, use cm attributes:
            # exc_type, exc_value, exc_traceback, thread
            ...

        # exc_type, exc_value, exc_traceback, thread attributes of cm no longer
        # exists at this point
        # (to avoid reference cycles)
    """

    def __init__(self):
        self.exc_type = None
        self.exc_value = None
        self.exc_traceback = None
        self.thread = None
        self._old_hook = None

    def _hook(self, args):
        self.exc_type = args.exc_type
        self.exc_value = args.exc_value
        self.exc_traceback = args.exc_traceback
        self.thread = args.thread

    def __enter__(self):
        self._old_hook = threading.excepthook
        threading.excepthook = self._hook
        return self

    def __exit__(self, *exc_info):
        threading.excepthook = self._old_hook
        del self.exc_type
        del self.exc_value
        del self.exc_traceback
        del self.thread


class MyTests(unittest.TestCase):
    def test_tpe(self):
        with futures.ThreadPoolExecutor() as pool:
            pool.submit(self.fail).result()

    def test_t_excepthook(self):
        with catch_threading_exception() as cm:
            t = threading.Thread(target=self.fail)
            t.start()
            t.join()
            if cm.exc_value is not None:
                raise cm.exc_value


if __name__ == '__main__':
    unittest.main()
0
Thomas Grainger

私のメインスレッドでは、終了コードをチェックすることでサブプロセスの失敗を検出しています(ゼロ以外は失敗)。

proc.join()
self.assertEqual(proc.exitcode, 0, 'Sub-process failed, check output for stack trace')
0
Kieren Anderson