SQL Server

C# を使用した SQL Server OLAP キューブの単体テスト

Mark Nadelson

コード サンプルのダウンロード

トーマス・ジェファーソンのアメリカ独立宣言のようになりますが、「我々は以下の諸事実を自明なものと見なす。すべてのコードには、バグを簡単に特定し、運用停止を最小限に抑えるため、簡潔かつ簡明な方法で徹底的に単体テストを受けることのできる、侵すべからざる権利が与えられている」と感じています。少し大げさですが、言いたいことを的確に表しています。

当然、大半の開発者はコードの単体テストの必要性を認めていますが、"コード" の定義があいまいだとしたらどうでしょう。これは最近起こった出来事です。やっかいな問題が発生し、その解決策を探すことになりました。開発者のグループが、SQL Server Analysis Services (SSAS) を使用して複雑なオンライン分析処理 (OLAP) キューブの作成に取り組んでいました。このキューブには多数のディメンションがあり、そのすべてが非常に複雑な 1 つのファクト テーブルに結び付けられています。キューブ開発経験豊富な開発者たちなので、キューブのピースをつなぎ合わせることはできましたが、多次元式 (MDX) クエリの結果を検証するのは大変な作業でした。さらに問題を難しくしているのは、ディメンションとファクト テーブルのデータ量や、キューブの構築に必要な時間とコンピューティング リソースなど、複数の要素が組み合わさっていることです。キューブが構築されると、(MDX クエリによって生成された) 結果がユーザーに送られます。データに問題が見つかった場合、原因を突き止めるには長い時間がかかります。また、根本的な問題を発見して修正したら、キューブを再生成する必要があります。さらに悪いことに、ディメンションの追加、基盤となるファクト テーブルの更新、別の集計を使用したキューブの構築などの変更によって生じる影響すべてを特定する方法がありません。無害に見える変更でも、キューブに対するクエリに大きく広範な影響が及ぶ可能性があります。

このキューブ問題に関する解決策は、限られたデータ量でキューブのディメンションとファクトをステージングできる環境とプロセスを作成し、運用バージョンのキューブ スキーマを使用してキューブにクエリを実行することです。既存の成果物によって副次的な悪影響が及ぶことのないよう、単体テスト実行のたびにゼロからテスト用のキューブを作成するのが理想的です。単体テスト フレームワークの他の要件 (対象アプリケーションに関係なく大半の単体テスト フレームワークに当てはまる要件) は、テストの検証を繰り返し行え、テストが失敗した場合にはその理由を迅速かつ簡単に特定できることです (テスト ケースに関して「データが一致しなかったため失敗した」と言うのは好ましくありません)。

今回は、OLAP キューブの MDX ベースの出力を検証する単体テスト スイートを作成するためのフレームワークを紹介します。ここで説明するアーキテクチャでは、単体テスト データベース内にある既存のスキーマを使用して、単体テスト実行のたびに再作成されるキューブを作成することができます。また、新しく形成した単体テスト バージョンのキューブに対して実行する MDX クエリを作成することも可能です。加えて、既存のテンプレートに照らし合わせて結果を検証し、単純な HTML 形式で表示します。テンプレートと照合するこのようなテスト ケース検証パターンは、データ結果が大きく複雑になる単体テスト フレームワークに拡張できます。

キューブの概要

キューブのテストにおける問題の解決策を詳しく説明する前に、キューブの概念と、キューブを構成するコンポーネントについて簡単に紹介します。キューブは、データ ウェアハウス内に保持されているデータにすばやくアクセスする手段です。キューブはデータを整理し、多次元構造にまとめます。キューブは OLAP テクノロジの主要コンポーネントで、データのクエリを予測可能な短い応答時間で行うことができる使いやすいメカニズムを備えています。キューブはディメンション データとメジャー (数値ファクト) から構成されます。キューブの中心となるテーブルをファクト テーブルと呼びます。ファクト テーブルは、キューブのメジャーのソースです。ディメンション テーブルは複数の階層レベルに分けられたクエリ可能な情報が含まれるテーブルで、ファクト テーブルから参照されます。ディメンション階層は、ユーザーが高度な質問を行えるようにするためのものです。そのため、ユーザーはディメンション階層を利用して、詳細情報を取得できます。

キューブはデータベースに含まれます。データベースのキューブ構造は以下のオブジェクトから構成されます。

  • データ ソース: キューブに読み込まれる情報のソース。
  • メジャー: キューブで表現される数値。日付をメジャーとすることも可能ですが、通常はさまざまなレベルの集計 (Sum、Max、Min、Count など) を伴う数値です。
  • ディメンション: メジャーに関連付けられている属性。ディメンションの例としては、ビジネス データ、顧客名、地理上の地域などが一般的です。
  • パーティション: ファクト データのうち、メジャー グループに読み込まれる部分を定義します。複数のパーティションを作成することで、キューブの並列処理、格納、およびクエリを個別に行えるため、パフォーマンスが向上します。個々のパーティションを再処理しても他のパーティションに影響が及ぶことはありません。
  • キューブ ロール: 各キューブには、エンド ユーザーのアクセスを許可するため、1 つ以上のキューブ ロールを設定します。ロールを設定することにより、個別のユーザー ID または Active Directory グループに基づいて、キューブに格納されたすべてのデータまたはデータのサブセットへのアクセスを許可できます。

キューブ スキーマの定義は、XML for Analysis (XMLA) の形式で SSAS から抽出できます。XMLA は、HTTP 経由でキューブへのアクセスを可能とする SOAP ベースの XML プロトコルです。XMLA の定義には上記の 5 つのキューブ オブジェクトの詳細がすべて含まれています。XMLA を使用するとさまざまなデータベースやサーバーですばやく簡単にキューブを再作成することができるため、XMLA は単体テスト可能なキューブの作成に不可欠です。

キューブを作成、処理した後は、キューブの作成に使用したさまざまなディメンションを組み合わせて、データ メジャーをクエリすることができます。キューブに対するクエリは、前述の MDX 構文を使用して行います。SQL クエリと同様、MDX クエリでも、データ要求 (SELECT ステートメントを使用)、データ ポイント (FROM ステートメントを使用)、および省略可能なデータ フィルター (WHERE 句を使用) を使用します。次に、基本的な例を示します。

SELECT {[]...} ON AXIS(0),
        {[]...} ON AXIS(1)
 FROM []
 WHERE ()

販売サンプル キューブ

さまざまな顧客、日付、店舗の販売データをすぐにクエリできるように、サンプル キューブを作成しました (図 1参照)。


図 1 販売サンプル キューブ データベース図

このキューブは、ファクト テーブルに接続された 4 つのディメンション テーブルから構成されています。説明を簡単にするために、ファクト テーブルに含まれるメジャーは、ある店舗で特定の日付に特定の顧客に販売された特定の商品の量を表す Quantity というメジャー 1 つだけにしています。このキューブ構成から、ユーザーはさまざまなディメンションを使用してさまざまなファクトをすばやくクエリできます。たとえば、企業幹部は、各店舗で販売されている商品を把握したり、特定の年月に最も売れた商品などの詳細情報を入手することができます。ディメンションの数を増やせば、さまざまな角度から販売データを分析し、店舗の業績についてさらに考察を深められるようになります。

単体テスト バージョンのキューブを作成する

既に述べたように、キューブの定義は XMLA ドキュメント形式で SSAS から抽出できます。この XMLA ドキュメントを単体テスト バージョンのキューブの作成に使用します。キューブの運用定義を使用することで、テストではテスト対象のキューブのすべての機能が正確に実行されます。SQL Server SDK アセンブリに含まれる Microsoft.AnalysisServices.dll を使用して SSAS エンジンとインターフェイスを取ります。

キューブの生成に使用するオブジェクトは XMLAtoCube です。コンストラクターは、XMLA を格納したベース構成ディレクトリを受け取ります。キューブ XMLA を格納するディレクトリ構造には、<ベース ディレクトリ>\<運用サーバー名>\­<運用データベース名> を選びました。

XMLAtoCube にはパブリック メソッドが 1 つ含まれています。この CreateCubeFromXMLA メソッドは、以下のパラメーターを受け取ります (メソッドのコードについてはコード サンプルに付属の Listings.zip ファイルに含まれる Listing 1 参照)。

  • SourceServerName: キューブを保持する運用サーバー名。
  • TargetServerName: 単体テスト バージョンのキューブの格納先サーバー名。
  • SourceDatabaseName: キューブを保持する運用データベース名。
  • TargetDatabaseName: 単体テスト バージョンのキューブの格納先データベース名。
  • DataSourceProviderDefinition: ソース ディメンションとファクト テーブルの場所を単体テスト バージョンのキューブに指定する接続文字列 URL。この URL が単体テスト用にスケールダウンしたデータのソースになります。

CreateCubeFromXMLA ではまず単体テスト バージョンの分析サーバーへの接続と、単体テスト バージョンのキューブ データベースが既に存在する場合にはその削除を行います。データベースの削除は、結果に悪影響を及ぼす残存成果物のないクリーンな環境でテストを実行するために重要です。分析サーバーへの接続は ConnectToServer メソッド内で、Microsoft.AnalysisServices.Server のインスタンスを使用して実行します。単体テスト サーバー名を渡して Server.Connect(<接続文字列>) を呼び出し、接続を確立します (コード サンプルの Listing 2参照)。サーバーとの接続が正常に確立されたら、単体テスト バージョンのキューブ データベースが既に存在する場合はそれを削除します。この削除は、DropDatabase メソッドで行います。このメソッドは接続した Server インスタンスと削除する単体テスト データベース名 (TargetDatabaseName) を受け取ります。DropDatabase によってサーバー インスタンスの接続が保証され、単体テスト データベース名を渡して Microsoft.AnalysisServices.Database を確認し、データベースが存在する (データベース インスタンスが null でない) 場合には削除します (コード サンプルの Listing 3 参照)。

次は、元のキューブの XMLA 定義を取得し、単体テスト バージョンのキューブを生成します。単体テスト バージョンには、元のキューブと同じディメンション、ディメンション階層、メジャー、パーティション、ロールなどが含まれます。違いは、異なる場所のソース データを指す新しいデータベースにキューブが生成されることです。AddCubeToDatabase メソッドによりテスト キューブを作成します (コード サンプルの Listing 4参照)。AddCubeToDatabase では、前述の名前付け規則を使用してファイル システムから XMLA 定義を読み取ります。XMLA ファイル名は、構築時に XmlTextReader のインスタンスに渡します。XMLA は Microsoft.AnalysisServices.Utils.Deserialize メソッドを使用して読み取ります。このメソッドには XmlTextReader と新しく作成した Microsoft.AnalysisServices.Database のインスタンスを渡します。Database オブジェクト インスタンスにはキューブの完全な定義が含まれますが、この定義にはまだ元のキューブのデータベース名とデータ ソースが指定されています。単体テスト データベースを指定するには、単純に、Database インスタンスの Name プロパティと ID プロパティを "単体テスト データベース名" (targetDatabaseName) パラメーターに設定します。このデータベースは、この後の Server.Databases.Add(<単体テスト データベース>) と Database.Update メソッドの呼び出しによって単体テスト分析サーバーのインスタンスに追加します。

データベース作成後は、キューブのデータ ソースを外部単体テスト データ コレクションに更新する必要があります。Database インスタンスには DataSource インスタンス (通常はキューブに関連付けられた 1 つだけのデータ ソース) のリストがあり、単体テスト接続文字列を XMLA 定義に含まれる接続文字列と置き換えます。接続文字列の置換後は、DataSource.Update メソッドを呼び出して、SSAS サーバー内の接続文字列を更新します。これで、XMLA 定義のカスタマイズが完了し、キューブの残りの部分 (DataSourceView、Dimension、および Cube) が更新および処理されます。

AddCubeToDatabase の呼び出しが完了すると、指定したサーバーに、選択した単体テスト データベースを使用して単体テスト バージョンのキューブが作成されています。これは、ソース データのカスタム セットを参照します。キューブを作成しましたが、まだ空の状態です。ソース データにディメンションを設定するには、ディメンションを処理する必要があります。ProcessDimensions を呼び出して、データベースのインスタンスを渡します。データベース内のすべてのディメンションは、Dimension.Process(ProcessType.ProcessFull) メソッドを使用して処理します。ディメンションを正常に処理したら、ProcessCube メソッドを使用して単体テスト データベース内のキューブを処理します。ProcessDimensions と同様、ProcessCube はデータベースのインスタンスを受け取り、データベース内のすべてのキューブをループし、Cube.Process(ProcessType.ProcessFull) を呼び出します (コード サンプルの Listing 5 参照)。これで、単体テスト キューブが作成され、対象テスト データが設定されます。次は、このキューブに対してテストを実行し、キューブが想定どおりに動作するかを確認します。

キューブを検証する

この検証ソリューションはキューブを対象にしていますが、ここで使用している設計パターンは他の単体テスト フレームワークでも活用できます。採用するパターンは、テンプレート検証を利用したテストです。簡潔に言うと、テスト実行時、結果を含むデータ構造を作成し、過去に保存した正しいと考えられるバージョンのデータ構造に照らし合わせて検証します。バイナリ データ構造を検証するのは難しいため、単体テスト担当者には構造の HTML 表現を提示します。テスト担当者はこの HTML 表現を使用して、単体テストの最初のテスト結果の検証 (結果が想定どおりであることを保証するため) や、2 回目以降の単体テスト失敗の原因の調査を行います。失敗した場合には、元のデータ構造と検証に失敗した部分、およびその理由を HTML に表示します。これは、問題のデバッグを容易にするために不可欠です。

ほとんどのシナリオでの最適なテスト方法は、"ブラック ボックス" テスト手法です。ブラック ボックス テストでは、入力を渡して、出力を検証します。キューブの出力に当たるのはクエリ結果なので、入力はクエリです。MDX ステートメントを使用してキューブにクエリします。キューブで MDX クエリが解釈されると、結果が返されます。結果は、MDX クエリを実行するために選択したディメンションを表現する行と列の形式です。MDX クエリには多数のディメンションを含めることができるため、出力がさまざまな行と列から構成されることになり、クエリ結果が非常に複雑になる場合があります。キューブ データを保持するために選択したデータ構造は、キューブの行名がキーに設定された CubeRow インスタンスの Dictionary です。CubeRow クラスには、状況によって使い分けられる 2 つの代替データ構造を含めています。データ構造の 1 つは、キューブの列名をキーに設定した CubeTuple インスタンスの Dictionary です。CubeTuple は、キューブの列名と指定した列の値を含む単なるオブジェクトです。CubeTuple オブジェクトの Dictionary は指定したキューブの列に値が含まれる場合に使用します。もう 1 つのデータ構造も Dictionary で、CubeRow インスタンスに行名をマッピングしています。MDX クエリは複数レベルの行ディメンションを指定できるため、CubeRow には行名の独自の Dictionary と CubeRow インスタンスを含めます。

キューブの結果は、Dictionary インスタンスに格納されるだけでなく、HTML 文字列にも格納されます。テスト担当者は、HTML 文字列を利用して、キューブの結果を視覚的に表示できます。HTML テーブルには複数レベルの列見出しと行見出しが含まれ、HTML テーブルのセルには MDX 値が含まれます。MDX クエリ結果の HTML 表現のレイアウトを図 2 に示します。

図 2 MDX クエリ結果の HTML 表現レイアウト

   

列ディメンション

キャプション 1 (値 1)

列ディメンション

キャプション 1 (値 1)

列ディメンション

キャプション 1 (値 2)

列ディメンション

キャプション 1 (値 2)

   

列ディメンション

キャプション 2 (値 1)

列ディメンション

キャプション 2 (値 2)

列ディメンション

キャプション 2 (値 1)

列ディメンション

キャプション 2 (値 2)

行ディメンション

キャプション 1 (値 1)

行ディメンション

キャプション 2 (値 1)

MDX 結果値 MDX 結果値 MDX 結果値 MDX 結果値

行ディメンション

キャプション 1 (値 1)

行ディメンション

キャプション 2 (値 2)

MDX 結果値 MDX 結果値 MDX 結果値 MDX 結果値

行ディメンション

キャプション 1 (値 2)

行ディメンション

キャプション 2 (値 1)

MDX 結果値 MDX 結果値 MDX 結果値 MDX 結果値

行ディメンション

キャプション 1 (値 2)

行ディメンション

キャプション 2 (値 2)

MDX 結果値 MDX 結果値 MDX 結果値 MDX 結果値

BaseMDXTest には MDX クエリを実行して MDX 結果データ構造と典型的な XML を構築するためのコードを含めます。BaseMDXTest では、SQL Server SDK アセンブリに含まれる Microsoft.AnalysisServices.AdomdClient.dll を使用して SSAS キューブに接続し、MDX クエリを実行します。BuildTemplate は MDX クエリを実行して MDX 結果 Dictionary と HTML 表現を構築するメソッドです。まず、キューブへの接続を確立します。接続を確立して開くために、接続文字列を MicrosoftAnalysisServices.AdomdClient.AdomdConnection のインスタンスに渡します。次に、新しく作成した接続インスタンスの Open メソッドを呼び出し、接続インスタンスを呼び出し元に返します。接続を確立したら、MicrosoftAnalysisServices.AdomdClient.AdomdCommand メソッドのインスタンスを構築して、MDX クエリ文字列と AdomdConnection インスタンスを渡します。AdomdCommand.ExecuteCellSet メソッドを呼び出して、単体テスト バージョンのキューブに対して MDX クエリを実行し、MicrosoftAnalysisServices.AdomdClient.CellSet インスタンスを返します (コード サンプルの Listing 6 参照)。

CellSet の取得後は、結果セットに 2 つの軸があるかどうかを確認します。軸 0 には列が、軸 1 には行が含まれます。各軸には Position オブジェクトのコレクションが含まれます。Position は特定の軸の組を表し、1 つ以上の Member オブジェクトを含みます。Member は特定の Position の列見出しまたは行見出しを表します (コード サンプルの Listing 7 参照)。

次に、MDX クエリによって返された行と列の数を計算します。この計算では、行数 (軸 1 の Position オブジェクト数) と列ディメンション数 (CellSet.Axes[0].Positions[0].Members.Count) を加算します。MDX 結果を 2 次元テーブルに表示する場合、列を行のセット内に含めるため、列の数を行に追加します。同様に、軸 0 の Position オブジェクト数と行ディメンション数を取得して列の数を計算します (コード サンプルの Listing 8参照)。

行と列の数を求めたら、CubeRow オブジェクトの Dictionary を MDX 出力の HTML 表現と一緒に生成します。コード サンプルの Listing 9 には、MDX 結果セットを走査し、HTML を作成して結果を CubeRow Dictionary に格納するコードを含めています。まず、行の数だけループ処理します。行の数が列ディメンション数より多い場合、新しい MDX 結果の行が使用できることがわかります。つまり、列見出しの行を渡した場合、MDX データを使用できます。この時点で、新しい CubeRow オブジェクトが作成されます。

次に、行内の各列をループ処理します。現在の行数が列ディメンション数よりも少ない場合、現在の行が実際の列見出し行になります。Dictionary では、各列の場所を示す見出しキャプションがコロンで区切られて連結されます。つまり、見出しが複数のディメンションから構成されている場合、各列では解決した順に特定のディメンションが連結されます。列見出しの HTML 生成はさらに簡単で、ディメンションのキャプションを HTML テーブル ヘッダー タグ (

) で単純に囲むだけです。どちらの場合も、現在のディメンションのキャプションは、見出し軸 (CellSet.Axis[0]) を取得して列の位置 (現在の列数から現在の行ディメンション数を引いたもの) とその Position (現在の行数) の現在の Member にアクセスすることで得られます。

現在の行数が列ディメンション数より多い場合は、列見出しを処理することはありません。代わりに、MDX 結果セット行の組を次に処理することになります。列に見出しを設定したのと同じように、MDX 結果セットの行にも見出しを設定できます。処理している列数が行ディメンションの数よりも少ない場合、行見出しを処理します。行見出しごとに新しい Dictionary を作成し、現在の Dictionary に追加して、現在の MDX 結果 Dictionary に設定します。これは、各行見出しに、より詳細な MDX 結果データを含む行の Dictionary が存在することを意味しています。

行見出しキャプションの抽出は、列見出しキャプションの抽出と似ています。行キャプションには、CellSet.Axis[1] の現在の行の Position から現在の列の場所の Member オブジェクトを取得します。行キャプションは、CubeRow の Dictionary のキーであり、現在の CubeRow Dictionary に抽出された行キャプションがない場合は、CubeRow オブジェクトが行キャプション別にキーが設定された Dictionary に追加されます。行キャプションが現在の CubeRow Dictionary に存在しない場合は、CubeRow オブジェクトを取得して currentCubeRow に設定します。

行キャプション メンバーが尽き (現在の列数が行ディメンション数より多くなり)、列見出し行を走査した (現在の行数が列ディメンション数より大きくなった) ら、現在の CubeRow オブジェクトに MDX セルの値を追加します。列見出しと列の値の各組み合わせが、1 つの CubeTuple インスタンスを構成すると考えます。各 CubeRow には、列見出しをキーに設定した CubeTuple オブジェクトの Dictionary が含まれます。列見出しは、以前構築した列見出しの配列から取得します (すべての列キャプションをコロンで区切って連結して列見出しとしたことを思い出してください)。列見出しのインデックスは現在の列数から行ディメンション数を引いたものです (合計列数には行ディメンションが含まれます)。現在の MDX CellSet 値は、適切な 2 次元 (列、行) の点にアクセスして取得します。これは、現在の列数 (から行ディメンションを引いたもの) と現在の行数 (から列ディメンション数を引いたもの) に基づきます。この値を、列見出しと列の値を渡す AddTuple メソッドを使用して CubeRow オブジェクトに追加します。同時に、MDX セルの値を HTML テーブル ディメンション (

) トークンで挟んだものを HTML 表現に追加して、HTML を更新します。図 3は、Dictionaryをグラフィカルに表現しています。

テンプレートの Dictionary と HTML 表現は、BaseMDXTest.PersistTemplate メソッドを使用して、以前定義したファイルの場所に保存します。テンプレートを単体テスト開発者が手作業で検証する必要があるため、テストは失敗していると見なします。これが、BaseMDXTest.TestMDXQuery メソッドが成功時に false を返す理由です。


図 3 CubeRow Dictionary のグラフィカルな表現

テンプレートの作成後、同じテストを 2 回目以降に実行する場合は、以前保存したテンプレートに照らし合わせて検証します。TestMDXQuery メソッドを呼び出すと、まず、指定した名前のテストが存在するかどうか確認されます。存在する場合は、新しいテンプレートの作成要求は行わず (現在のテンプレートが不適切な場合にはテンプレートの再作成の要求が発生することがあります)、テスト結果テンプレートをメモリに読み込みます。テンプレートには、MDX 結果セットのオブジェクト表現と HTML 表現の両方が含まれます。BaseMDXTest.RunComparison メソッドは、MDX クエリの実行、およびその結果と保存されたテンプレートとの比較を行います。MDX クエリ結果はテンプレートを作成したときと同じように走査します。元のテンプレートを作成したときとテンプレートと比較して検証するときの主な違いは、Dictionary を作成するのではなく、テンプレート Dictionary に対して検索を実行し、同じ MDX クエリ結果が存在するかどうかを確認することです。行と列をループ処理するときには、テンプレート作成時と同じ方法で HTML テーブルを作成します。ただし、今回は、HTML テーブル内のセルに色を付けます。緑色のセルは、セルが元のテンプレートと一致していることを、赤色のセルは一致していないことを表します。セルを色付けして HTML テーブルに表示することで、単体テスト担当者はテスト ケースが成功/失敗した理由をすぐに確認できます。不一致が存在する場合には、ブール値 (testPass) に false を設定し、テスト ケースが失敗したことを示します。

MDX クエリ結果を走査し、テンプレート Dictionary に照らし合わせて検証する間に、見つかった各 CubeTuple (列のディメンション、連結した名前、および列の値を含むオブジェクト) を CubeTuple オブジェクトの現在の CubeRow Dictionary から削除します。そのため、MDX クエリ結果全体が渡された後、MDX 結果が完全に一致している場合は、元のテンプレート Dictionary には CubeTuple オブジェクトの空の Dictionary が設定された CubeRow オブジェクトが存在することになります。そうでない場合、新しい MDX クエリ結果では、元の結果に含まれるデータが不足しています。BaseMDXTest.CheckForExtraDataInTemplate メソッドを再帰的に実行して、テンプレート Dictionary の残りの CubeTuple オブジェクトを調査し、CubeTuple が残っている場合には true の値を返します。余分なデータが見つかった場合は、RunComparison メソッドの testPass のブール値を false に設定し、このテスト ケースを失敗とします。

MDX 結果をすべて走査し、テンプレート Dictionary に照らし合わせて検証したら、Cube­ComparisonResult オブジェクトのインスタンスを返します。これは、testPass のブール値と結果を示す HTML テーブルから構成されています。BaseMDXTest.TestMDXQuery メソッドは、CubeComparisonResult を使用して元の MDX クエリ結果 HTML テーブルおよび比較する HTML テーブルを示す HTML ページを構築します。HTML は、BaseMDXTest.PersistTestReport メソッドを呼び出して、ファイル システムに保存します。このメソッドでは、実行したすべてのテストとその HTML 結果ページへのリンク、および成功/失敗したテスト ケース数の概要が一覧になった TestReport.html 概要Web ページを作成します。

購入キューブをテストする

キューブ テスト フレームワークの両方のコンポーネント、つまりキューブ作成コード (XMLAtoCube) と MDX クエリ結果テンプレート (BaseMDXTest) を使用して、単体テスト ケースの作成とキューブの検証を実行できます。フレームワークのコードは膨大ですが、テスト ケースを作成するのはわかりやすく単純です。コード サンプルの Listing 10に、購入キューブを検証するサンプル単体テストを含めています。これらのテスト ケースではマイクロソフトのテスト フレームワークを使用していますが、任意のテスト フレームワークを組み込むことができます。

単体テスト オブジェクト (サンプルの PurchaseCubeTest) は BaseMDXTest から継承しています。PurchaseCubeTest の既定のコンストラクターは、キューブが配置されている SSAS サーバーの URL、および MDX クエリ結果テンプレートと後続のテスト結果を保存するベース ディレクトリを設定した BaseMDXTest を構築します。

[TestInitialize] メソッドは、単体テスト バージョンの購入キューブの作成に使用します。このメソッドは、元のキューブの XMLA 定義を利用し、単体テスト データベース (targetDatabaseName) を使用して単体テスト SSAS サーバー (targetServerName) にキューブを作成します。また、テスト ディメンションとファクト データの場所のソース データ URL を参照します。[TestInitialize] は指定された [TestClass] に対して 1 回だけ実行され、テスト開始時にのみキューブを作成します。

テスト ケース自体は [TestMethod] 注釈付きメソッド内で実行します。各テストは単純です。MDX クエリを定義し、継承した BaseMDXTest.TestMDXQuery メソッドを使用して実行します。テスト ケースには名前をつけ、MDX クエリに渡します。TestMDXQuery では、テストが成功の場合は true、失敗の場合には false を返します。単体テストの成功/失敗を渡すために Assert.IsTrue メソッドを使用します。すべてのテストの実行が終わると、結果の HTML テスト ドキュメントを開き、失敗したテスト ケースを調査できます。図 4は、あるテストの HTML 出力例です。


図 4 サンプル テストの HTML 出力

適切にテストされたコード

簡単ではありませんが、C# を使用して OLAP キューブも単体テストできます。Microsoft.AnalysisServices.dll と Microsoft.AnalysisServicesAdomdClient.dll ファイルの両方を SQL Server アセンブリに含めることにより、SSAS キューブの作成とクエリの両方に利用できる API を使用できます。今回提案したアーキテクチャを利用して、充実した単体テストのセットを追加して、読みやすい形式の出力を行い、テスト ケースの失敗をすばやく特定することができます。テンプレートを使用してテスト ケースを検証する手法は、出力の検証が容易ではない他の単体テスト アーキテクチャにも応用できます。応用の例は、アプリケーションの実行後にデータベースに保存するデータをテンプレートに含めて従来のリレーショナル データベース永続化を使用するアプリケーションから、UI の状態をデータ構造に格納して HTML 形式で UI を表示する UI アプリケーションまで、多岐にわたります。

トーマス・ジェファーソンや米国建国の父たちが提唱した国家の自由が、コードが適切にテストされる権利に匹敵するかどうかはわかりませんが、アプリケーションが適切にテストされていることがわかれば、ユーザーや上司が喜ぶのは確実です。

Mark Nadelson は、通信、インターネット、および金融業界における 22 年の経験を持つプロフェッショナルのソフトウェア開発者です。キャリア全体を通じて、アセンブリ、C、C++、Java、C# など、さまざまなプログラミング言語を使用してきました。また、Nadelson は、2 冊の書籍の著者であり、多数の技術記事の執筆もしています。

この記事のレビューに協力してくれた技術スタッフの David Neigler (SAC Capital Advisors LP) と Joe Sampino (SAC Capital Advisors LP) に感謝します。
David Neigler は SAC Capital Advisors LP に勤務する、金融業界の開発マネージャーです。彼は、大規模な取引を行う金融企業が直面するビッグ データ問題を解決するシステムの開発に取り組んでいます。

Joe Sampino は SAC Capital Advisors LP に勤務し、大手銀行と投資企業向けのビジネス インテリジェンス プラットフォームを開発しています。彼は、社内外の監査とコンプライアンスのニーズに合わせたデータ ウェアハウス、SQL Server Analysis Services キューブ、およびデータ分析/レポート システムの作成と管理を担当しています。