システムが起動する書面による機能テストの数はすでに数千を超えており、増え続けています。 この場合、1つの機能テストは1つの機能です。 この開発方法では、Readyのステータスをテストに割り当てた後、長い間そのことを忘れられます。
一方、他のテスト機能の開発プロセスでは、多くの場合、リファクタリングが必要になります。 さらに、このプロセスは、テスター自動化エンジニアの不注意により、既に終了したデバッグ済みテストに影響を与える可能性があります。
多くのモジュールや機能に影響する場合でも、プログラム自体をリファクタリングすることは一般的で非常に便利なことです。 ただし、テスト機能に関しては、常にそうとは限りません。 各テストは、特定の検証アルゴリズムを実装するように設計されています。 作成者が作成した検証ロジックは、テストコードにわずかな変更を加えた場合でも違反する場合があります。
このような状況の悪影響を回避するために、テスト関数のコードを修正するメカニズムを開発しました。これにより、関数の整合性を同時に制御し、コードを複製できます。
メカニズム
各関数について、リビジョン、ハッシュのセット、および関数コードを定義できます。
(func_hash, func_source)
すべての重要な機能を改訂辞書に追加できます。
{"funcName1": (funcName1_hash, funcName1_source), "funcName2": (funcName2_hash, funcName2_source), ...}
たとえば、私たちにとって、すでに開発されたテストを備えたすべての機能は重要です。 すべてのリビジョンは、特別なテキストファイル(リビジョンファイル)に保存できます。このファイルには、最後のリビジョンの日付とリビジョンディクショナリのリストが保存されます。
[revision's last date-n-time, {revisions}]
テストシステムの次のリリースの前に、改訂担当のスペシャリストは、機能コードの変更を追跡し、必要に応じて、古いテストのコードを短時間で復元し、単に改訂からコピーすることができます。
もちろん、コード検査やリポジトリ内のツール(GIT、SVNなど)の使用など、問題に対する代替ソリューションがあります。 ただし、数百のテストを自動的に変更する場合、検査は役に立たず、数回のマージ後にリポジトリツールを使用してコードの変更を追跡するのは面倒で長いプロセスです。 さらに、通常、ユニットテストはテスト関数に記述されていませんが、関数の品質と不変性を制御する必要性は残っています。このメカニズムは修正メカニズムによっても解決できます。
コード
上記のアイデアを実装するために、小さなモジュールFileRevision.pyがPythonで作成されました。 その中で使用可能なRevision()クラスをプロジェクトにインポートして、必要な機能のリビジョンを追加できます。
モジュールをわずかに変更したら、たとえばリビジョンファイルの圧縮を追加で実装できますが、小規模なプロジェクトではそれほど重要ではありません。
コードはリンクから入手できます。
モジュールの実装
クラスリビジョン():
__init __()#パラメーターの初期化
def __init__(self, fileRevision='revision.txt'): self.fileRevision = fileRevision self.mainRevision = self._ReadFromFile(self.fileRevision) # get main revision first
_ReadFromFile()#ファイルからリビジョンを取得する
def _ReadFromFile(self, file=None): """ Helper function that parse and return revision from file. """ revision = [None, {}] if file == None: file = self.fileRevision try: if os.path.exists(file) and os.path.isfile(file): with open(file) as fH: revision = eval(fH.read()) except: traceback.print_exc() finally: return revision
_WriteToFile()#ファイルにリビジョンを書き込みます。
def _WriteToFile(self, revision=[None, {}], file=None): """ Helper procedure than trying to write given revision to file. """ status = False if file == None: file = self.fileRevision try: with open(file, "w") as fH: fH.write(str(revision)) status = True except: traceback.print_exc() finally: return status
_GetOld()#関数の前のリビジョンを取得します。
def _GetOld(self, func=None): """ Get old revision for given function and return tuple: (old_hash, old_source). """ funcHashOld = None # old code is None if function not exist in previous revision funcSourceOld = None # old hash is None if function not exist in previous revision try: if func.__name__ in self.mainRevision[1]: funcHashOld = self.mainRevision[1][func.__name__][0] # field with old hash of function funcSourceOld = self.mainRevision[1][func.__name__][1] # field with old code of function except: traceback.print_exc() finally: return (funcHashOld, funcSourceOld)
_GetNew()#関数の新しいリビジョンを取得します。
def _GetNew(self, func=None): """ Get new revision for given function and return tuple: (new_hash, new_source). """ funcSourceNew = None # if function doesn't exist, its also doesn't have code funcHashNew = None # hash is None if function not exist try: funcSourceNew = inspect.getsource(func) # get function's source funcHashNew = hash(funcSourceNew) # new hash of function except: traceback.print_exc() finally: return (funcHashNew, funcSourceNew)
_Similar()#2つのリビジョンの比較。
def _Similar(self, hashOld, sourceOld, hashNew, sourceNew): """ Checks if given params for modified then return tuple with revision's diff: (old_revision, new_revision), otherwise return None. """ similar = True # old and new functions are similar, by default if hashNew != hashOld: if sourceOld != sourceNew: similar = False # modified if hashes are not similar and functions not contains similar code return similar
Update()#指定された関数のリビジョンを更新します。
def Update(self, func=None): """ Set new revision for function. revision = [revision date-n-time, {"funcName1": (funcName1_hash, funcName1_source), {"funcName2": (funcName2_hash, funcName2_source), ...}] """ status = False if func: try: funcSourceNew = inspect.getsource(func) # get function's source funcHashNew = hash(funcSourceNew) # new hash of function revisionDateNew = datetime.now().strftime('%d.%m.%Y %H:%M:%S') # revision's date funcRevisionNew = {func.__name__: [funcHashNew, funcSourceNew]} # form for function's revision self.mainRevision[0] = revisionDateNew # set new date for main revision self.mainRevision[1].update(funcRevisionNew) # add function's revision to main revision if self._WriteToFile(self.mainRevision): # write main revision to file status = True except: traceback.print_exc() finally: return status
DeleteAll()#ファイルからすべてのリビジョンを削除します。
def DeleteAll(self): """ Helper function that parse and return revision from file. """ status = False try: self.mainRevision = [None, {}] # clean revision if self._WriteToFile(self.mainRevision): # write main revision to file status = True except: traceback.print_exc() finally: return status
ShowOld()#関数の以前のリビジョンに関する情報を表示します。
def ShowOld(self, func=None): """ Function return old revision for given function. """ funcHashOld, funcSourceOld = self._GetOld(func) # get old revision for given function dateStr = "Last revision: " + str(self.mainRevision[0]) hashStr = "\nOld function's hash: " + str(funcHashOld) codeStr = "\nOld function's code:\n" + "- " * 30 + "\n" + str(funcSourceOld) + "\n" + "- " * 30 oldRevision = dateStr + hashStr + codeStr return oldRevision
ShowNew()#関数の新しいリビジョンに関する情報を表示します。
def ShowNew(self, func=None): """ Function return old revision for given function. """ funcHashNew, funcSourceNew = self._GetNew(func) # get old revision for given function hashStr = "New function's hash: " + str(funcHashNew) codeStr = "\nNew function's code:\n" + "- " * 30 + "\n" + str(funcSourceNew) + "\n" + "- " * 30 newRevision = hashStr + codeStr return newRevision
Diff()#必要に応じて、関数のリビジョンとdiff出力の比較。
def Diff(self, func=None): """ Checks if given function modified then return tuple with revision's diff: (old_revision, new_revision), otherwise return None. """ funcHashOld, funcSourceOld = self._GetOld(func) # get old revision for given function funcHashNew, funcSourceNew = self._GetNew(func) # get new revision for given function # check old and new revisions: if self._Similar(funcHashOld, funcSourceOld, funcHashNew, funcSourceNew): diff = None # not difference else: diff = ("Last revision: " + str(self.mainRevision[0]) + "\nOld function's hash: " + str(funcHashOld) + "\nOld function's code:\n" + "- " * 30 + "\n" + str(funcSourceOld) + "\n" + "- " * 30, "\nNew function's hash: " + str(funcHashNew) + "\nNew function's code:\n" + "- " * 30 + "\n" + str(funcSourceNew) + "\n" + "- " * 30) # if new function not similar old function return diff
_testFunction()#モジュールの動作をチェックするための偽の関数
def _testFunction(a=None): """ This is fake test function for module. """ # this is comment if a: return True else: return False
if __name__ == '__main__' :()#モジュールを個別に起動するときの使用例
func = _testFunction # set function for review in revision revision = Revision('revision.txt') # init revision class for using with revision.txt # how to use this module for review revision of function: print(MSG_CHECK, func.__name__) funcModified = revision.Diff(func) # get function's diff as tuple (old_revision, new_revision) if funcModified: print(MSG_MODIFIED) print(funcModified[0]) # old revision print(funcModified[1]) # new revision else: print(MSG_NOT_MODIFIED) # how to use this module for update revision: action = input("Update function's revision? [y/n]: ") if action == 'y': print(MSG_UPDATE, func.__name__) if revision.Update(func): print(MSG_UPDATED) else: print(MSG_UPDATE_ERROR) # how to use this module for clean file-revision: action = input("Clean file-revision now? [y/n]: ") if action == 'y': print(MSG_DELETE) if revision.DeleteAll(): print(MSG_DELETED) else: print(MSG_DELETE_ERROR) # how to use this module for show old review: action = input('Show old revision for function? [y/n]: ') if action == 'y': print(revision.ShowOld(func)) # how to use this module for show new review: action = input('Show new revision for function? [y/n]: ') if action == 'y': print(revision.ShowNew(func))
このモジュールの使用例を見るには、Python 3.2.3を使用して実行するだけです。
python FileRevision.py
最初の起動時に、スクリプトは、例で実装された偽の関数のリビジョンがないことを検出し、その情報を更新し、リビジョンファイルをクリアし、以前のリビジョンと新しいリビジョンに関する情報を表示します。 次に、.pyファイルの横に、例のリビジョンを含むrevision.txtファイルが作成されます。
したがって、モジュールを使用し、コード改訂ファイルの生成を担当する従業員がいると、テスト機能のセキュリティの度合いが高まります。
今日は以上です。 コメントでの質問や提案をお待ちしています。 ご清聴ありがとうございました!
Posted by Timur Gilmullin、Positive Technologies自動テストグループ