😀

自動バックエンドテストやってみた: MSTest,WPF,.NET Framework4.7.2

2023/04/10に公開

はじめに

テスト工程があまりにめんどうなのでいい方法ないかなと思い、自動テストについてChatGPTさんにいろいろ聞いてやってみました。

目次

自動テスト動作環境

C#
.NET Framework4.7.2
WPF
MSTest

バックエンドの自動テストのためのフレームワーク

Windowsアプリケーションのテスト自動化

xUnit、NUnit、または MSTestでどれを使うのがいいなどありますか?
また動作試験をしたいプロジェクトは、.Net Framework4.7.2を利用して、WPFで開発をしています。

各テストフレームワークには、それぞれ異なる機能と利点があります。どのフレームワークを選択するかは、プロジェクトの要件とチームの好みによって異なります。以下に、xUnit、NUnit、および MSTest のいくつかの主要な違いを示します。

xUnit:

  • xUnit は、より最近のテストフレームワークであり、.NET Core と .NET 5+ との互換性が高いです。
  • クリーンでモダンなアプローチを提供し、特性とアサーションのための豊富な API を持っています。
  • パラメータ化されたテストがサポートされており、Theory 属性と InlineData 属性を使用してデータ駆動テストを実装できます。

NUnit:

  • NUnit は、.NET 開発者にとって非常に人気のあるテストフレームワークで、長い歴史があります。
  • それは豊富な特性とアサーションのセットを提供し、柔軟性が高いです。
  • NUnit は、.NET Framework と .NET Core の両方で使用できますが、.NET Core サポートは xUnit ほど最適化されていません。

MSTest:

  • MSTest は、Microsoft が開発およびサポートしている公式のテストフレームワークです。
  • Visual Studio との統合が非常に良好で、設定と実行が簡単です。
  • MSTest は .NET Framework と .NET Core の両方で動作しますが、xUnit や NUnit ほど多機能ではありません。

あなたのプロジェクトが .NET Framework 4.7.2 を使用している場合、NUnit または MSTest のいずれかを検討することが適切です。xUnit は主に .NET Core および .NET 5+ 向けに設計されているため、互換性の問題が発生する可能性があります。ただし、xUnit は .NET Framework 4.5.2 以降もサポートしているため、xUnit を使用しても問題はないかもしれません。

自動テストのやり方

システムを動作させるプロジェクトと、自動テスト用のプロジェクトの2つを用意し、システムを動作させるプロジェクトから自動テスト用のプロジェクトを参照して自動テストを行うという認識で合ってますか?

いいえ、実際には逆の関係です。自動テスト用のプロジェクトが、システムを動作させるプロジェクトを参照します。このようにすることで、テストプロジェクト内でシステムのコードにアクセスし、テストを行うことができます。

ソリューションの構成は以下のようになります。

システムを動作させるプロジェクト(アプリケーションの主要なコードが含まれる)。
自動テスト用のプロジェクト(テストコードが含まれる)。
自動テスト用のプロジェクトは、システムを動作させるプロジェクトに参照を追加します。これにより、テストプロジェクト内でアプリケーションのコードにアクセスしてテストを実行できます。

テストプロジェクトを作成し、システムプロジェクトへの参照を追加する手順は次のとおりです。

ソリューションに新しいテストプロジェクトを追加します(xUnit、NUnit、または MSTest を使用)。
テストプロジェクトを右クリックし、[参照の追加] を選択して、システムを動作させるプロジェクトを選択します。
これで、テストプロジェクト内でシステムのコードを使用してテストを実行できるようになります。

上記の自動テストは一般的な自動テストのやり方ですか?

はい、上記で説明した手順は、C# で自動テストを実行する際の一般的なやり方です。アプリケーションのコードをテストするために、別のテストプロジェクトを作成し、そのプロジェクトにアプリケーションのコードへの参照を追加することで、テストを実行できるようになります。

主要なテストフレームワーク(xUnit、NUnit、MSTest)は、基本的に同じアプローチを使用しますが、それぞれに独自の構文や特徴があります。これらのフレームワークを使って、単体テストや統合テストを実行し、アプリケーションの正確性と信頼性を確認します。

自動テストは、ソフトウェア開発プロセスの一部として一般的に使用されており、特にアジャイル開発や継続的インテグレーション/デリバリー(CI/CD)の環境では、自動テストは非常に重要な役割を果たしています。自動テストを実行することで、コードの変更による機能の破損を早期に検出でき、開発者が問題を修正してアプリケーションの品質を向上させることができます。

Webアプリケーションでのテスト自動化

他言語を使ったWebアプリケーション開発でも同様の手順になりますか?

はい、他の言語やフレームワークを使用したWebアプリケーション開発でも、同様の手順が適用されます。一般的に、アプリケーションのコードとテストコードは別々のプロジェクトやモジュールに分けられます。そして、テストプロジェクトやモジュールはアプリケーションのコードに対して参照を持ち、テストを実行できるようになります。

JavaScriptやTypeScriptを使ったフロントエンド開発の場合、JestやMocha、Karmaなどのテストフレームワークがよく使われます。これらのフレームワークを使用すると、アプリケーションのコードとテストコードを分離し、自動テストを実行することができます。

PythonやRuby、Java、PHPなどのバックエンド開発でも、同様に言語固有のテストフレームワークが存在します。これらの言語には、それぞれ独自のテストフレームワークがありますが、基本的なアプローチは同じです。

アプリケーションのコードとテストコードを別々のプロジェクトやモジュールに分ける。
テストプロジェクトやモジュールが、アプリケーションのコードへの参照を持つようにする。
テストフレームワークを使用して、自動テストを実行する。
このアプローチは、さまざまな言語やフレームワークで一般的に適用されるもので、開発者がアプリケーションの品質を確保するために使用されます。

継続的インテグレーション/デリバリー(CI/CD)とはなんですか?詳しく教えてください

継続的インテグレーション(Continuous Integration: CI)と継続的デリバリー(Continuous Delivery: CD)は、ソフトウェア開発プロセスを効率化し、品質を向上させるための手法です。これらは、特にアジャイル開発やDevOpsの文脈でよく使われる概念です。

継続的インテグレーション(CI):
継続的インテグレーションは、開発者が頻繁にコードをリポジトリにマージするプロセスです。一般的には、開発者は1日に複数回、コードをマージします。この頻繁なマージにより、コードの変更が他の開発者の作業に影響を与えることを最小限に抑えることができます。また、コードがマージされるたびに、自動テストやビルドプロセスが実行されるため、バグや問題が早期に検出され、修正が容易になります。

継続的デリバリー(CD):
継続的デリバリーは、ソフトウェアのリリースプロセスを効率化し、自動化する手法です。継続的デリバリーでは、コードがリポジトリにマージされると、自動テストとビルドが実行され、さらにデプロイメントも自動化されます。これにより、アプリケーションが常に本番環境にデプロイ可能な状態に保たれ、リリースプロセスがスムーズになります。

継続的デプロイメント(Continuous Deployment: CD)という関連する概念もあります。これは、継続的デリバリーの一歩先行ったもので、すべての変更が自動的に本番環境にデプロイされるプロセスです。

CI/CDの目的は、開発プロセスを迅速化し、効率化することで、品質の高いソフトウェアを短期間でリリースできるようにすることです。継続的インテグレーションと継続的デリバリーを適切に実装することで、開発チームは迅速なフィードバックループを確保し、プロジェクトのリスクを低減することができます。

ということは、テストの自動化は、複数人でコードの変更を頻繁に行うような場合に導入するとメリットが大きいということでしょうか?

はい、その通りです。テストの自動化は、特に複数人でコードの変更を頻繁に行うチームやプロジェクトにおいて大きなメリットをもたらします。その理由は以下の通りです。

  1. 早期のバグ検出: 自動テストを継続的に実行することで、コードの変更が他の部分に悪影響を及ぼしていないか確認し、バグや問題を早期に検出できます。

  2. 品質の維持: テストの自動化により、コードの品質が一定の基準を満たしていることを保証できます。これにより、プロジェクト全体の品質が向上します。

  3. リスクの低減: 自動テストにより、コード変更に伴うリスクが低減されます。これは、新たな変更が既存の機能に影響を与える可能性を最小限に抑えるためです。

  4. 高速なフィードバックループ: テストの自動化は、開発者が素早くフィードバックを受け取り、必要に応じて修正を行うことができます。これにより、開発プロセスが効率化され、プロジェクトの進行がスムーズになります。

  5. リリースの迅速化: テストの自動化は、デプロイメントプロセスの一部として組み込むことができます。これにより、リリースが迅速化され、顧客へのフィードバックが早くなります。

  6. 人的ミスの削減: 手動でテストを行う場合、人的ミスが発生しやすくなります。自動テストは、一度設定すれば繰り返し正確なテストが実行されるため、人的ミスのリスクが低減されます。

これらの理由から、テストの自動化は、複数人でコードの変更を頻繁に行うチームやプロジェクトにおいて特に有益です。

つまり、システムが正しく動くことを担保するためのテストコードを書いて、コードの改修時、リポジトリにマージするたびにテストを走らせて、マージするたびにバグチェックをおこなって、常にシステムが動く状態を維持できるようにする、というイメージでしょうか?

はい、そのイメージで正確です。テストコードを書いてシステムが正しく動作することを担保し、コードの改修時やリポジトリへのマージのたびにテストを実行します。こうすることで、バグや問題を早期に検出し、修正できるようになります。また、開発者は素早くフィードバックを受け取り、適切な修正を行うことができます。

継続的インテグレーション(CI)ツールを使用すると、自動的にテストが実行され、結果が開発者に通知されます。これにより、チームは常にシステムが動作する状態を維持し、リリースが迅速化されるため、顧客からのフィードバックを早く受け取ることができます。

要約すると、テストの自動化は、システムが正常に動作することを保証し、品質を維持するための重要なプロセスです。これにより、開発チームは効率的に作業を進め、リリースを迅速化できます。

CI/CDなどのテストの自動化を行う場合、例えばマージしたときに、すべてのテストが行われ、自分のコードの変更がどこかに影響してバグになっていないか、速度が問題ないか、などを確かめられるということなのでしょうか?他に確かめられることはありますか?

はい、その通りです。CI/CDを使用すると、コードがマージされたときに自動的にテストが実行され、コードの変更がバグを引き起こしたり、パフォーマンスに悪影響を与えていないかを確認できます。CI/CDでは、以下のような様々な検証を行うことができます。

  1. ユニットテスト: 個々のコードユニット(関数やメソッドなど)が正しく機能することを確認します。
  2. 統合テスト: 複数のコンポーネントが連携して正しく機能することを確認します。
    システムテスト: アプリケーション全体が正しく動作し、要求仕様を満たしていることを確認します。
  3. パフォーマンステスト: アプリケーションの速度や応答時間、リソース使用率などのパフォーマンスを評価します。
  4. セキュリティテスト: セキュリティの脆弱性やリスクを検出し、対策を講じます。
    コード品質チェック: コードの可読性や保守性を向上させるため、コーディング規約の遵守や静的解析ツールを利用します。
  5. リグレッションテスト: コードの変更が既存の機能に悪影響を与えていないことを確認します。

これらのテストや検証をCI/CDパイプラインに組み込むことで、開発プロセスを効率化し、品質を維持・向上させることができます。ただし、これらのテストを効果的に実施するには、適切なテスト戦略とテストケースの設計が不可欠です。

CI/CDの一般的なフローは以下の通りです。

  1. 開発者がコード変更をリモートリポジトリ(例えば、GitHubやGitLab)にプッシュします。
    C2. I/CDパイプラインがトリガーされます。これには、ビルド、テスト(ユニットテスト、統合テスト、エンドツーエンドテストなど)、静的コード解析、セキュリティチェックなどのステップが含まれることがあります。
  2. テストがすべて成功した場合、CI/CDパイプラインは自動的にアプリケーションをステージング環境や本番環境にデプロイします。
  3. 何らかの問題が検出された場合、開発者に通知が送信され、問題を修正するためのフィードバックが提供されます。

ということは、CI/CDですべての自動テストをパスした場合、アプリケーションの品質が担保されているということですよね?
ただし、テストコードに漏れがあったりする場合は、品質は担保されませんよね?

おっしゃる通りです。CI/CDにより自動テストがすべてパスした場合でも、品質が完全に担保されているわけではありません。CI/CDは、設定されたテストケースに基づいてアプリケーションの品質を評価しますが、テストカバレッジが低い場合や、テストケースに漏れがある場合は、未検出のバグが存在する可能性があります。

品質をより高いレベルで担保するためには、以下のような対策が役立ちます。

  1. テストカバレッジを向上させる: より多くのコードパスや機能をテストすることで、未検出のバグのリスクを減らします。
  2. 網羅的なテストケースを設計する: 異常系や境界値条件など、さまざまなシナリオを考慮してテストケースを作成します。
  3. コードレビューを行う: 他の開発者によるコードレビューを通じて、テスト漏れやバグのリスクを減らします。
  4. リグレッションテストを実施する: 新しい機能や変更が既存の機能に悪影響を与えていないことを確認するため、定期的にリグレッションテストを実施します。

CI/CDは、開発プロセスを効率化し、品質を向上させる重要なツールですが、品質の担保には十分なテストとコードレビューが欠かせません。開発チームは、これらのプラクティスを継続的に改善して、アプリケーションの品質を向上させる必要があります。

テストカバレッジってなんですか?

テストカバレッジとは、ソフトウェアのテストがどれだけのコードや機能を網羅しているかを示す指標です。これは、テストの網羅性や品質を評価するために使用されます。テストカバレッジの高い値は、テストが広範囲のコードや機能を検証していることを意味し、コードの信頼性や品質が高いと言えます。

テストカバレッジには、いくつかの種類があります。以下に、主なものを示します。

  1. ステートメントカバレッジ(行カバレッジ): ソースコードの各行がテストで実行されたかどうかを測定します。これにより、テストされていないコード行を特定できます。
  2. ブランチカバレッジ: ソースコード内の条件分岐(if、else、switchなど)がすべてテストで評価されているかを測定します。これにより、テストが条件分岐を十分に網羅しているかを確認できます。
  3. パスカバレッジ: ソースコード内のすべての実行パスがテストで評価されているかを測定します。これは、複雑な条件やループを含むコードの網羅性を評価するのに役立ちます。
  4. 関数カバレッジ: ソースコード内のすべての関数やメソッドがテストで呼び出されているかを測定します。

テストカバレッジを計測することで、テストがコードのどの部分を網羅しているかを把握し、テストの品質や効果を向上させることができます。ただし、高いテストカバレッジが必ずしも品質の高いソフトウェアを保証するわけではないため、カバレッジだけに頼らず、他の品質指標や手法も併用することが重要です。

自動テストをやってみる

動作確認をしたいメソッドを作る

ある関数を作ってほしいです。C#でお願いします。

第一引数が出荷日で日付型、第二引数が締日で数値型です。
戻り値は締切日で日付型です。
第一引数にはランダムな年月日もしくはnullが入力されます。第二引数には10,15,20,25,31の中のどれかの数値もしくはnullです。
第一引数と第二引数から、締切日というものを生成して戻り値として返したいです。

計算のルールのサンプルは下記です。
出荷日が2023/12/5で締日が25の場合、締切日は2023/12/25になります。
出荷日が2023/12/5で締日が31の場合、締切日は2023/12/31(月末の日付)になります。
出荷日が2023/12/5で締日が15の場合、締切日は2023/12/15になります。
出荷日が2023/2/5で締日が25の場合、締切日は2023/2/25になります。
出荷日が2023/2/5で締日が15の場合、締切日は2023/2/15になります。
出荷日が2023/2/5で締日が31の場合、締切日は2023/2/28(月末の日付)になります。
出荷日がnullで締日が15の場合、締切日はnullになります。

このような関数を作ってほしいです。

以下のような関数を作成しました。引数に DateTime? 型と int? 型を使用して、null値を許容しています。また、DateTime.DaysInMonth メソッドを使って、月末の日付を取得しています。


    public static DateTime? CalculateDeadline(DateTime? shipmentDate, int? closingDate)
    {
        if (shipmentDate == null || closingDate == null)
        {
            return null;
        }

        int year = shipmentDate.Value.Year;
        int month = shipmentDate.Value.Month;
        int daysInMonth = DateTime.DaysInMonth(year, month);

        int deadlineDay = Math.Min(closingDate.Value, daysInMonth);

        return new DateTime(year, month, deadlineDay);
    }

テストプロジェクトとテストコードを作る

この関数を、MSTestを使って自動テストで動作確認をしてみたいです。どのような手順で自動テストをすればよいか、ステップバイステップで手順を教えてください。
.NetFramework4.7.2を利用して、WPFでのプロジェクトにこの関数を入れました

以下に、.NET Framework 4.7.2 の WPF プロジェクトで MSTest を使用して自動テストを実行する手順を示します。

  1. テストプロジェクトの作成:
  • Visual Studio でソリューションを開きます。
  • ソリューションエクスプローラで、ソリューションを右クリックし、「追加」>「新しいプロジェクト」を選択します。
  • 「C#」>「テスト」カテゴリに移動し、「単体テストプロジェクト(.NET Framework)」を選択します。プロジェクト名を入力し、「OK」をクリックします。
  1. テスト対象のプロジェクトへの参照の追加:
  • テストプロジェクトを右クリックし、「参照の追加」をクリックします。
  • 「プロジェクト」タブを選択し、テスト対象のプロジェクトにチェックを入れ、「OK」をクリックします。
  1. テストクラスの作成:
  • テストプロジェクトで新しい C# クラスファイルを作成します。名前は例えば DeadlineCalculatorTests.cs などにします。
  • テスト対象のクラスが入っている名前空間を using で参照します。
  • 以下のようにテストクラスとテストメソッドを作成します。
  1. テストコードの記述:
    TestCalculateDeadline() メソッド内にテストコードを記述します。例えば以下のようになります。
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using Yzw.Kega.Models;

namespace UnitTestProject1
{
  [TestClass]
  public class UnitTest1
  {
    [TestMethod]
    public void TestCalculateDeadlineFirst()
    {
      // ここにテストコードを記述
      DateTime? shipmentDate = new DateTime(2023, 12, 5);
      int? closingDate = 25;
      DateTime? expectedDeadline = new DateTime(2023, 12, 25);

      DateTime? actualDeadline = CommonModel.CalculateDeadline(shipmentDate, closingDate);
      Assert.AreEqual(expectedDeadline, actualDeadline);
    }

テストの実行

先ほどの自動テストの手順で、テストコードの記述まで行いました。
続きを教えていただけますか。

  1. テストエクスプローラーウィンドウを開く: Visual Studio のメニューバーで、「テスト」>「テストエクスプローラー」をクリックします。テストエクスプローラーが表示されます。

  2. テストの検出: テストエクスプローラーでは、プロジェクト内のテストが自動的に検出されます。検出が完了すると、テストエクスプローラーにテストが一覧表示されます。検出が遅い場合は、「再実行」ボタンをクリックして、テストの検出を手動で開始できます。

  3. テストの実行: テストエクスプローラーで、すべてのテストを実行するには、「すべて実行」ボタンをクリックします。また、個別のテストや複数の選択したテストだけを実行するには、テスト名の横にある再生ボタンをクリックします。

  4. テスト結果の確認: テストが実行されると、テストエクスプローラーに結果が表示されます。各テストの結果は、緑のチェックマーク(成功)または赤の×マーク(失敗)で示されます。失敗したテストの詳細情報を確認するには、そのテストをクリックしてください。エラーメッセージやスタックトレースが表示されます。

テストがすべて成功した場合は、コードに対して作成されたテストケースが正常に動作していることが確認できます。失敗したテストがある場合は、失敗の原因を特定してコードやテストケースを修正してください。そして、再度テストを実行して、問題が解決されたことを確認します。

テストの実行と結果の確認を繰り返し、すべてのテストが成功するまでコードを修正していくことで、コードが正しく動作することを保証できます。

テストコードの改修

先ほどのテストコードの場合、出荷日と締日が1つのパターンしかテストできていないと思います。
実際にはどの出荷日が入っても、どの締日が入っても、値にnullが入っていても、正常に動作する必要があると思います。
それらのテストケースに対応するにはどうしたらよいですか?

テストケースの数だけテストメソッドを追加し、それぞれに適切な入力値と期待される結果を設定します。これにより、関数がさまざまなシナリオで正しく動作することを確認できます。

ただし、テストケースが多くなると、テストメソッドの数が増え、コードが冗長になることがあります。その場合は、DataRow属性とDataTestMethod属性を使用して、複数のテストデータを1つのテストメソッドでテストできます。

以下は、DataRowを使用したテストメソッドの例です。

[DataTestMethod]
[DataRow(2023, 12, 5, 25, 2023, 12, 25)]
[DataRow(2023, 12, 5, 15, 2023, 12, 15)]
[DataRow(null, null, 15, null, null, null)]
[DataRow(2023, 2, 5, 31, 2023, 2, 28)]
public void TestCalculateDeadline(int? shipmentYear, int? shipmentMonth, int? shipmentDay, int? closingDate, int? expectedYear, int? expectedMonth, int? expectedDay)
{
    DateTime? shipmentDate = shipmentYear.HasValue && shipmentMonth.HasValue && shipmentDay.HasValue
        ? new DateTime(shipmentYear.Value, shipmentMonth.Value, shipmentDay.Value)
        : (DateTime?)null;
    DateTime? expectedDeadline = expectedYear.HasValue && expectedMonth.HasValue && expectedDay.HasValue
        ? new DateTime(expectedYear.Value, expectedMonth.Value, expectedDay.Value)
        : (DateTime?)null;

    DateTime? actualDeadline = CommonModel.CalculateDeadline(shipmentDate, closingDate);
    Assert.AreEqual(expectedDeadline, actualDeadline);
}

さらにDataRowを増やして、20個ほどのテストケースを出してほしいですが可能ですか?特に月末の日付が変わるものであったり、締日近辺の出荷日を多めに入れてほしいです。

さまざまなテストケースを含む20個のDataRowを追加しました。

[DataTestMethod]
[DataRow(2023, 12, 5, 25, 2023, 12, 25)]
[DataRow(2023, 12, 5, 15, 2023, 12, 15)]
[DataRow(null, null, 15, null, null, null)]
[DataRow(2023, 2, 5, 31, 2023, 2, 28)]
[DataRow(2023, 4, 30, 10, 2023, 4, 10)]
[DataRow(2023, 4, 30, 15, 2023, 4, 15)]
[DataRow(2023, 4, 30, 20, 2023, 4, 20)]
[DataRow(2023, 4, 30, 25, 2023, 4, 25)]
[DataRow(2023, 4, 30, 31, 2023, 4, 30)]
[DataRow(2023, 2, 28, 10, 2023, 2, 10)]
[DataRow(2023, 2, 28, 15, 2023, 2, 15)]
[DataRow(2023, 2, 28, 20, 2023, 2, 20)]
[DataRow(2023, 2, 28, 25, 2023, 2, 25)]
[DataRow(2023, 2, 28, 31, 2023, 2, 28)]
[DataRow(2023, 1, 31, 10, 2023, 1, 10)]
[DataRow(2023, 1, 31, 15, 2023, 1, 15)]
[DataRow(2023, 1, 31, 20, 2023, 1, 20)]
[DataRow(2023, 1, 31, 25, 2023, 1, 25)]
[DataRow(2023, 1, 31, 31, 2023, 1, 31)]
[DataRow(2023, 6, 30, 31, 2023, 6, 30)]
public void TestCalculateDeadline(int? shipmentYear, int? shipmentMonth, int? shipmentDay, int? closingDate, int? expectedYear, int? expectedMonth, int? expectedDay)
{
    DateTime? shipmentDate = shipmentYear.HasValue && shipmentMonth.HasValue && shipmentDay.HasValue
        ? new DateTime(shipmentYear.Value, shipmentMonth.Value, shipmentDay.Value)
        : (DateTime?)null;
    DateTime? expectedDeadline = expectedYear.HasValue && expectedMonth.HasValue && expectedDay.HasValue
        ? new DateTime(expectedYear.Value, expectedMonth.Value, expectedDay.Value)
        : (DateTime?)null;

    DateTime? actualDeadline = CommonModel.CalculateDeadline(shipmentDate, closingDate);
    Assert.AreEqual(expectedDeadline, actualDeadline);
}

すみません、テストケースが間違っているのでDataRow部分のみでいいので修正をお願いできますか?
例えば、
[DataRow(2023, 4, 30, 10, 2023, 4, 10)]は、正確には[DataRow(2023, 4, 30, 10, 2023, 5, 10)]
[DataRow(2023, 4, 30, 15, 2023, 4, 15)]は、正確には[DataRow(2023, 4, 30, 15, 2023, 5, 15)]
つまり出荷日と締日を見て、締日の方が早い場合は、次の月の締日を納品日にしたいのです。

すみません、理解しました。出荷日と締日を見て、締日が早い場合は次の月の締日を納品日にするように修正します。以下の修正を加えたDataRowです。

[DataTestMethod]
[DataRow(2023, 12, 5, 25, 2023, 12, 25)]
[DataRow(2023, 12, 5, 15, 2023, 12, 15)]
[DataRow(null, null, 15, null, null, null)]
[DataRow(2023, 2, 5, 31, 2023, 2, 28)]
[DataRow(2023, 4, 30, 10, 2023, 5, 10)]
[DataRow(2023, 4, 30, 15, 2023, 5, 15)]
[DataRow(2023, 4, 30, 20, 2023, 5, 20)]
[DataRow(2023, 4, 30, 25, 2023, 5, 25)]
[DataRow(2023, 4, 30, 31, 2023, 4, 30)]
[DataRow(2023, 2, 28, 10, 2023, 3, 10)]
[DataRow(2023, 2, 28, 15, 2023, 3, 15)]
[DataRow(2023, 2, 28, 20, 2023, 3, 20)]
[DataRow(2023, 2, 28, 25, 2023, 3, 25)]
[DataRow(2023, 2, 28, 31, 2023, 2, 28)]
[DataRow(2023, 1, 31, 10, 2023, 2, 10)]
[DataRow(2023, 1, 31, 15, 2023, 2, 15)]
[DataRow(2023, 1, 31, 20, 2023, 2, 20)]
[DataRow(2023, 1, 31, 25, 2023, 2, 25)]
[DataRow(2023, 1, 31, 31, 2023, 1, 31)]
[DataRow(2023, 6, 30, 31, 2023, 6, 30)]
public void TestCalculateDeadline(int? shipmentYear, int? shipmentMonth, int? shipmentDay, int? closingDate, int? expectedYear, int? expectedMonth, int? expectedDay)
{
    // ...
}

メインメソッド改修

テストケースでエラーが出ました。
このテストコードが正しい場合、関数の方が間違っていると考えられますよね?
その場合、下記のメソッドは何か間違っていますか?

すみません、確かに関数の実装が間違っています。締日が出荷日より前の場合に、次の月の締日を返すように修正が必要です。以下のように修正してみてください。

public static DateTime? CalculateDeadline(DateTime? shipmentDate, int? closingDate)
{
    if (shipmentDate == null || closingDate == null)
    {
        return null;
    }

    int year = shipmentDate.Value.Year;
    int month = shipmentDate.Value.Month;
    int daysInMonth = DateTime.DaysInMonth(year, month);

    int deadlineDay = Math.Min(closingDate.Value, daysInMonth);
    DateTime deadline = new DateTime(year, month, deadlineDay);

    if (deadline < shipmentDate.Value)
    {
        deadline = deadline.AddMonths(1);
    }

    return deadline;
}

これで、締日が出荷日より前の場合に次の月の締日を返すようになります。テストケースでエラーが出なくなるはずです。

完成

[DataRow(null, null, 15, null, null, null)]
この時だけエラーが出ました。何が問題でしょうか?

申し訳ありません。問題を見落としていました。DataRow属性の引数はnullを許容しないため、nullを直接使用することはできません。その代わりに、特殊な値(例えば、-1)を使ってnullを表すことができます。以下のようにテストケースを変更してください。

[DataTestMethod]
// 他のテストケースは省略
[DataRow(-1, -1, -1, 15, -1, -1, -1)]

この変更により、-1をnullの代わりに使用してテストケースを表現できます。これで、すべてのテストケースが正常に実行されるはずです。

テストコードとは

要はテストコードとは、様々な実データのパターンをコード上に用意して、そのデータを利用して、メソッドが正しく動くかを自動で確認するためにあるということでしょうか?
それとも実データは外部からstreamreaderで読み込んだりする場合もありますか?

はい、その通りです。テストコードは、様々なデータのパターンを使用してメソッドが正しく動作することを自動的に確認するために存在します。テストケースは、コード内で直接定義されることが多いですが、外部からデータを読み込むこともあります。

例えば、複雑なデータセットや多数のテストケースが必要な場合、テストデータをCSVファイルやデータベースから読み込むことがあります。これにより、テストコードをすっきりさせることができ、データの管理が容易になります。

ただし、外部からデータを読み込む場合でも、テストコードの目的は同じままです。つまり、メソッドが正しく動作することを自動的に確認することが目的です。どのようにテストデータを取得するかは、プロジェクトの要件や好みによって異なります。

テストケースとは

そもそもテストケースってなんですか?
[DataRow(2023, 12, 5, 25, 2023, 12, 25)]みたいな、個別のデータパターンって意味ですか?

はい、おっしゃる通りです。テストケースとは、ソフトウェアや機能が正しく動作していることを確認するために、開発者が作成する個別のデータパターンやシナリオのことを指します。テストケースは、機能が期待通りに動作するかどうかを検証する際に使用される入力値、期待される出力値、およびその他の条件を含むことが一般的です。

先ほどの例である [DataRow(2023, 12, 5, 25, 2023, 12, 25)] は、次のようなテストケースを表しています。

入力値: 出荷日が 2023 年 12 月 5 日、締日が 25 日
期待される出力値: 納品日が 2023 年 12 月 25 日
このテストケースを使用して、関数が正しく動作していることを確認できます。機能やアプリケーションの異なる側面を網羅的にテストするために、通常は複数のテストケースが作成されます。これにより、さまざまな入力値や条件での動作を確認し、ソフトウェアの品質を向上させることができます。

単体テストと統合テスト

先ほどの例では、一つのテストメソッドで、メインプロジェクトの一つのメソッドの動作が確認できたと思います。
これ以外にも、一つのテストメソッドに、メインプロジェクトの複数メソッドが書かれていて、それらメソッドがすべて正しいかを判断するのにも使ってよいのですか?それとも一つのテストメソッドに対して、メインプロジェクトのメソッドは一つにしておくべきですか?

一般的には、1つのテストメソッドは1つのメインプロジェクトのメソッドに対応するべきです。これは、テストの単位が小さく、独立していることで、問題が発生した際に原因を特定しやすくするためです。テストメソッドが複数のメソッドをテストする場合、どのメソッドで問題が発生しているのか特定しにくくなります。

しかし、実際のプロジェクトでは、複数のメソッドが連携して動作するシナリオをテストすることもあります。このような場合は、複数のメソッドを含むテストを作成することが適切です。ただし、そのようなテストケースは、各メソッド単体のテストケースに加えて作成すべきであり、単体テストと統合テストを明確に区別することが重要です。

要約すると、以下のようになります。

基本的には、1つのテストメソッドは1つのメインプロジェクトのメソッドに対応すべきです(単体テスト)。
複数のメソッドが連携して動作するシナリオをテストする場合は、複数のメソッドを含むテストを作成することが適切です(統合テスト)。
単体テストと統合テストを明確に区別し、適切なテストケースを作成してください。

自動テストで、他にもこのような場合に使った方がよいなどがあれば教えてほしいです。

自動テストを導入することによって得られる利点は多岐にわたります。以下に、自動テストを導入することが特に有益なケースをいくつか挙げます。

  1. 頻繁なリリースが行われるプロジェクト: 自動テストを導入することで、リリースのたびに手動でテストを行う手間が省け、リリースの効率を向上させることができます。

  2. 多くの開発者が関与するプロジェクト: 複数の開発者が同時にコードを変更すると、予期しない問題が発生する可能性があります。自動テストを導入することで、他の開発者による変更が自分のコードに影響を与えていないことを確認できます。

  3. 長期間にわたるプロジェクト: プロジェクトが長期間にわたる場合、開発者の入れ替わりや仕様変更などが発生しやすいです。自動テストを導入することで、新しい開発者がコードに慣れるのを助け、仕様変更に伴う問題を早期に発見できます。

  4. 複雑なロジックやアルゴリズムが含まれるプロジェクト: 複雑なロジックやアルゴリズムを持つプロジェクトでは、手動テストだけでは網羅的な検証が難しい場合があります。自動テストを導入することで、コードの正確性を保証することができます。

  5. 品質が重要なプロジェクト: 金融、医療、航空など、品質が特に重要な分野では、自動テストを導入することで、品質の高いソフトウェアを提供することができます。

これらの状況に当てはまるプロジェクトでは、自動テストを導入することで、効率の向上、品質の向上、リスクの軽減など、さまざまなメリットを享受できます。

小規模な受託開発よりも大規模のWeb開発などで有効という認識で正しいですか?

はい、その認識は概ね正しいです。大規模なWeb開発では、多くの機能やコンポーネントが相互に影響を与えるため、テストカバレッジや自動化されたテストが品質を担保する上で非常に有効です。

一方で、小規模な受託開発でも、テストカバレッジや自動化されたテストは役に立ちます。しかし、プロジェクトの規模が小さい場合は、手動でのテストが実現可能であることが多く、自動化の恩恵はやや限定的になることがあります。ただし、自動化されたテストは、プロジェクトが拡大し続ける場合や、将来のメンテナンスを考慮した場合にも役立ちます。

おまけ:UIテストもあるよ

画面のクリックなども、テストの自動化を行いますか?

はい、画面のクリックやユーザーインターフェイス(UI)に関連する操作も、テストの自動化を行うことができます。これらの自動化テストは、主にUIテストやエンドツーエンド(E2E)テストと呼ばれ、アプリケーションのユーザーインターフェイスを操作して、正しい振る舞いや表示が行われていることを確認することを目的としています。

UIテストやE2Eテストでは、通常、テストフレームワークやツールを使用して、ユーザーが実際にアプリケーションを操作するかのように、画面上の要素をクリックしたり、入力フィールドにテキストを入力したり、ドロップダウンメニューを選択したりする操作を自動化します。

いくつかの一般的なUIテストフレームワークやツールには、以下のようなものがあります。

  • Selenium: ウェブアプリケーションのUIテストの自動化に広く使用されているツールで、複数のプログラミング言語とブラウザをサポートしています。
  • Appium: モバイルアプリケーションのUIテストの自動化に使用されるツールで、iOSとAndroidの両方のプラットフォームをサポートしています。
  • Cypress: JavaScriptで書かれたウェブアプリケーションのエンドツーエンドテストを行うためのフレームワークです。

UIテストは、アプリケーションの機能が正しく動作しているだけでなく、ユーザーが操作するインターフェイスも期待通りに動作していることを確認するために重要な要素です。ただし、UIテストは他の種類のテストに比べて実装やメンテナンスが複雑になることがあるため、適切なバランスを見つけることが重要です。

WindowsアプリケーションのUIテスト

WPFで作ったwindowsアプリケーションでUIテストを行う場合、どのようなフレームワークを使いますか

WPFで作成されたWindowsアプリケーションのUIテストを行う場合、以下のようなフレームワークやツールを使用することができます。

  1. Appium: 先ほども述べたように、AppiumはモバイルアプリケーションのUIテストに広く使われていますが、Windowsアプリケーションのテストにも利用できます。Appiumは、Windows Application Driver (WinAppDriver) を利用して、WPFアプリケーションのUIテストを自動化することができます。

  2. Coded UI Test (CUIT): CUITは、Visual StudioでサポートされているUIテストフレームワークで、WPFやWindows FormsアプリケーションのUIテストを行うことができます。ただし、Visual Studio 2019以降、CUITのサポートは終了し、Microsoftはこれに代わるUIテストソリューションとしてWinAppDriverを推奨しています。

  3. White Framework: Whiteは、.NETアプリケーション(WPF、Windows Forms、Silverlight、Win32)のUIテストを行うためのオープンソースフレームワークです。Whiteは、UIAutomationライブラリをベースにしており、様々なコントロールを操作するためのAPIを提供しています。

  4. FlaUInspect / FlaU: FlaUInspectは、MicrosoftのUI Automationを利用してWPFアプリケーションのUI要素を調べるためのツールです。FlaUは、UIテストの自動化を行うための.NETライブラリで、UIAutomationをベースにしています。FlaUを使用すると、WPFアプリケーションのUI要素にアクセスして、操作を自動化することができます。

これらのフレームワークやツールを使用して、WPFアプリケーションのUIテストを自動化することができます。プロジェクトの要件や、開発チームのスキルセットに合わせて適切なフレームワークを選択してください。

まとめ

自社開発企業でみんなでどんどん機能を拡張する場合、どんどんマージしていく場合は、絶対あった方がよいと思われる。
現在の受託開発では、テストケースが多かったり複雑だったりする場合、また改修が増え何度もテストを行うような処理がある場合に、自動テストを導入するとメリットがありそう。

とりあえず、テストケースがたくさんあって、二度と同じテストやりたくないときは自動テスト導入するとよさそう。質も確実に担保されるのは大きい。

Discussion