私は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
Python unittest
アサーションは例外によって通信されるため、例外がメインスレッドで終了するようにする必要があります。したがって、.join()
を実行する必要があるスレッドの場合、スレッドからメインスレッドに例外がスローされるためです。
_ t = threading.Thread(target=lambda: self.assertTrue(False))
t.start()
t.join()
_
また、unittest
がそれらを登録する前に、例外を使い果たす可能性がある_try/except
_ブロックがないことを確認してください。
編集:self.fail()
が存在していても、スレッドから呼び出された場合、.join()
は実際には通信されません。どうなっているかわからない。
このコードが「例外なし」を出力するのと同じ理由で、テストは失敗していません
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()
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()
私のメインスレッドでは、終了コードをチェックすることでサブプロセスの失敗を検出しています(ゼロ以外は失敗)。
proc.join()
self.assertEqual(proc.exitcode, 0, 'Sub-process failed, check output for stack trace')