良い、悪い、悪い-初心者プロジェクトでテストする

序文:大学はタスクを受け取りました-スクラムチームを編成し、プロジェクトを選択して、1学期の間作業します。 私たちのチームは、Webアプリケーション開発(React + Flask)を選択しました。 この記事では、どのようなテストが行​​われるべきかを説明し、バックエンドで行ったことを分析します。













期待



テストは、まず、プログラムがテストの状況で正常に動作することを全員(自分自身を含む)に確信させるために必要です。 第二に、将来のテストカバーされるコードのパフォーマンスを保証します。 テストの作成は有用なプロセスです。そのプロセスでは、問題のある領域につまずいたり、極端なケースを思い出したり、インターフェイスの問題を確認したりすることが非常に多いためです。







システムを開発するときは、少なくとも3種類のテストを覚えておく必要があります。









Googleからの投稿の1つで、 3種類のテストの特性を記載した表が公開されました。 「小」、「中」、「大」。













単体テスト



単体テストは小規模なテストに対応します。高速で、プログラムの特定の部分の正確性のみをチェックする必要があります。 データベースにアクセスするべきではなく、複雑なマルチスレッド環境で動作するべきではありません。 それらは仕様/標準への準拠を制御し、しばしば回帰テストの役割を果たします







統合テスト



統合テストは、いくつかのモジュールと機能に影響を与える可能性のあるテストです。 このようなテストには時間がかかり、特別な環境が必要になる場合があります。 これらは、個々のモジュールと機能が相互に連携できるようにするために必要です。 つまり 単体テストでは、実際のインターフェイスが期待どおりであることを確認し、統合テスト-機能とモジュールが互いに正しく相互作用することを確認します。







システムテスト



これは最高レベルの自動テストです。 システムテストでは、システム全体が機能すること、その部分がタスクを実行し、正しく相互作用できることを確認します。







タイプを追跡する理由



通常、プロジェクトの成長に伴い、コードベースも成長します。 自動チェックの期間が長くなり、多数の統合をサポートし、システムテストがますます困難になります。 したがって、開発者にとっての課題は、必要なテストを最小限にすることです。 これを行うには、可能な場合は単体テストを使用し、「モック」(モック)を使用して統合を減らします。







現実



典型的なAPIテスト



def test_user_reg(client): return json.loads( client.post(url, json=data, content_type='application/json').data ) response = client.post('api/user.reg', json={ 'email': 'name@mail.ru', 'password': 'password1', 'first_name': 'Name', 'last_name': 'Last Name' }) data = json.loads(response.data) assert data['code'] == 0
      
      





フラスコ公式ドキュメントから、アプリケーションを初期化しデータベースを作成するための既製のレシピを入手します。 データベースを使用した作業は次のとおりです。 これは単体テストではありませんが、システムテストではありません。 これは、データベーステストアプリケーションを使用する統合テストです。







モジュラーではなく統合する理由 リクエストの処理では、フラスコ、ORM、ビジネスロジックとのやり取りが実行されるためです。 ハンドラーはプロジェクトの他の部分の統合要素として機能するため、ユニットテストの記述は単純すぎず(データベースをmokami、内部ロジックに置き換える必要があります)、実用的ではありません(統合テストは同様の側面をチェックします-「必要な関数は呼び出されましたか?」データは正しく受信されましたか?」など)。







テストの名前とグループ化



 def test_not_empty_errors(): assert validate_not_empty('email', '') == ('email is empty',) assert validate_not_empty('email', ' ') == ('email is empty',) assert validate_email_format('email', "") == ('email is empty',) assert validate_password_format('pass', "") == ('pass is empty',) assert validate_datetime('datetime', "") == ('datetime is empty',)
      
      





このテストでは、「小規模」テストのすべての条件が満たされます-依存関係のない関数の動作は、期待されるコンプライアンスをチェックします。 しかし、設計には疑問が生じます。







プログラムの特定の側面に焦点を当てたテストを作成することをお勧めします。 この例には、 validate_password_format



validate_datetime



validate_password_format



さまざまな関数があります。 グループ化チェックは結果ではなく、テストオブジェクトに基づいています。







テストの名前( test_not_empty_errors



)は、テストオブジェクト(テスト対象のメソッド)を記述せず、結果のみを記述します(エラーは空ではありません)。 このメソッドはtest__validate_not_empty__error_on_empty



と呼ばれるべきtest__validate_not_empty__error_on_empty



。 この名前は、テストされているものと期待される結果を説明しています。 これは、プロジェクトのほとんどすべてのテスト名に適用されます。これは、テストの命名規則について議論する時間が取られていないためです。







回帰テスト



 def test_datetime_errors(): assert validate_datetime('datetime', '0123-24-31T;431') == ('datetime is invalid',) assert validate_datetime('datetime', '2018-10-18T20:21:21+-23:1') == ('datetime is invalid',) assert validate_datetime('datetime', '2015-13-20T20:20:20+20:20') == ('datetime is invalid',) assert validate_datetime('datetime', '2015-02-29T20:20:20+20:20') == ('datetime is invalid',) assert validate_datetime('datetime', '2015-12-20T25:20:20+20:20') == ('datetime is invalid',) assert validate_datetime('datetime', '2015-12-20T20:61:20+22:20') == ('datetime is invalid',) assert validate_datetime('datetime', '2015-12-20T20:20:61+20:20') == ('datetime is invalid',) assert validate_datetime('datetime', '2015-12-20T20:20:20+25:20') == ('datetime is invalid',) assert validate_datetime('datetime', '2015-12-20T20:20:20+20:61') == ('datetime is invalid',) assert validate_datetime('datetime', '2015-13-35T25:61:61+61:61') == ('datetime is invalid',)
      
      





このテストはもともと最初の2つのassert



から成りました。 その後、「バグ」が発見されました-日付をチェックする代わりに、正規表現のみがチェックされました。 9999-99-99



は通常の日付と見なされました。 開発者が修正しました。 当然、バグを修正した後、将来の回帰を防ぐためにテストを追加する必要があります。 このテストが存在する理由を記述する新しいテストを追加する代わりにこのテストにチェックが追加されました。







検証を追加するために、新しいテストを呼び出す必要がありますか? おそらくtest__validate_datetime__error_on_bad_datetime









無視するツール



 def test_get_providers(): class Tmp: def __init__(self, id_external, token, username): self.id_external = id_external self.token = token self.username = username ...
      
      





Tmp



? これは、このテストで使用されていないオブジェクトの代替です。 開発者は、 @patch



MagicMock



存在を知らないようです。 コードを複雑にする必要はありません。適切なツールがあれば問題を単純に解決します。







(データベース内の)サービスを初期化し、アプリケーションコンテキストを使用するようなテストがあります。







 def test_get_posts(client): def fake_request(*args, **kwargs): return [one, two] handler = VKServiceHandler() handler.request = fake_request services_init() with app.app_context(): posts = handler.get_posts(None) assert len(posts) == 2
      
      





@patch



を1つ追加するだけで、データベースとコンテキストをテストから除外できます。







 @patch("mobius.services.service_vk.Service") def test_get_posts(mock): def fake_request(*args, **kwargs): return [one, two] handler = VKServiceHandler() handler.request = fake_request posts = handler.get_posts(None) assert len(posts) == 2
      
      





まとめ





非常に重要な点は、テストがバグの可用性または不在を保証しないことです。 テストは、プログラム(またはその一部)の実際の結果が期待されることを確認します。 この場合、検証は、テストが記述された側面に対してのみ発生します。 したがって、高品質の製品を作成する場合、他の種類のテストを忘れてはなりません。








All Articles