ユニットテストと例外処理の違いを理解するために、丸2日を費やしましたが、理解できません。
私が理解した(または私が理解したと思う)こと):
私が取得しないもの:
私が使用できるかどうか-そしてそれが良いか悪いか-単体テストのステートメントを試して/除外する
両方がまったく同じ目標を達成することを目的としている、つまりより「読みやすい」方法で例外を処理することが真実である場合
それらが2つの異なるものである場合
それらを一緒に使用する必要がある場合
たとえば、ターン属性を持つゲームクラスがあります。私がやりたいのは、ターン値が正の数であることをテストすることです。負になることはないことはわかっていますが、これを使用してテストの練習をしたいだけです。
現実の世界では、メインコードでtry/exceptステートメントを使用するか、それともユニットテストを実行する必要がありますか。
このコードを含むメインファイルがあります。
import pygame
# initialize
pygame.init()
# set the window
window_size = (800, 800)
game_window = pygame.display.set_mode(window_size)
pygame.display.set_caption('My Canvas')
# define colours
colours = {
'black': (0, 0, 0),
'white': (255, 255, 255),
'gold': (153, 153, 0),
'green': (0, 180, 0)
}
class Game:
def __init__(self):
self.turn = 0
self.player = None
self.winner = None
# getter methods
def get_turn(self):
try:
assert self.turn >= 0
except ValueError:
print('turn number must be positive')
def get_player(self):
return self.player
def get_winner(self):
return self.winner
# setter methods
def set_turn(self, turn):
self.turn = int(turn)
def set_player(self, player):
self.player = player
def set_winner(self, winner):
self.winner = winner
そして、そのすぐ隣にtest.pyファイルが開いているので、次のコードですぐにtestを記述できます。
import unittest
from canvas import Game
class TestPlayer1(unittest.TestCase):
def setUp(self):
# Game objects
self.game_turn_0 = Game()
self.game_turn_5 = Game()
self.game_turn_negative = Game()
# values
self.game_turn_0.turn = 0
self.game_turn_5.turn = 5
self.game_turn_negative.turn = -2
def test_get_turn(self):
self.assertEqual(self.game_turn_0.get_turn(), 0)
self.assertEqual(self.game_turn_5.get_turn(), 5)
with self.assertRaises(ValueError):
self.game_turn_negative.get_turn()
if __name__ == '__main__':
unittest.main()
説明をお願いします。
ありがとうございました
get_turn
メソッドを見てみましょう。
def get_turn(self):
try:
assert self.turn >= 0
except ValueError:
print('turn number must be positive')
ここにはいくつかの問題があります:
-O
フラグを指定して実行すると、アサーションは実行されません。AssertionError
ではなくValueError
が発生しますself.turn
が負の場合、実際のバグは別の場所で発生しています。これが私がそれを書き直す方法です:
def set_turn(self, turn):
if turn < 0:
raise ValueError('Turn number must be nonnegative')
self.turn = int(turn)
def get_turn(self):
assert self.turn >= 0 # This should never fail. If it does, you made a programming mistake.
return self.turn
そして、単体テストでは、期待どおりに動作することを確認するための呼び出しを記述できます。
例外処理と単体テストは、さまざまな問題を解決します。
例外処理は実行時の問題ですが、単体テストはビルド時の問題です。
二人は一緒に働くことができます。たとえば、ある場所から別の場所にファイルを移動しようとするユーティリティがあり、それらが少しずつ同じであることを確認するとします。ユーティリティでは、使用可能なファイルをスキャンして、それらを1つずつ処理する必要があります。
ユーティリティがファイルをコピーできない原因はいくつかあります。
それらはすべて例外です。これらの例外が原因でユーティリティの処理が停止するかどうかを判断する必要があります。たとえば、最後の例外は、途中で停止したくないものであり、別のプロセスにファイルがある場合は、ファイルをスキップしても問題ありません。権限の問題は、ユーティリティがその仕事をすることを本当に妨げているので、それが事実であるなら、あなたは止める必要があります。
単体テストでは、これらの各条件が満たされるシナリオを設定し、アプリケーションが適切に動作することをassert
します。
exists
チェックを行う必要があります。ご覧のとおり、これらはアプリケーションを改善するための相互に排他的な概念ではありません。ただし、目的は非常に異なります。
単純なif
ステートメントの方が適切な場合は、例外処理を使用すると言います。コードを変更する必要があります。指定した例では、次のようなスニペットがありました。
try:
assert self.turn >= 0
except ValueError:
print('turn number must be positive')
これはassert
の誤用です。特に、例外をメソッドから離れさせないためです。次のようにすると、よりクリーンになり、(特にループで)より速く実行できます。
if selt.turn < 0:
print('turn number must be positive')
そのコードは、より読みやすい方法で同じ効果を提供し、例外処理のオーバーヘッドがありません。例外処理は高価です。
これで、ゲームが未確定の状態にあるためにゲームを停止する例外的なケースにしたい場合は、例外をnotでキャッチする必要がありますそのコードを呼び出して、コードを呼び出している人にそれを処理させます。これは単に次のようになります。
assert(self.turn >= 0, 'turn number must be positive')