お久しぶりです・・・
だいぶ空いてしまいました。
ブログって一回書かなくなるとダメですね。
気を取り直してテストシナリオの共通部分の抽出行きましょう。
あれ?シナリオ書くって言ってなかった?
はい。
Friendly ハンズオン 13 アプリケーションドライバ -その5- - ささいなことですが。に最後に書いた時はそのつもりだったんですけど、久しぶりに見るとやっぱり共通化しといた方がいいかなーと。あと、見直すとサンプルコードにバグがあったんで修正しときました。(すみません。不具合は見つかったら直しますので、見るたびちょっと変わってるかもです・・・)
Ishikawa-Tatsuya/HandsOn14 · GitHubからダウンロードしてください。このコードを変更していきます。
アプリのライフサイクル管理コード
AdjustDriverのこの部分です。テストシナリオは大体これを書きます。(ライフサイクル管理が異なれば、内容は変わってきます)ちょっと面倒なコードなので共通化しておきます。
static AppDriver _app; static Dictionary<string, bool> _tests; public TestContext TestContext { get; set; } [ClassInitialize] publicstaticvoid ClassInitialize(TestContext c) { _app = new AppDriver(); _tests = typeof(AdjustDriver).GetMethods().Where(e => 0< e.GetCustomAttributes(typeof(TestMethodAttribute), true).Length).ToDictionary(e => e.Name, e => true); } [ClassCleanup] publicstaticvoid ClassCleanup() { _app.EndProcess(); } [TestInitialize] publicvoid TestInitialize() { _app.Attach(); } [TestCleanup] publicvoid TestCleanup() { if (TestContext.DataRow == null || ReferenceEquals(TestContext.DataRow, TestContext.DataRow.Table.Rows[TestContext.DataRow.Table.Rows.Count - 1])) { _tests.Remove(TestContext.TestName); } _app.Release(TestContext.CurrentTestOutcome == UnitTestOutcome.Passed && 0< _tests.Count); }
そもそも何をやっているか?
ポイントは_app.Release(bool isContinue)呼び出しの引数です。テストが終了したときに対象アプリを生き残すか否かです。以下の条件で終了させます。
- テスト失敗
- クラスに定義したテストメソッドを全て実行した
2.の方を実現するためにClassInitializeに面倒なコード入れてます。(実はこれでも完全ではなく、違うクラスの複数のメソッドを選択されて実行された場合は、アプリ終了のタイミングが全てのテストが終わった後になってしまします。でも、今回はそのケースはよしとします。)
TestBase作成
共通部分は基本クラスに押し込むことにします。TestBaseクラスを作成します。テストごとにstaticなインスタンスを持ちたいので、ジェネリックパラメータでテストクラスを指定させるようにします。(タイプが異なるとstaticインスタンスは別に確保される)
AppDriverは継承先でも使うのでprotectedなプロパティーにしておきます。
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using EmployeeManagementDriver; using System.Collections.Generic; using EmployeeManagement; using System.Linq; namespace TestScenario { publicclass TestBase<T> { staticprotected AppDriver App { get; set; } static Dictionary<string, bool> _tests; public TestContext TestContext { get; set; } publicstaticvoid NotifyClassInitialize() { App = new AppDriver(); _tests = typeof(T).GetMethods().Where(e => 0< e.GetCustomAttributes(typeof(TestMethodAttribute), true).Length).ToDictionary(e => e.Name, e => true); } publicstaticvoid NotifyClassCleanup() { App.EndProcess(); } publicvoid NotifyTestInitialize() { App.Attach(); } publicvoid NotifyTestCleanup() { if (TestContext.DataRow == null || ReferenceEquals(TestContext.DataRow, TestContext.DataRow.Table.Rows[TestContext.DataRow.Table.Rows.Count - 1])) { _tests.Remove(TestContext.TestName); } App.Release(TestContext.CurrentTestOutcome == UnitTestOutcome.Passed && 0< _tests.Count); } } }
これをAdjustDriverに継承させます。ライフ管理の部分は随分スッキリしました。
[TestClass] publicclass AdjustDriver : TestBase<AdjustDriver> { [ClassInitialize] publicstaticvoid ClassInitialize(TestContext c) { NotifyClassInitialize(); } [ClassCleanup] publicstaticvoid ClassCleanup() { NotifyClassCleanup(); } [TestInitialize] publicvoid TestInitialize() { NotifyTestInitialize(); } [TestCleanup] publicvoid TestCleanup() { NotifyTestCleanup(); }
パラメタライズドテスト考慮
そして、もう一つこのTestBaseに機能を入れます。それはパラメタライズドテスト用です。VSTestのパラメタライズは癖があって、DBかExcelからパラメータを読み込ませることになっています。TestContext.DataRowに現在の行が入ります。このままだと、使いづらいので便利機能を付けておきます。指定の型に変換するメソッドです。
public Data GetParam<Data>() where Data : new() { Data data = new Data(); foreach(var e intypeof(Data).GetProperties()) { e.GetSetMethod().Invoke(data, newobject[] { Convert(e.PropertyType, TestContext.DataRow[e.Name]) }); } return data; } staticobject Convert(Type type, object obj) { //一旦int,bool,stringで//必要に応じて変換方法を追加stringvalue = obj == null ? string.Empty : obj.ToString(); if (type == typeof(int)) { returnint.Parse(value); } elseif (type == typeof(bool)) { returnstring.Compare(value, true.ToString(), true) == 0; } elseif (type == typeof(string)) { returnvalue; } thrownew NotSupportedException(); }
使う側ではこんな感じで使うことを想定しています。
こんなExcelがあって、(セルは全て文字列型にしています。)
読み込みコードです。
//Excelのカラム名称と合わせるclass Data { publicstring Input { get; set; } publicint Expectation { get; set; } } //ファイル名とシート名を合わせる [DataSource("System.Data.OleDB", @"Provider=Microsoft.ACE.OLEDB.12.0; Data Source=TestParams.xlsx; Extended Properties='Excel 12.0;HDR=yes';", "DataSheet$", DataAccessMethod.Sequential )] [TestMethod] publicvoid ParameterizedTest() { var data = GetParam<Data>(); }
システムテストだと多くの人と内容を共有する必要もあって、Excelでパラメータ管理する方が受けが良いですね。ソースにパラメータ埋め込んでいいとかだと、@neueccさんのChainingAssertionを使えばVSTestでも気軽にパラメタライズできます。好みに合わせて使ってみてください。
Chaining Assertion - Home
共通化まで終えたコードはこちらに置きました。次回はこれを使ってシナリオを実装します。
HandsOn14-2/Project/TestScenario at master · Ishikawa-Tatsuya/HandsOn14-2 · GitHub
サンプルコード修正
4/29 TestCleanup、NotifyTestCleanupのコードを修正しました。