Unittest自動テストフレームワークの解説と実戦

単体テストを学ぶ理由

テストフェーズに応じて、テストは単体テスト、結合テスト、システムテスト、受け入れテストに分類できます。単体テストとは、ソフトウェア内のテスト可能な最小単位をプログラムの他の部分から切り離して検査および検証する作業を指し、通常は開発される関数またはクラスを指します。

単体テストにより、テスト作業を前進させ、問題を早期に発見し、問題解決のコストを削減できます。同時に、単体テストによって単一モジュールの機能に問題がないことを確認し、その後の統合テストの準備を整え、合成後の問題を軽減することもできます。

テストの場合、単体テストはユースケースの実行です。ユースケースをより適切に管理するには、Python に付属する単体テスト フレームワークの Unittest を学習する必要があります。

単体テストのフレームワークと原則

Unittest は Python に付属するテスト フレームワークのセットであり、比較的簡単に学習できます。unittest フレームワークには 4 つの中心的な概念があります。

  • test case: テストケース。Unittest には基本クラス TestCase が提供されており、新しいテスト ケースの作成に使用できます。TestCase の例はテスト ケースです。unittest 内のテスト ケースのメソッドはすべて で始まり、実行順序は ASCII 値に従ってソートされ testますメソッド名の .
  • test fixure:テストフィクスチャ。これは、テスト ケースの切り替え、つまりユース ケース テスト前の環境の大部分 (SetUp事前条件) と、テスト後の環境の復元 (TearDown事後条件) の構築と破棄に使用されます。例えば、テスト前にログインしてテストケースで必要な環境であるトークンを取得し、操作完了後は次のユースケース実行前に環境に影響を与えないように復元する必要があります。次のユースケースのテスト結果。
  • test suit: テストスイート。これは、一緒に実行する必要がある複数のテスト ケースを実行するために使用され、バスケットに相当します。通常、 TestLoaderテスト ケースをテスト スイートにロードするために使用します。
  • test runner: テスト走行。テストケースを実行し、テストケースの実行結果を返すために使用されます。グラフィックまたはテキスト インターフェイスと組み合わせて、返されたテスト結果をより鮮明に表示できます。 HTMLTestRunner.

unittest断言

アサーションはテスト ケースの重要な部分であり、操作が正しいことを確認するために使用できます。たとえば、ログイン プロセスでは、成功したページにはユーザー名に類似した要素が含まれている必要があります。このとき、アサーションを使用して、期待される結果が実際の結果と一致するかどうかを判断できます。一致する場合、テスト ケースは次のようになります。合格とみなされます。

Python ベースには、 assert基本的に 形式を使用するassert メソッドが ありますassert 表达式,基本信息Unittest フレームワークでは、組み込みのアサーション メソッドも提供されており、アサーションが失敗または失敗した場合はアサーション AssertionErrorエラーがスローされ、成功した場合は合格としてマークされます。

次のアサーション メソッドはすべて 1 つの msg=Noneパラメーターを持ち (表には最初のパラメーターのみがリストされていますが、すべて 1 つあります)、デフォルトで を返します Noneただし、msg パラメータの値が指定されている場合、メッセージは失敗のエラー メッセージとして返されます

方法 診る
assertEqual(a, b ,msg=None) a == b
assertNotEqual(a, b) a != b
アサートTrue(x) bool(x) は True
アサートFalse(x) bool(x) は False です
アサートIs(x, b) aはbです
assertIsNot(a, b) aはbではない
アサートIsNone(x) x はなしです
アサートIsNotNone(x) x は None ではありません
アサートイン(a, b) bのa
assertNotIn(a, b) aはbにない
assertIsInstance(a, b) インスタンス(a, b)
assertNotIsInstance(a, b) インスタンス(a, b)ではありません

TestCase テストケース

テスト ケースを作成する前に、unittest で TestCase クラスを継承するクラスを作成する必要があります。その後、実際に Unittest フレームワークを使用してテスト ケースを作成できるようになります。

次のように進めます。

  • インポートunittestモジュール
  • unittest.TestCaseクラスを継承するテストクラスを作成する
  • テスト メソッドを定義します。メソッド名は次でtest_始まる必要があります。
  • テスト ケースを実行するには、unittest.main() メソッドを呼び出します。unittest.main()このメソッドは、このモジュールの test で始まるすべてのテスト ケースとメソッドを検索し、自動的に実行します。

コード例

# 注册功能代码

# users列表存储成功注册的用户
users = [{'username': 'testing', 'password': '123456'}]  

def register(username, password1, password2):
  
  if not all([username, password1, password2]):
    return {'code': 0, 'msg': '所有参数不能为空.'}
  
  # 注册
  for user in users:
    if username == user['username']:
      return {'code': 0, 'msg': '用户名已存在!'}
  
  else:
    if password1 != password2:
      return {'code': 0, 'msg': '两次密码输入不一致!'}
    else:
      if 6 <= len(username) <= 18 and 6 <= len(password1) <= 18:
        # 追加到users列表
        users.append({'username': username, 'password': password2})
        return {'code': 0, 'msg': '注册成功.'}
      else:
        return {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
  
  


import unittest
from demo import register # 导入被测试代码

class RegisterTest(unittest.TestCase):
    '''注册接口测试类'''
  
    def test_register_success(self):
        '''注册成功'''
        data = ('palien', 'palien', 'palien') # 测试数据
        result = register(*data) # 测试结果
        expected = {'code': 0, 'msg': '注册成功.'} # 预期结果
        self.assertEqual(result, expected) # 断言测试结果与预期结果一致
        # pass
  
    def test_username_exist(self):
        '''注册失败-用户名已存在'''
        data = ('testing', '123456', '123456')
        result = register(*data)
        expected = {'code': 0, 'msg': '用户名已存在!'}
        self.assertEqual(result, expected)
  
    def test_username_isnull(self):
        '''注册失败-用户名为空'''
        data = ('', 'palien', 'palien')
        result = register(*data)
        expected = {'code': 0, 'msg': '所有参数不能为空.'}
        self.assertEqual(result, expected)
        # pass
  
    def test_username_lt18(self):
        '''注册失败-用户名长度大于18位'''
        data = ('palienpalienpalienpalien', 'palien', 'palien')
        result = register(*data)
        expected = {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
        self.assertEqual(result, expected)
        # pass
  
    def test_password1_not_password2(self):
        '''注册失败-两次输入密码不一致'''
        data = ('palien', 'palien1', 'palien2')
        result = register(*data)
        expected = {'code': 0, 'msg': '两次密码输入不一致!'}
        self.assertEqual(result, expected)
        # pass
  
# 如果要直接运行这个测试类,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':
    unittest.main()

# Output
Windows PowerShell
版权所有 (C) Microsoft Corporation。保留所有权利。

尝试新的跨平台 PowerShell https://aka.ms/pscore6

PS D:\d_02_study\01_git> cd d:/d_02_study/01_git/papers/system/02automation
PS D:\d_02_study\01_git\papers\system\02automation> & C:/Users/TDH/AppData/Local/Programs/Python/Python310-32/python.exe d:/d_02_study/01_git/papers/system/02automation/demo.py
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK
PS D:\d_02_study\01_git\papers\system\02automation> 

TestFixture テストフィクスチャ

Unittest のテスト フィクスチャには 2 つの使用方法があり、1 つはテスト ケースのメソッドに基づく方法:setUp()と tearDown()、もう 1 つはテスト クラスに基づく方法:setUpClass()と ですtearDownClass()

コード例:

# users列表存储成功注册的用户
users = [{'username': 'testing', 'password': '123456'}]  

def register(username, password1, password2):
  
  if not all([username, password1, password2]):
    return {'code': 0, 'msg': '所有参数不能为空.'}
  
  # 注册
  for user in users:
    if username == user['username']:
      return {'code': 0, 'msg': '用户名已存在!'}
  
  else:
    if password1 != password2:
      return {'code': 0, 'msg': '两次密码输入不一致!'}
    else:
      if 6 <= len(username) <= 18 and 6 <= len(password1) <= 18:
        # 追加到users列表
        users.append({'username': username, 'password': password2})
        return {'code': 0, 'msg': '注册成功.'}
      else:
        return {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
  
  


import unittest
from demo import register # 导入被测试代码

class RegisterTest(unittest.TestCase):
    '''注册接口测试类'''
  
    @classmethod # 指明这是个类方法,以类为维度去执行的
    def setUpClass(cls) -> None:
          '''整个测试用例类中的用例执行之前,会先执行此方法'''
          print('-----setup---class-----')
  
    @classmethod
    def tearDownClass(cls) -> None:
          '''整个测试用例类中的用例执行完成后,会执行此方法'''
          print('-----teardown---class-----')
  
    def setUp(self):
        '''每条测试用例执行前都会执行'''
        print('用例{}开始执行...'.format(self))
  
    def tearDown(self):
        '''每条测试用例执行结束后都会执行'''
        print('用例{}执行结束...'.format(self))
  
    def test_register_success(self):
        '''注册成功'''
        data = ('palien', 'palien', 'palien') # 测试数据
        result = register(*data) # 测试结果
        expected = {'code': 0, 'msg': '注册成功.'} # 预期结果
        self.assertEqual(result, expected) # 断言测试结果与预期结果一致
        # pass
  
    def test_username_exist(self):
        '''注册失败-用户名已存在'''
        data = ('testing', '123456', '123456')
        result = register(*data)
        expected = {'code': 0, 'msg': '用户名已存在!'}
        self.assertEqual(result, expected)
  
    def test_username_isnull(self):
        '''注册失败-用户名为空'''
        data = ('', 'palien', 'palien')
        result = register(*data)
        expected = {'code': 0, 'msg': '所有参数不能为空.'}
        self.assertEqual(result, expected)
        # pass
  
    def test_username_lt18(self):
        '''注册失败-用户名长度大于18位'''
        data = ('palienpalienpalienpalien', 'palien', 'palien')
        result = register(*data)
        expected = {'code': 0, 'msg': '用户名和密码的长度必须在6~18位之间.'}
        self.assertEqual(result, expected)
        # pass
  
    def test_password1_not_password2(self):
        '''注册失败-两次输入密码不一致'''
        data = ('palien', 'palien1', 'palien2')
        result = register(*data)
        expected = {'code': 0, 'msg': '两次密码输入不一致!'}
        self.assertEqual(result, expected)
        # pass
  
# 如果要直接运行这个测试类,需要使用unittest中的main函数来执行测试用例
if __name__ == '__main__':
    unittest.main()


### Output
PS D:\d_02_study\01_git> cd d:/d_02_study/01_git/papers/system/02automation
PS D:\d_02_study\01_git\papers\system\02automation> & C:/Users/TDH/AppData/Local/Programs/Python/Python310-32/python.exe d:/d_02_study/01_git/papers/system/02automation/demo.py
-----setup---class-----
用例test_password1_not_password2 (__main__.RegisterTest)开始执行...   
用例test_password1_not_password2 (__main__.RegisterTest)执行结束...   
.用例test_register_success (__main__.RegisterTest)开始执行...
用例test_register_success (__main__.RegisterTest)执行结束...
.用例test_username_exist (__main__.RegisterTest)开始执行...
用例test_username_exist (__main__.RegisterTest)执行结束...
.用例test_username_isnull (__main__.RegisterTest)开始执行...
用例test_username_isnull (__main__.RegisterTest)执行结束...
.用例test_username_lt18 (__main__.RegisterTest)开始执行...
用例test_username_lt18 (__main__.RegisterTest)执行结束...
.-----teardown---class-----

----------------------------------------------------------------------
Ran 5 tests in 0.004s

OK
PS D:\d_02_study\01_git\papers\system\02automation> 

TestSuit テスト スイート

unittest.TestSuit()クラスは、テスト ケースのセット、実行する必要があるテスト ケース クラスまたはモジュールのコレクションを表すために使用されます。

一般的に使用される方法:

  • ユニットテスト.TestSuit()
    • addTest(): 単一のテスト ケース メソッドを追加します
    • addTest([...]): 複数のテスト ケース メソッドを追加します。メソッド名のリストがあります。
  • ユニットテスト.TestLoader()
    • loadTestsFromTestCase(测试类名): テストクラスを追加します
    • loadTestsFromMdule(模块名): モジュールを追加します
    • discover(测试用例所在的目录): ロードするディレクトリを指定すると、このディレクトリ内の命名規則を満たすすべてのテスト ケースが自動的に検索されます。

コード例:

'''
    以下三个文件必须在同一文件夹下:
    demo.py
    test_demo.py
    run_test.py
'''

import os
import unittest
import test_demo

# 第一步,创建一个测试套件
suit = unittest.TestSuite()

# 第二步,将测试用例加载到测试套件中

# # 方式一,添加单条测试用例
# case = test_demo.RegisterTest('test_register_success')
# '''
# 创建一个用例对象。
# 注意:通过用例类去创建测试用例对象的时候,需要传入用例的方法名(字符串类型)
# 这里不是像调用普通类中的方法那样通过类名.方法名调用,可以理解为unittest框架的特殊之处
# '''
# suit.addTest(case) # 添加用例到测试套件中

# # 方式二:添加多条用例
# case1 = test_demo.RegisterTest('test_username_exist')
# case2 = test_demo.RegisterTest('test_username_isnull')
# suit.addTest([case1, case2]) # 添加用例到测试套件中。注意这里使用的是suit.addTest()方法而不是suit.addTests()方法

# # 方式三:添加一个测试用例集
# loader = unittest.TestLoader() # 创建一个加载对象
# suit.addTest(loader.loadFromTestCase(test_demo.RegisterTest)) # 通过加载对象从测试类中加载用例到测试套件中

# '''
#     通产我们使用方式4、5比较多,可以根据实际情况来运用。
#     其中方式5还可以自定义匹配规则,默认是会寻找目录下的test*.py文件,即所有以test开头命名的py文件。
# '''
# # 方式四:添加一个模块(其实就是一个后缀名为.py文件,这里就是test_demo.py文件)
# loader = unittest.TestLoader() # 创建一个加载对象
# suit.addTest(loader.loadTestsFromModule(test_demo))  # 通过加载对象从模块中加载测试用例到测试套件中

# 方式五:指定测试用例的所在目录路径,进行加载
loader = unittest.TestLoader() # 创建一个加载对象
case_path = os.path.dirname(os.path.abspath(__file__)) # 文件路径
# print('用例所在的目录路径为:', case_path)
# suit.addTest(loader.discover(case_path))  # 通过加载对象从用例所在目录加载测试用例到测试套件
suit.addTest(loader.discover(start_dir=case_path, pattern='test_demo*.py')) # 两个参数:路径和匹配规则

TestRunner 実行の使用例

testRunnerユースケースを実行し、対応するテストレポートを生成するために使用されます。テスト レポートには 2 つの形式があります: 1 つは text文本、もう 1 つは です html格式

HTML 形式は、  HTMLTestRunnerPython 標準ライブラリの Unittest フレームワークの拡張機能であるプラグインを利用して生成され、明確で直感的な HTML テスト レポートを生成できます。使用の前提条件は、ダウンロードする必要があることです HTMLTestRunner.py。ダウンロードが完了したら、Python インストール ディレクトリの下の script ディレクトリに配置できます。

テキストのテキストは html に比べて粗雑すぎ、出力コンソールと何ら変わりはなく、ほとんど適用できません。

コード例:

# demo.py,与test_demo.py和run_test.py在同一目录下

# 导入模块
import unittest
import os
import test_demo
from HTMLTestReportCN import HTMLTestRunner

# 用例文件所在目录
base_path = os.path.dirname(os.path.abspath(__file__))
# report_path = base_path + 'report.html'

# 打开报告文件


# 创建测试套件
suit = unittest.TestSuite()

# 通过模块加载测试用例
loader = unittest.TestLoader()
suit.addTest(loader.discover(start_dir=base_path, pattern='test_demo*.py'))

# 创建测试运行程序启动器
runner = HTMLTestRunner(
    stream=open('report.html', 'w', encoding='utf-8'),   # 打开一个报告文件,并将句柄传给stream
    tester='palien',                    # 报告中显示的测试人员  
    description='注册接口测试报告',      # 报告中显示的描述信息
    title='自动化测试报告'               # 测试报告标题
)

# 使用启动器去执行测试套件里面的测试用例
runner.run(suit)

関連パラメータの説明:

  • stream:出力方法を指定します
  • tester: レポートに表示するテスターの名前
  • description:レポートに表示する説明情報
  • title: テストレポートのタイトル
  • verbosity: テストレポート情報の詳細レベルを示します。合計 3 つの値、デフォルトは 2 です。
    • 0(サイレントモード):合計100件、失敗90件など、合計テストケースブックと合計結果のみ取得可能
    • 1(デフォルト モード): サイレント モードと似ていますが、成功した各ユース ケースの前に F がある点と、失敗した各ユース ケースの前に F がある点が異なります。
    • 2(冗長モード): テスト結果には、各テスト ケースに関連するすべての情報が表示されます。

実行後、プロジェクト ディレクトリにファイルが生成されreport.html、ブラウザで開くとテスト レポートが表示されます。

問題記録

学習の過程でいくつかの問題に遭遇したので記録しました。

  • HTMLTestRunnerダウンロード

次の 2 つのファイルが上記のスクリーンショットのレポートの生成をサポートしていることが確認されています。

  • エラー TypeError: a bytes-like object is required, not 'str'解決

これは、ファイルを開く方法が間違っていることが原因です。問題解決ブログを添付してください: https://blog.csdn.net/Teresa_lqs/article/details/126250505?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none- task-blog-2~default~CTRLIST~Rate-1-126250505-blog-116235034.pc_relevant_default& Depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~Rate-1-126250505-blog-116 235034 .pc_relevant_default&utm_relevant_index= 1

  • 中国の報道文字化け問題

問題は次の図に示すとおりです。

おすすめ

転載: blog.csdn.net/xiao1542/article/details/130622535