大化通信

大化物流開発合同会社の社員から技術発信をしていきます

Visual Studio Code でUML図を描く方法

始めに

3か月ぶりの登場です。迷走星人です。 今回は「Visual Studio Code(以後VSCode)」でUML図を作成する方法について紹介します。

環境構築

1.インストーラの入手

以下のインストーラを入手してきます。

2.拡張機能のインストール

VSCodeがインストール出来たら、UML図を描画するために、以下の拡張機能をインストールします。

  • Japanese Language Pack for Visual Studio Code
    • 日本語言語パック(インストール直後は英語)
  • PlantUML
    • VSCodeUML図を描画するための本体

上記の拡張機能は以下の手順でインストール

  1. VSCodeの画面の左下にある歯車のアイコンをクリック
  2. 拡張機能」の項目を選択
  3. 手順2.実施後、画面にその時点でインストールされている拡張機能の一覧が表示されるので、
    冒頭にある検索テキストボックスにインストールしたい拡張機能を入力
  4. 手順3.で拡張機能を見つけたら、「インストール」をクリック

詳細は以下のサイトを参照してください。

PlantUMLでの図の書き方

拡張機能のインストールまで完了したら、いよいよPlantUMLでUML図を描画してみます。 まずは基本の描き方です。 VSCodeUMLソースコードファイル(ここでは拡張子は「.pu」とします)を開き、ファイルの冒頭と末尾に以下の記述をします。

@startuml
@enduml

この2つの行の間に描きたいUML図のソースコードを書いていきます。 例えば、アクティビティ図やシーケンス図を描く時は以下の様に記述します。

@startuml

title アクティビティ図サンプル

start

:処理1;

:処理2;

if (分岐) then (分岐の結果(真))
    :処理3;
else (分岐の結果(偽))
    :処理4;
endif

while (ループ)
    :処理5;

    if (終了判定) then (真)
        break
    endif

    :処理6;
endwhile

if (分岐) then (真)
    stop
endif

switch (分岐2)
    case (ケース1)
        :処理7;
    case (ケース2)
        :処理8;
    case (ケース3)
        :処理9;
    case (ケース4)
        :処理10;
    case (ケース5)
        :処理11;
endswitch

end

@enduml

アクティビティ図サンプル処理1処理2分岐分岐の結果(真)分岐の結果(偽)処理3処理4処理5終了判定処理6ループ分岐分岐2処理7処理8処理9処理10処理11ケース1ケース5ケース2ケース3ケース4

@startuml

title シーケンス図サンプル

box ユーザー : #HotPink
    participant ユーザ1 as User1
    participant 管理人1 as Admin1
end box

box 画面 : #Brown
    participant 画面1 as Form1
    participant 画面2 as Form2
end box

box クラス : #LightGreen
    participant クラス1 as Class1
    participant クラス2 as Class2
end box 

box データベース #0000CC
    database DB1 as DB1
end box

activate User1
activate Admin1

alt ユーザ1
    User1 -> Form1 : 開く
    activate Form1
    User1 -> Form1 : ボタン押下
    Form1 -> Class1 : インスタンス生成
    activate Class1
    group 内部処理
        Class1 -> DB1 : 取得
        activate DB1
        DB1 -> Class1 : 取得結果
        deactivate DB1

        Class1 -> Class1 : 内部処理
    end
    Class1 --> Form1
    deactivate Class1
    Form1 --> User1
    deactivate Form2
else 管理人
    Admin1 -> Form2 : 開く
    activate Form2
    opt 新規登録者有
        Admin1 -> Form2 : 登録
        Form2 -> Class2 : インスタンス生成
        activate Class2
        loop 登録者リストループ
            Class2 -> DB1 : 登録
            activate DB1
            DB1 -> Class2 : 登録結果
            deactivate DB1
        end
        Class2 -> Form2 : 実行結果
        deactivate Class2
    end
    Form2 --> Admin1
    deactivate Form2
end

deactivate Admin1
deactivate User1


@enduml

シーケンス図サンプルユーザー :画面 :クラス :データベースユーザ1ユーザ1管理人1管理人1画面1画面1画面2画面2クラス1クラス1クラス2クラス2DB1DB1alt[ユーザ1]開くボタン押下インスタンス生成内部処理取得取得結果内部処理[管理人]開くopt[新規登録者有]登録インスタンス生成loop[登録者リストループ]登録登録結果実行結果

PlantUMLは他にもマインドマップJSONの構成図を描くことが出来ます。 それぞれの図の描き方は以下の通りです。

@startmindmap
title マインドマップ図サンプル
+ NodeRoot
++ NodeSub01-01
+++ NodeSub02-01
++ NodeSub01-02
-- NodeSub01-03
--- NodeSub02-02
-- NodeSub01-04
@endmindmap

マインドマップ図サンプルNodeRootNodeSub01-01NodeSub02-01NodeSub01-02NodeSub01-03NodeSub01-04NodeSub02-02

@startjson

{
    "key01": "val01",
    "key02": {
        "subKey01" : "subVal01",
        "subKey02" : "subVal02"
    },
    "Key03" : [],
    "Key04" : [
        "elem1",
        "elem2", 
        "・・・", 
        "elemN"
    ]
}

@endjson

key01val01key02   Key03   Key04   subKey01subVal01subKey02subVal02elem1elem2・・・elemN

PlantUMLでの図の描き方については、日本語のオンラインマニュアルも公開されているので、そちらも参照してください。

Markdown Preview Enhancedの導入

Markdown Preview Enhanced」の拡張機能をインストールすると、Markdown形式のファイルの中にPlantUMLの図を描くことが出来ます。詳細は以下のサイトを参考にしてください。

ただし、上記のマインドマップ図やJSON図を描く時は少し注意が必要です。 これらの図を描く時は、 「@startuml」「@enduml」だけでなく、更にその間に「@startmindmap」「@endmindmap」または、「@startjson」「@endjson」の挿入が必要になります。後は、同じ描き方です。

正規表現について

こんにちは、大化社員のnezutchiです。
今回は正規表現について記事にしました。

1.正規表現とは

 複数の文字列を一つの形式で表現する方法です。

2.正規表現で使う特殊文字

 . ^ $ [ ] * + ? | ( )

 これらをメタ文字と呼びます。
 メタ(meta)とは「超越」という意味です。
 また、文字列の中からメタ文字を含む文字を検索する場合は
 \(バックスラッシュ)でエスケープする必要があります。

3.メタ文字の種類と意味

(1) なんでもいい1文字 .(ドット)
   .   なんでもいい1文字を表現する。
(2) 行の先頭と末尾 ^(ハット) $(ドル)
   ^  行の先頭を表現する。
   $  行の末尾を表現する。
(3) 同じ文字の繰り返し *(アスタリスク) +(プラス) ?(クエッション)
   *  直前の1文字の0回以上の繰り返しを表現する。
   +  直前の1文字の1回以上の繰り返しを表現する。
   ?  直前の1文字の0か1回を表現する。
(4) いずれかの文字列 |(パイプ)
   |  複数の文字列をorで表現する。
   例:〇〇|△△|××
(5) 指定した文字のどれか 
   []  大括弧内のいずれかを指定する表現。
   例:[abc]     abcのいずれか
     [a-z]     アルファベット(小文字)のいずれか
     [A-Z]     アルファベット(大文字)のいずれか
     [0-9]     数字のいずれか
     ※[^0-9]のように大括弧内でハットを使うと否定の意味を持つ。
       ↑は0~9以外となる。
(6) グループ化( )(小括弧)
   () 小括弧内を一塊の文字列に指定できる。

4.応用したよく使う表現

・特定の文字列を含む行を検索する場合
 〇〇の使われ方(前後を含む)調査をするときなど。

   .*〇〇.*\r\n


・特定の文字列を含まない行を検索する場合
 〇〇を含まない行を選択することで対象外の行を削除するときなど。

  ^(?!.*〇〇).*\r\n

コマンドプロンプトでPowerShellのコマンドを実行する

こんにちは、大化社員のKayです。

batファイルで実装したいのに欲しい機能がPowerShellにしかない等、PowerShellが使用できない状況でもPowerShellのコマンドが使いたい時があると思います。

今回は、そういう時にコマンドプロンプトやbatファイルでPowerShellのコマンドを実行する方法について紹介したいと思います。

コマンドプロンプトからの実行例

コマンドプロンプトPowerShellのコマンドを実行するには次のように記載します。

powershell -command "<PowerShellのコマンド>"

PowerShellでの記載例

例えばPowerShellでGuidを取得する場合

New-Guid

Guid
----
c28104eb-e834-4071-83e1-0100fca12afe

と記述します。

実際にコマンドプロンプトからPowerShellのコマンドを実行する

これをコマンドプロンプトで記述すると

powershell -command "New-Guid"

Guid
----
c28104eb-e834-4071-83e1-0100fca12afe

のように、PowerShellで実行したときと同じ結果が返ってきます。

終わりに

今回は、コマンドプロンプトからPowerShellのコマンドを実行する方法について紹介させていただきました。 ここまで読んでいただきありがとうございました。

PHPでwordファイルの文字列を取得する

こんにちは、大化社員のkannaです。
今回はPHPでword(docx)ファイルの文字列の取得を行ったのでそれについて記事にしました。

以前携わった案件にてwordファイルを読み込み、
特定の文字列を抜き出し置換用のテンプレートをcsvで作成するという処理があったため備忘録として書きました。

概要

PHP標準のZipArchiveを使用し
word(docx)ファイルのプレーンテキストを取得

環境

本記事での環境は以下

文字列の取得(本文のみ)

// 取得した文字列格納用
$contents = "";
// 読み込むword(docx)ファイル
$file_path = /path/to/file.docx;
// ZipArchiveクラスの初期化
$zip = new \ZipArchive();
if ($zip->open($file_path) === true) {
    // zipアーカイブからxmlファイルを取得
    $xml = $zip->getFromName("word/document.xml");
    // xmlファイルから文字列を取得
    if ($xml) {
        $dom = new \DOMDocument();
        $dom->loadXML($xml);
        $paragraphs = $dom->getElementsByTagName("p");
        foreach ($paragraphs as $p) {
            $texts = $p->getElementsByTagName("t");
            foreach ($texts as $t) {
                $contents .= $t->nodeValue;
            }
            $contents .= "\n";
        }
    }
}

$zip->open($file_path)
wordファイルを読み込みzipファイルとして開けた場合はtrueそれ以外はエラーコードが返却されます。

$xml = $zip->getFromName("word/document.xml");
wordファイルはzipファイルなため解凍した際にword/document.xmlというファイルが存在し、その中にテキスト情報が入っています。

$dom = new \DOMDocument();
$dom->loadXML($xml);
DOMDocumentを使用しxmlファイルの読み込みを行います。

$paragraphs = $dom->getElementsByTagName("p");
xmlファイルから段落のDOMNodelistを取得します。

$texts = $p->getElementsByTagName("t");
段落のDOMNodelistを回してテキストのDOMNodelistを取得します。

foreach ($texts as $t) { $contents .= $t->nodeValue; }
テキストのDOMNodelistを回して文字列の値を取得します。

ヘッダー・フッターの文字列も含めて取得を行う場合

$contents = "";
$file_path = /path/to/file.docx;
$zip = new \ZipArchive();
if ($zip->open($file_path) === true) {
    // 本文のxmlファイルをリストに追加
    $xmlFiles = ["word/document.xml"];
    for ($i = 1; $i <= $zip->numFiles; $i++) {
        $name = $zip->getNameIndex($i - 1);
        // ヘッダーとフッターのxmlファイルが存在する場合はリストに追加
        if (preg_match('/word\/header\d+\.xml/', $name) || 
        preg_match('/word\/footer\d+\.xml/', $name)) {
            $xmlFiles[] = $name;
        }
    }
    // xmlファイルから文字列の取得を行う
    foreach ($xmlFiles as $xmlFile) {
        $xml = $zip->getFromName($xmlFile);
        if ($xml) {
            $dom = new \DOMDocument();
            $dom->loadXML($xml);
            $paragraphs = $dom->getElementsByTagName("p");
            foreach ($paragraphs as $p) {
                $texts = $p->getElementsByTagName("t");
                foreach ($texts as $t) {
                    $contents .= $t->nodeValue;
                }
                $contents .= "\n";
            }
        }
    }
}

if (preg_match('/word\/header\d+\.xml/', $name) || preg_match('/word\/footer\d+\.xml/', $name))
ヘッダーとフッターは"word/header1.xml"、"word/footer1.xml"などでxmlファイルとして取得できるので
preg_matchを使用して取得しています。

PHPでwordファイルの文字列を取得するについては以上になります。
読んでくださってありがとうございます。
参考になれば幸いです。

参考サイト

今更ながらphpでword(docx)を読み込んでplain textを得る

tamago-engineer.hateblo.jp

COBOLのルーチンが見当たらない!

こんにちは、大化社員のかまぼこです。 COBOLの修正作業をするにあたって、個人的に感じたやりにくさと、それを改善した方法についてご紹介したいと思います。

(今回、COBOLの環境構築等については割愛)

私は基本的にサクラエディタを使用してCOBOLの修正を行なっているのですが、長文で複雑なソースになればなるほどルーチンの数が多く、見たいルーチンを見失うことが頻発します。

例えば

《主処理》  ①  ②  ③

 …

〈①〉

 ④を呼び出す処理

〈②〉

〈③〉

〈④〉

…と言うように、複数のルーチン(①〜④)があった場合。 サクラエディタでは、ルーチンは特に目立つことなくただ文字の羅列として表示されるため、文字検索を駆使して探し当てる事も少なくありません。

出来ればルーチンだけでも目立たせて欲しい、一覧での表示やジャンプ機能が欲しい……。 そういったCOBOL初心者の願いを叶えるツールとその設定を見つけることができました。

それは、 『秀丸』の『COBOLアウトライン解析』!

秀丸自体が有名なアプリですから、ご存知の方も多いかもしれません。 ですが、私にとってこれは画期的で、なんだか凄いものを発見したような気分になりました。

せっかくなので、インストールから設定方法までをご説明させて頂きます。

秀丸をインストールします。 秀丸公式サイト:https://hide.maruo.co.jp/software/hidemaru64.html

②メニューバー内の その他 > ファイルタイプ別の設定 を押下

1

③アウトライン を選択し、 アウトライン解析の枠 へチェックを入れる

④解析 を選択し、追加ボタンを押下

⑤種類(K)項目で「文字列」を選択し、文字列(S)項目へ ”section” と記載  右側のメニュー内 表示範囲(N)項目で「行全体」を選択

⑥設定を「OK」を押下して閉じると、右側に一覧用のスペースが表示されます。

実際にCOBOLソースを表示させると、このようになります(ソースはテスト用のものになります)。

右側に「section」が含まれる行が表示されており、とても見やすくなっています。 また、表示されている行を選択すると、該当の場所へジャンプすることができます。

今回は、COBOLが見やすくなる設定についてご紹介させて頂きました。 ご参考になれば幸いです。

成績ランク算出プログラムを設計・製造してみた(プログラム試験実践編)

はじめに

どうも、迷走星人です。 4回目はプログラム試験の実践部分の話を出来たらと思います。 尚、この段階では以下の工程が完了しているものとします。

  • プログラムの設計&レビュー
  • プログラムの実装&レビュー
  • プログラム試験の設計&レビュー

試験環境準備

単体テスト

プログラム試験1の試験を実施するために、テスト用のプロジェクトを用意します。 テスト用のプロジェクトは、Visual Studioの「ネイティブ単体テストプロジェクト」で作成します。

テスト用プロジェクトの作成

まずはテスト用のプロジェクト作成をしていきます。作成手順は以下に書いていきます。

  1. プログラムを作成したソリューションから[追加]->[新しいプロジェクト]を選択します。

テスト用プロジェクト作成画面キャプチャ01
2. プロジェクトウィザードから「ネイティブ単体テストプロジェクト」を選択します。

テスト用プロジェクト作成画面キャプチャ02
3. 自分のPCの任意の場所にプロジェクトを作成します。ここでは「TaikaProj01_CalcGradeRankTest」というプロジェクト名にします。

テスト用プロジェクト作成画面キャプチャ03
4. 単体テスト用のプロジェクトが作成されました。このテスト用ソースコードにテスト用の実装を進めていきます。

テスト用プロジェクト作成画面キャプチャ04

テスト用プロジェクトの設定

テスト用のプロジェクトが作成出来たら、テスト対象のプロジェクトとリンクするように設定します。

  1. テスト用プロジェクトで、[参照]->[参照の追加]を選択します。

テスト用プロジェクト設定画面キャプチャ01
2. 「参照の追加」ダイアログで[プロジェクト]を選択し、テスト対象のプロジェクトのチェックボックスにチェックを入れます。

テスト用プロジェクト設定画面キャプチャ02
3. テスト用のプロジェクトに今回のプログラム開発で実装したクラスファイルの「*.h」及び「*.obj」格納場所を追加します。また、「*.obj」はファイル名もテスト用プロジェクトに追加します。

テスト用プロジェクト設定画面キャプチャ03-01
テスト用プロジェクト設定画面キャプチャ03-02

  • *.objファイル
    • ライブラリディレクトリパス([構成プロパティ]⇒[リンカー]⇒[追加のライブラリディレクトリ])
      テスト用プロジェクト設定画面キャプチャ03-03
      テスト用プロジェクト設定画面キャプチャ03-04
    • ライブラリファイル名([構成プロパティ]⇒[リンカー]⇒[入力])
      テスト用プロジェクト設定画面キャプチャ03-05
      テスト用プロジェクト設定画面キャプチャ03-06

試験実施

テスト対象のプロジェクトとのリンク設定が完了出来たら、いよいよテスト用のソースコードを実装していきます。 テスト用プロジェクトの「pch.h」に今回のプログラム開発で実装したヘッダーファイルをincludeします。

テスト用プロジェクトヘッダファイル設定画面キャプチャ

プログラム試験1

試験用テストコード実装

まずはプログラム試験1のテスト用ソースコードの実装です。 2つの機能のテスト用ソースコードを用意します。

メソッド名 内容
TestMethod1 ファイル読み書き用メソッド
TestMethod2 成績ランク算出用メソッド

それぞれのメソッド内でテスト用の構造体を定義します。 テスト用の構造体データを格納した配列をループさせて、設計したテストパターンを機械的に実施していきます。 * ファイル読み書き機能

typedef struct _testCase {
    int     TestNo;                         // テストNo.
    char    TestFname[BUFSIZE0128];         // テスト用結果ファイル名
    int     TestMode;                       // テスト用モード
    int     TestExpectedRtn;                // 期待する戻り値
    char    TestExpectedName[BUFSIZE0128];  // 期待する名前
    char    TestExpectedGrade[BUFSIZE0128]; // 期待する点数
} testCase;
  • 成績ランク算出機能
typedef struct _testCase {
    int     TestNo;                 // テストNo.
    int     TestNoSub;              // テストサブNo.
    char    TestGrade[BUFSIZE0128]; // テスト用試験点数
    int     TestExpectedRtn;        // 期待する戻り値
    char    TestExpectedRank;       // 期待するランク
} testCase;

プログラム試験1のテスト実施フローを以下にまとめます。

テストパターンを設定テスト対象機能クラスのインスタンス生成テスト結果のカウントを初期化(OK/NG)テスト対象の関数実行戻り値確認戻り値の結果で分岐期待通り期待通りでないテスト結果(戻り値):OK戻り値が「COM_SUCCESS」?YESNO配列データの値を確認配列データの値の確認結果で分岐期待通り期待通りでないテスト結果(OK)カウントアップテスト結果(NG)カウントアップテスト結果(OK)カウントアップテスト結果(戻り値):NGテスト結果(NG)カウントアップテストパターンループテスト実施結果を出力

フロー図の通り、テスト結果が「OK」となるのは、以下のパターンです。

パターンNo. 戻り値 配列のデータ or 算出した成績ランク
1 期待する値と一致
(COM_SUCCESS)
期待する値と一致
2 期待する値と一致
(COM_FAILED)
---

上記パターンのどちらにも属さない場合は、全てテスト結果「NG」とします。

実際に試験を実施するためのソースコード(ファイル読み書き機能)は以下の通りです。

/// <summary>
/// ファイル読み書き機能のテスト用メソッド
/// </summary>
TEST_METHOD(TestMethod1)
{
    // テスト用構造体データ
    typedef struct _testCase {
        int     TestNo;                         // テストNo.
        char    TestFname[BUFSIZE0128];         // テスト用結果ファイル名
        int     TestMode;                       // テスト用モード
        int     TestExpectedRtn;                // 期待する戻り値
        char    TestExpectedName[BUFSIZE0128];  // 期待する名前
        char    TestExpectedGrade[BUFSIZE0128]; // 期待する点数
    } testCase;

    int i;
    int TestRtn;
    int TestArraySize = 0;
    char TestResultBuf[BUFSIZE1024];
    char TestFilePath[BUFSIZE1024];
    std::vector<structGradeRank> testList;
    std::string rootdir = "D:\    aika\\SVN\\Project01\\x64\\Debug";

    // 初期化
    testList.clear();
    int TestOKCount = 0;
    int TestNGCount = 0;
    memset( TestResultBuf, 0x00, sizeof(TestResultBuf));

    // テスト用パターンを設定
    testCase testCases[] = {
        {1, {NULL}, COM_MODER, COM_FAILED, {NULL}, {NULL}},
        {2, "WBT01_02.csv", COM_MODER, COM_FAILED, {NULL}, {NULL}},
        {3, "WBT01_03.csv", COM_MODER, COM_SUCCESS, {NULL}, {NULL}},
        {4, "WBT01_04.csv", COM_MODER, COM_SUCCESS, {NULL}, {NULL}},
        {5, "WBT01_05.csv", COM_MODER, COM_SUCCESS, "テスト5", "100"},
        {6, "WBT01_06.csv", COM_MODEW, COM_FAILED, {NULL}, {NULL}},
        {7, "WBT01_07.csv", COM_MODEW, COM_SUCCESS, {NULL}, {NULL}},
        {8, "WBT01_08.csv", COM_MODEW, COM_SUCCESS, "テスト8", "100"}
    };

    // インスタンスを生成
    ClassFileReadWrite testClassFileReadWrite;

    // テストケース毎にループ処理
    TestArraySize = sizeof(testCases) / sizeof(testCases[0]);
    for (i = 0; i < TestArraySize; i++)
    {
        // 初期化
        testList.clear();
        memset(TestFilePath, '\0', sizeof(TestFilePath));

        if (testCases[i].TestNo == 8) {
            structGradeRank data;
            memset(&data, 0x00, sizeof(structGradeRank));
            sprintf_s(data.name, testCases[i].TestExpectedName);
            sprintf_s(data.grade, testCases[i].TestExpectedGrade);
            data.rank = 'A';
            testList.push_back(data);
        }

        // 結果ファイルの絶対パスを設定
        if (testCases[i].TestFname[0] == NULL)
        {
            TestRtn = testClassFileReadWrite.FileReadWrite(
                NULL,
                testCases[i].TestMode,
                testList
            );
        }
        else
        {
            sprintf_s(TestFilePath, "%s\\%s", rootdir.c_str(), testCases[i].TestFname);
            // テスト対象の関数を実行
            TestRtn = testClassFileReadWrite.FileReadWrite(
                TestFilePath,
                testCases[i].TestMode,
                testList
            );
        }

        // テスト対象の関数の実行結果を確認
        memset(TestResultBuf, 0x00, sizeof(TestResultBuf));
        if (TestRtn == testCases[i].TestExpectedRtn) {
            sprintf_s(TestResultBuf, "テスト%02d:OK[TestRtn:%d    Expected:%d]\n", testCases[i].TestNo, TestRtn, testCases[i].TestExpectedRtn);
            Logger::WriteMessage(TestResultBuf);
            if (TestRtn == COM_SUCCESS) {
                if ((testCases[i].TestMode == COM_MODER) && (!testList.empty()))
                {
                    // 名前
                    if ((strcmp(testCases[i].TestExpectedName, testList.at(0).name) == 0) &&
                        (strcmp(testCases[i].TestExpectedGrade, testList.at(0).grade) == 0)) {
                        sprintf_s(TestResultBuf, "テスト%02d:OK[Name(Expected):%s    Name(Actual):%s]\n",
                            testCases[i].TestNo,
                            testCases[i].TestExpectedName,
                            testList.at(0).name
                        );
                        Logger::WriteMessage(TestResultBuf);
                        sprintf_s(TestResultBuf, "テスト%02d:OK[Grade(Expected):%s    Grade(Actual):%s]\n",
                            testCases[i].TestNo,
                            testCases[i].TestExpectedGrade,
                            testList.at(0).grade
                        );
                        Logger::WriteMessage(TestResultBuf);
                        TestOKCount++;
                    }
                    else {
                        sprintf_s(TestResultBuf, "テスト%02d:NG[Name(Expected):%s    Name(Actual):%s]\n",
                            testCases[i].TestNo,
                            testCases[i].TestExpectedName,
                            testList.at(0).name
                        );
                        Logger::WriteMessage(TestResultBuf);
                        sprintf_s(TestResultBuf, "テスト%02d:NG[Grade(Expected):%s    Grade(Actual):%s]\n",
                            testCases[i].TestNo,
                            testCases[i].TestExpectedGrade,
                            testList.at(0).grade
                        );
                        Logger::WriteMessage(TestResultBuf);
                        TestNGCount++;
                    }
                }
                else
                {
                    sprintf_s(TestResultBuf, "テスト%02d:OK[Mode:%d    size:%d]\n",
                        testCases[i].TestNo,
                        testCases[i].TestMode,
                        (int)testList.size()
                    );
                    Logger::WriteMessage(TestResultBuf);
                    TestOKCount++;
                }
            }
            else {
                // 戻り値が「COM_FAILED」
                TestOKCount++;
            }
        }
        else {
            // 戻り値と期待する戻り値が異なる
            sprintf_s(TestResultBuf, "テスト%02d:NG[TestRtn:%d    Expected:%d]\n", testCases[i].TestNo, TestRtn, testCases[i].TestExpectedRtn);
            Logger::WriteMessage(TestResultBuf);
            TestNGCount++;
        }
    }

    sprintf_s( TestResultBuf, "テスト結果[OK:%3d 件    NG:%3d 件]\n", TestOKCount, TestNGCount);
    Logger::WriteMessage( TestResultBuf );
}

テスト用ソースコードの内部の処理は以下の順で行います。

  1. テスト用構造体の配列データを用意
  2. テストケースごとに試験用の入力パラメータを用意
  3. 配列データをループさせ、テストケースごとに試験対象の機能を実行
  4. 戻り値とデータを確認

上記の処理 4. でテストケースごとの実施結果をLogger::WriteMessage関数で出力ウィンドウに表示しています。また、OK、NGそれぞれの件数を集計し、全てのテストケース実施後、その集計結果も出力ウィンドウに表示させます。 成績ランク算出機能も同様のソースコードを実装していきます。

/// <summary>
/// 成績ランク算出機能のテスト用メソッド
/// </summary>
TEST_METHOD(TestMethod2)
{
    // テスト用構造体データ
    typedef struct _testCase {
        int     TestNo;                 // テストNo.
        int     TestNoSub;              // テストサブNo.
        char    TestGrade[BUFSIZE0128]; // テスト用試験点数
        int     TestExpectedRtn;        // 期待する戻り値
        char    TestExpectedRank;       // 期待するランク
    } testCase;

    int TestRtn;
    std::vector<structGradeRank> testList;  // テスト用データ配列
    char TestResultBuf[BUFSIZE1024];
    char TestCaseName[BUFSIZE0128];
    int TestOKCount = 0;
    int TestNGCount = 0;

    // インスタンスを生成
    ClassCalcGradeRank testClassCalcGradeRank;

    // テスト用パターンを設定
    testCase testCases[] = {
        { 1, 0, "9999",   COM_FAILED, '\0'},
        { 2, 0, "95",   COM_SUCCESS, 'A'},
        { 3, 0, "85",   COM_SUCCESS, 'B'},
        { 4, 0, "70",   COM_SUCCESS, 'C'},
        { 5, 0, "30",   COM_SUCCESS, 'D'},
        { 6, 1, "101",  COM_SUCCESS, 'E'},
        { 6, 2, "-1",  COM_SUCCESS, 'E'},
        { 7, 1, "100.04",  COM_SUCCESS, 'A'},
        { 7, 2, "89.95",  COM_SUCCESS, 'A'},
        { 8, 1, "89.94",  COM_SUCCESS, 'B'},
        { 8, 2, "74.95",  COM_SUCCESS, 'B'},
        { 9, 1, "74.94",  COM_SUCCESS, 'C'},
        { 9, 2, "59.95",  COM_SUCCESS, 'C'},
        { 10, 1, "59.94",  COM_SUCCESS, 'D'},
        { 10, 2, "-0.04",  COM_SUCCESS, 'D'},
        { 11, 1, "100.05",  COM_SUCCESS, 'E'},
        { 11, 2, "-0.05",  COM_SUCCESS, 'E'},
        { 12, 1, "P",  COM_SUCCESS, 'E'},
        { 12, 2, "#",  COM_SUCCESS, 'E'},
        { 12, 3, "ア",  COM_SUCCESS, 'E'},
        { 12, 4, "P",  COM_SUCCESS, 'E'},
        { 12, 5, "@",  COM_SUCCESS, 'E'},
        { 12, 6, "あ",  COM_SUCCESS, 'E'},
        { 12, 7, "ア",  COM_SUCCESS, 'E'},
        { 12, 8, "百",  COM_SUCCESS, 'E'},
    };

    structGradeRank TestData;
    // テストケース毎にループ処理
    for (int i = 0; i < sizeof(testCases) / sizeof(testCase); i++)
    {
        // 初期化
        memset( &TestData, 0x00, sizeof(TestData) );
        memset(TestCaseName, 0x00, sizeof(TestCaseName));
        testList.clear();

        // テスト用のデータを生成
        if ( testCases[i].TestNoSub == 0 ) {
            sprintf_s(TestData.name, "TestName%02d", testCases[i].TestNo);
            sprintf_s(TestCaseName, "テスト%02d   ", testCases[i].TestNo);
        }
        else {
            sprintf_s(TestData.name, "TestName%02d_%02d", testCases[i].TestNo, testCases[i].TestNoSub);
            sprintf_s(TestCaseName, "テスト%02d-%02d", testCases[i].TestNo, testCases[i].TestNoSub);
        }
        if ( testCases[i].TestNo > 1 )
        {
            strcpy_s( TestData.grade, testCases[i].TestGrade );
            // テスト用の配列に格納
            testList.push_back(TestData);
        }

        // テスト対象の関数を実行
        TestRtn = testClassCalcGradeRank.CalcGradeRank(
            testList
        );

        // テスト対象の関数の実行結果を確認
        memset(TestResultBuf, 0x00, sizeof(TestResultBuf));
        if ( TestRtn == testCases[i].TestExpectedRtn ) {
            // 戻り値と期待する戻り値が一致
            sprintf_s(TestResultBuf, "%s:OK[TestRtn:% d    Expected : % d]\n", TestCaseName, TestRtn, testCases[i].TestExpectedRtn);
            Logger::WriteMessage(TestResultBuf);

            if ( TestRtn == COM_SUCCESS ) {
                // 戻り値が「COM_SUCCESS」
                // ランクの比較
                if ( (testCases[i].TestExpectedRank & testList.at(0).rank) == testCases[i].TestExpectedRank )
                {
                    // 算出結果と期待値が一致
                    sprintf_s(TestResultBuf, "%s:OK[Rank(Expected):0x%02X    Rank(Actual):0x%02X]\n",
                        TestCaseName,
                        testCases[i].TestExpectedRank,
                        testList.at(0).rank
                    );
                    Logger::WriteMessage(TestResultBuf);
                    TestOKCount++;
                }
                else
                {
                    // 算出結果と期待値が異なる
                    sprintf_s(TestResultBuf, "%s:NG[Rank(Expected):0x%02X    Rank(Actual):0x%02X]\n",
                        TestCaseName,
                        testCases[i].TestExpectedRank,
                        testList.at(0).rank
                    );
                    Logger::WriteMessage(TestResultBuf);
                    TestNGCount++;
                }
            }
            else {
                // 戻り値が「COM_FAILED」
                TestOKCount++;
            }
        }
        else {
            // 戻り値と期待する戻り値が異なる
            sprintf_s(TestResultBuf, "%s:NG[TestRtn:%d    Expected:%d]\n", TestCaseName, TestRtn, testCases[i].TestExpectedRtn);
            Logger::WriteMessage(TestResultBuf);
            TestNGCount++;
        }
    }
    sprintf_s(TestResultBuf, "テスト結果[OK:%3d 件    NG:%3d 件]\n", TestOKCount, TestNGCount);
    Logger::WriteMessage(TestResultBuf);
}
試験実施
実施手順

ファイル読み書き機能、成績ランク算出機能それぞれでテストを行います。 テストの実行方法は以下の通りです。

  1. Visual Studioで[テスト]→[テストエクスプローラー]を選択し、ダイアログを開きます。

テスト用プロジェクト実施画面キャプチャ01

  1. そのダイアログ内の左端の2つのボタンのいずれかを選択し、テストを実行します。

テスト用プロジェクト実施画面キャプチャ02

実施結果

上記の手順で実施した結果は以下の通りです。

  • ファイル読み書き機能

テスト用プロジェクト実践結果画面キャプチャ01

  • 成績ランク算出機能

テスト用プロジェクト実践結果画面キャプチャ02

試験成績記入

試験実施後、出力ウィンドウに表示された結果を確認して以下の様に試験結果を記入します。

テスト用プロジェクト成績記入画面キャプチャ01

テスト用プロジェクト成績記入画面キャプチャ02

障害起票

今回試験を実施してNGを2件確認しました。NGが出た場合は、その内容をドキュメントに記録しておきます。 大抵の場合はエクセル形式で障害管理帳が用意されています。 現場によってはRedmineで障害を管理している所もあります。 どんな形式にせよ、障害を記録する時は他の人が自分と同じ手順で試験を実施した時、 同じ障害が再現できるように書くことです。 これは障害対応後、同じ手順を踏んで障害が改善されたのを確認するためにも必要です。 記録する時は以下の項目を書くことが多いです。

  1. 何を(障害を起こしたもの)
  2. どうした(障害を起こした手順)
  3. どうなった(障害の内容)
  4. 本来どうあるべき(障害が起きない時の内容)

今回の場合だと、以下の様に書きます。

  • ファイル読み書き機能

    1. ファイル読み書き機能
    2. 以下の手順を実施
      1. 結果ファイル(半角カンマ「,」無)をプログラムと同じ場所に格納
      2. 引数に結果ファイル名を指定してプログラムを実行
    3. 配列に設定したデータ(名前)が期待していたものと異なる
      • 配列の「name」メンバ変数に行データが設定される
    4. 配列には何も設定されない
  • 成績ランク算出機能

    1. 成績ランク算出機能
    2. 以下の手順を実施
      1. 試験の点数を「100.05」で配列に設定して、プログラムを実行
    3. 算出した成績ランクが期待していたものと異なる
      • 「E」と算出したいが、「A」が算出される
    4. 「E」が算出される
障害調査

障害の記録作業が完了したら、その障害の原因追究をしていきます。 他の人の障害調査を依頼された場合、まずは自分も同じ障害を再現出来るか確認します。 そこから開発ツールを使い、デバッグ&ステップ実行をしながら障害の原因箇所を見つけていきます。 今回の障害は以下の原因が考えられます。

  • ファイル読み書き機能の障害
    • 半角カンマ「,」が無い行データで半角カンマの検出をする時、検出無しにもかかわらず、配列にデータを格納していた
  • 成績ランク算出機能の障害
障害対応
機能 対応内容
ファイル読み書き機能 最初の検出で失敗した行データはスキップ
成績ランク算出 double型からstringに変換する時に絶対値0.001を足す

ソースコードの変更箇所は以下の通りです。

  • ファイル読み書き機能

テスト用プロジェクト障害対応画面キャプチャ01
* 成績ランク算出機能

テスト用プロジェクト障害対応画面キャプチャ02

上記の対応を追加したソースコードをビルドし、再度プログラム試験1を実施します。 * ファイル読み書き機能

テスト用プロジェクト障害対合後実施結果画面キャプチャ01
* 成績ランク算出機能

テスト用プロジェクト障害対合後実施結果画面キャプチャ02

障害対応前は2件の「NG」が出ていましたが、障害対応後は「NG」→「OK」に変わっています。 これでプログラム試験1の試験項目は全てパスしたことになりました。

プログラム試験2

プログラム試験1をパスしたので、次はプログラム試験2です。 プログラム試験2ではユーザーの要求通りのプログラムになっているかを確認します。 そのため、ユーザーと同じ条件でプログラムを実行していきます。 試験仕様書に記載した手順に従ってプログラムを実行した結果は以下の通りです。

プログラム試験2実施結果画面キャプチャ

それぞれの試験パターンでの結果ファイルの中身は以下の様になりました。

  • ケースNo.2

プログラム試験2(ケースNo.2)実施結果画面キャプチャ
* ケースNo.3(読み取り専用を解除)

プログラム試験2(ケースNo.3)実施結果画面キャプチャ
* ケースNo.4

プログラム試験2(ケースNo.4)実施結果画面キャプチャ
* ケースNo.5

プログラム試験2(ケースNo.5)実施結果画面キャプチャ

いずれも試験仕様書の「期待される出力結果」欄の通りになりました。 プログラム試験2では「NG」は出なかったです。 これでプログラム試験1、2の試験項目が全て「OK」になったので、 ユーザーの要求に応じた成績算出プログラムが完成しました。

最後に

ここまで長々と記事を書いてきました。 当初は1つの記事に収めるつもりでしたが、書きたいことがどんどん増えて気付けば4回も連載する形になりました。 元々このテーマを選んだのは、テスト作業に不慣れな人が少しでも参考になればと思っての事でした。 外の現場ではテスト作業から始めることが多く、その試験項目の設計作業を任されることも多々あるので、このシリーズの記事が役に立ってもらえれば幸いです。 大事なのは「ストーリー」が見えるようにすること。 設計~コーディング~試験の紐づきが自分以外の人にも見えるように試験項目を作るのがベストです。 そのために以下の点を意識するようにしてください。

  • 何が変わるのか(対応前後の動作等)
  • 着目点を整理する(正常系/異常系の時の動作等)

上記2点を押さえておけば、方向性の定まった試験項目を設計できるようになります。

成績ランク算出プログラムを設計・製造してみた(プログラム試験設計編)

はじめに

どうも、迷走星人です。 3回目はプログラム試験の設計部分の話を出来たらと思います。 尚、この段階では以下の工程が完了しているものとします。

  • プログラムの設計&レビュー
  • プログラムの実装&レビュー

プログラム試験1

プログラム試験1は、実装した関数単位の試験を行います。 今回は成績ランク算出プログラムで実装した以下の2つの機能が正しく動作するかを確認します。

  • ファイル読み書き機能の動作確認

  • 成績ランク算出機能の動作確認

よって、実装した関数が正常系、異常系の動作を持つ場合はそれら両方を試験する必要があります。 試験漏れを防ぐため、試験方針を設定し、その方針に基づいて試験項目を設計します。 尚、プログラム試験1は関数内部の処理を全て網羅するテストを行うために、「ホワイトボックステスト」と呼ばれる形式を採用します。

試験方針

コーディング作業が完了したら、コーディングが機能詳細仕様に基づいて実装できているかを確認していきます。 プログラム試験1では今回の開発で実装した機能が正しく動作することを確認したいので、以下の確認を試験方針に設定します。

  1. ファイル読み書き機能の動作確認
    • 正常系のパターンでファイルの読込/書込が仕様通りに動作することを確認する。
    • 異常系のパターンでファイルの読込/書込が仕様通りに動作することを確認する。
  2. 成績ランク算出機能の動作確認
    • 正常系のパターンで成績ランク算出が仕様通りに動作することを確認する。
    • 異常系のパターンで成績ランク算出が仕様通りに動作することを確認する。

試験要領

試験方針が決まったら試験要領を考えます。 試験要領では自分が試験を実施する時、どこに着目したかが大事になります。 試験要領を今回実装した機能で下表の通り振り分け、それぞれの要領でどのように確認していくかを整理します。

確認内容 試験要領
ファイルの読み書き機能の動作確認 試験要領1
成績ランク算出機能の動作確認 試験要領2

正常系/異常系それぞれのケースで実装した機能が正しく動作することを確認するために、機能詳細仕様で書いた処理フローを活用します。 機能詳細仕様に書いた処理フロー内の制御(分岐、ループ等)箇所を着目点として洗い出し、洗い出した着目点の組み合わせを考えていきます。

試験要領1(ファイルの読み書き機能の動作確認)

試験項目設計のために、1回目の記事で書いたファイルの読み書き機能の処理フローで着目点になる箇所を確認します。

ファイル読み書き機能処理フロー図ファイル名がNULL?(着目点1)yes動作モードで分岐(着目点2)読込書込結果ファイルを開く行データ取得行データ分割配列にデータ格納(点数)yes分割成功?(着目点6)行データループ(着目点4)noファイルオープン失敗?(着目点3)yes結果ファイルを閉じる結果ファイルを開く配列データ取得結果ファイルにデータ(点数、ランク)書込配列データループ(着目点5)noファイルオープン失敗?(着目点3)yes結果ファイルを閉じる戻り値を返す

フロー図から、ファイル読み書き機能は以下の項目を着目点として考えることが出来ます。

着目点No. 内容 制御パターン
1 ファイル名 分岐
2 動作モード 分岐
3 ファイルオープン 分岐
4 行データループ ループ
5 配列データループ ループ
6 分割結果 分岐

上の表で「制御パターン」が「分岐」に属する着目点の場合、分岐の条件を満たすケース(真; TRUE)と満たさないケース(偽; FALSE)の2パターンが考えられます。 例えば、着目点No.1(項目:ファイル名)の場合、以下の2パターンのケースを確認します。

  • ファイル名がNULLである(分岐条件の内容に対して「真(TRUE)」)
    • 戻り値「COM_FAILED(-1)」を返して処理を終了
  • ファイル名がNULLでない(分岐条件の内容に対して「偽(FALSE)」)
    • 次の処理に移行

また、「制御パターン」が「ループ」に属する着目点の場合は、ループ内のケースとループ外のケースの2パターンが考えられます。 着目点No.4(項目:行データループ)の場合、以下の2パターンのケースを確認します。

  • 行データの末尾に到達していない(ループ条件の内容に対して「真(TRUE)」)
    • ループ内の処理を実施する
  • 行データの末尾に到達した(ループ条件の内容に対して「偽(FALSE)」)
    • ループを抜けて、ループ外の処理に移行

以上の様にそれぞれの着目点について試験で確認するパターンを洗い出し、必要な試験パターンを設計していきます。

試験パターンを設計するために、以下の様な表を用意します。

着目点 試験パターン
⇒パターン内容 [1] [2]
着目点でのロジック
⇒パターン1 ---
⇒パターン2 ---

1列目には実装した機能での着目点の内容及び、その着目点で考えられるパターンを書き出します。 2列目以降は実施する試験がどの着目点のパターンと紐づくかを「〇」で表しています。 この様な表を活用し、必要な試験パターンを見積もっていきます。

着目点 試験パターン
⇒パターン内容 [1] [2] [3] [4] [5] [6] [7] [8]
ファイル名
⇒NULL --- --- --- --- --- --- ---
⇒Not NULL ---
動作モード
⇒読取 --- --- --- ---
⇒書込 --- --- --- --- ---
ファイルオープン
⇒失敗 --- --- --- --- --- ---
⇒成功 --- --- ---
行データループ
⇒ループ終了 --- --- --- --- --- --- ---
⇒ループ継続 --- --- --- --- --- ---
配列データループ
⇒ループ終了 --- --- --- --- --- --- ---
⇒ループ継続 --- --- --- --- --- --- ---
行データ分割
⇒失敗 --- --- --- --- --- --- ---
⇒成功 --- --- --- --- --- --- ---

表の見方ですが、試験パターン1の場合、ファイル名がNullの時点で処理を終了するので、着目点No.1だけが紐づきます。 一方、試験パターン[4]、[5]の場合は全ての着目点が紐づいた試験パターンになります。 上記の表から、ファイルの読み書き機能の場合、8パターンの試験を実施する必要がありそうです。 その内訳は

  • 読み書き共通で1パターン(試験パターン[1])
  • 読取で4パターン(試験パターン[2]~[5])
  • 書込で3パターン(試験パターン[6]~[8])

となります。

必要な試験パターンを見積もれたので、各試験パターンで期待する動作を整理していきます。

試験パターン 着目点No. 期待する動作
1 2 3 4 5 6 戻り値 試験成績の有無
(配列データ)
成績ランクの有無
(結果ファイル)
[1] NULL --- --- --- --- --- COM_FAILED
(失敗)
--- ---
[2] Not NULL 読取 失敗 --- --- --- COM_FAILED
(失敗)
--- ---
[3] Not NULL 読取 成功 ループ
終了
--- --- COM_SUCCESS
(成功)
---
[4] Not NULL 読取 成功 ループ
継続
--- 失敗 COM_SUCCESS
(成功)
---
[5] Not NULL 読取 成功 ループ
継続
--- 成功 COM_SUCCESS
(成功)
---
[6] Not NULL 書込 失敗 --- --- --- COM_FAILED
(失敗)
--- ---
[7] Not NULL 書込 成功 --- ループ
終了
--- COM_SUCCESS
(成功)
---
[8] Not NULL 書込 成功 --- ループ
継続
--- COM_SUCCESS
(成功)
---

各試験パターンの期待する動作から、ファイル読み書き機能の試験パターンは正常系(戻り値が「COM_SUCCESS」)が5パターン、異常系(戻り値が「COM_FAILED」)が3パターンになります。 上記の表からファイルの読み書き機能の試験要領が完成しました。 後はこの試験要領を元に、具体的な値を用いてファイルの読み書き機能の試験項目を設計していきます。

試験要領2(成績ランク算出機能の動作確認)

成績ランク算出機能もファイルの読み書き機能と同様に試験要領を完成させていきます。 まずは、成績ランク算出機能の処理フロー処理フローで着目点になる箇所を確認します。

成績ランク算出機能処理フロー図配列がNULL?(着目点1)yes配列データ(点数)取得データの「型」で分岐(着目点2)整数 or 実数それ以外読み取った点数(着目点3)成績ランク:「A」成績ランク:「B」成績ランク:「C」成績ランク:「D」成績ランク:「E」配列データ格納(ランク:「E」)配列ループ(着目点1で確認するので、この箇所は着目点にならない)戻り値を返す90点以上100点以下 その他75点以上90点未満60点以上75点未満0点以上60点未満

フロー図から、ファイル読み書き機能は以下の項目を着目点として考えることが出来ます。

着目点No. 内容 制御パターン
1 配列 分岐
2 データの「型」 分岐
3 読み取った点数 分岐

成績ランク算出機能の着目点が確認出来たので、必要な試験パターンを見積もっていきます。

着目点 試験パターン
⇒パターン内容 [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12]
配列
⇒NULL(空) --- --- --- --- --- --- --- --- --- --- ---
⇒Not NULL
(空でない)
---
データの「型」
⇒整数 --- --- --- --- --- --- ---
⇒実数 --- --- --- --- --- --- ---
⇒上記以外 --- --- --- --- --- --- --- --- --- --- ---
読み取った点数
⇒90点以上
100点以下
--- --- --- --- --- --- --- --- --- ---
⇒75点以上
90点未満
--- --- --- --- --- --- --- --- --- ---
⇒60点以上
75点未満
--- --- --- --- --- --- --- --- --- ---
⇒0点以上
60点未満
--- --- --- --- --- --- --- --- --- ---
⇒上記以外 --- --- --- --- --- --- --- --- --- ---

上記の表から、成績ランク算出機能の場合、12パターンの試験を実施する必要がありそうです。 その内訳は

  • 配列が空の状態で1パターン(試験パターン[1])
  • 整数値で5パターン(試験パターン[2]~[6])
  • 実数値で5パターン(試験パターン[7]~[11])
  • 整数値、実数値以外で1パターン(試験パターン[12])

となります。

必要な試験パターンを見積もれたので、各試験パターンで期待する動作を整理していきます。

試験パターン 着目点No. 期待する動作
1 2 3 戻り値 成績ランク
[1] NULL --- --- COM_FAILED
(失敗)
---
[2] Not NULL 整数 90点以上
100点以下
COM_SUCCESS
(成功)
A
[3] Not NULL 整数 75点以上
90点未満
COM_SUCCESS
(成功)
B
[4] Not NULL 整数 60点以上
75点未満
COM_SUCCESS
(成功)
C
[5] Not NULL 整数 0点以上
60点未満
COM_SUCCESS
(成功)
D
[6] Not NULL 整数 0~100点の
範囲外
COM_SUCCESS
(成功)
E
[7] Not NULL 実数 90点以上
100点以下
COM_SUCCESS
(成功)
A
[8] Not NULL 実数 75点以上
90点未満
COM_SUCCESS
(成功)
B
[9] Not NULL 実数 60点以上
75点未満
COM_SUCCESS
(成功)
C
[10] Not NULL 実数 0点以上
60点未満
COM_SUCCESS
(成功)
D
[11] Not NULL 実数 0~100点の
範囲外
COM_SUCCESS
(成功)
E
[12] Not NULL その他 --- COM_SUCCESS
(成功)
E

各試験パターンの期待する動作から、成績ランク算出機能の試験パターンは正常系(戻り値が「COM_SUCCESS」)が11パターン、異常系(戻り値が「COM_FAILED」)が1パターンになります。 これで成績ランク算出機能の試験要領も完成しました。 ただし、試験パターン[7]~[11]に関して、今回開発するプログラムは小数点第1位までを有効にした実数値を扱うので、試験項目では小数点第2位の四捨五入を考慮して試験項目を設計します。 また、試験パターン[12]では、半角の「数字列」以外の文字列(全角文字、特殊文字等)を入力データとして試験で動作確認をしていきます。

試験項目

試験要領が完成したら、いよいよ試験項目の設計に入ります。 試験項目では試験要領での着目点に基づいた具体的な値を入力し、試験実施後、期待通りの動作をしているかを整理します。 大抵の現場では試験項目はエクセルドキュメントで設計します。 なので、このプログラムでもエクセルで試験項目を設計していきます。 今回の試験項目は以下のフォーマットで設計します。

ケースNo. 分類 操作方法 テスト確認項目 テスト日 結果
各試験項目に割り振られた試験番号 ・機能名
・正常系/異常系
具体的な実施方法 試験実施時に確認する内容 試験実施日 試験良否結果

試験項目の設計例は以下の様になります。

  • ファイル読み書き機能

    プログラム試験1試験項目設計例(ファイル読み書き機能)

  • 成績ランク算出機能

    プログラム試験1試験項目設計例(成績ランク算出機能)

上記の様に、試験項目では1行毎に確認する内容を1つ書いていきます。 なので、1つの試験項目で複数の確認事項がある時はケースNo.1-3、1-4の様に「テスト項目」の欄は1行毎に書き、分類や操作方法等、一括りに出来るところはセルを結合しています。 また、成績ランク算出機能の試験項目は隣に下図の様な試験パターンのイメージを添えておきます。

プログラム試験1試験イメージ例(成績ランク算出機能)

試験項目の近くに試験パターンのイメージがあると、レビューも進みやすくなります。

プログラム試験2

プログラム試験2では、1回目の記事で書いた以下の要求内容が満たせているかを確認します。

プログラム試験2要求内容確認

この時点でプログラム試験1で実装した機能は機能単体で設計通りに動作することを確認した、という前提になります。 よって、プログラム試験2はプログラム試験1で確認した機能を嚙み合わせた試験を設計していきます。 プログラム試験2はプログラムの内部構造(ここでは関数)には関知せず、入力と出力の対応が正しいことを確認するために、「ブラックボックステスト」と呼ばれる形式を採用します。 プログラム試験2もプログラム試験1と同様、先に試験方針、試験要領を設定してから試験項目を設計していきます。

試験方針

プログラム試験2の試験方針は、1回目の記事で書いた、要求内容を参考に設定します。 ユーザーの要求内容を満たしているかを確認したいので、同じく1回目の記事で書いた、運用フロー図を活用します。

プログラム運用フロー①:結果ファイルをプログラムがあるフォルダに格納する②:プログラムを実行する第1引数:結果ファイル名③:結果ファイルから成績を読み取る④:成績ランクを算出する⑤:プログラムが算出した成績ランクを結果ファイルに追記する⑥:結果ファイルを確認するyes成績ランク追記成功?(着目点4)noyes成績ランク算出成功?(着目点3)noyesファイル読み取り成功?(着目点2)noyesプログラムと結果ファイルは同じ場所に設置?(着目点1)no

運用フロー図から、以下の着目点を洗い出しました。

着目点No. 内容 制御パターン
[1] 結果ファイルの格納場所 分岐
[2] ファイル読み取り結果 分岐
[3] 成績ランク算出結果 分岐
[4] 成績ランク追記結果 分岐

これらの着目点を元に、プログラム試験2の試験方針を以下の通り設定します。

  • 成績ランク算出プログラム動作確認
    • 以下の点に着目して、プログラムが期待通りに動作することを確認する。
      [1] プログラム引数(結果ファイル名)指定有無
      [2] ファイル読み取り結果
      [3] 成績ランク算出結果
      [4] 成績ランク追記結果

試験要領

試験方針が決まったので、この方針に基づいて試験要領を設定します。 上記の着目点の組み合わせて試験を実施した時、プログラムに期待する動作を整理します。

着目点 試験パターン
⇒パターン内容 [1] [2] [3] [4] [5]
結果ファイルの格納場所
⇒プログラムと異なる --- --- --- ---
⇒プログラムと同じ ---
ファイル読み取り結果
⇒失敗 --- --- --- ---
⇒成功 --- ---
成績ランク算出結果
⇒失敗 --- --- --- ---
⇒成功 --- --- ---
成績ランク追記結果
⇒失敗 --- --- --- ---
⇒成功 --- --- --- ---

試験パターンが見積もれたので、各試験パターンで期待する動作を整理していきます。

試験パターン 着目点No. 期待する動作
1 2 3 4 標準出力 結果ファイル
[1] --- --- --- ファイルがプログラムと
同じ場所にありません。
---
[2] 失敗 --- --- ファイル読込に失敗しました。 成績ランク追記無
[3] 成功 失敗 --- 成績ランク算出に失敗しました。 成績ランク追記無
[4] 成功 成功 失敗 ファイル書込みに失敗しました。 成績ランク追記無
[5] 成功 成功 成功 大化技術通信用プログラム終了 成績ランク追記有

試験項目

試験要領が完成したら、次は試験項目の設計に入ります。 プログラム試験1の時と同じく、試験要領での着目点に基づいた具体的な値を入力します。 試験実施後、プログラムの出力結果が期待通りの出力になっているかを整理します。 プログラム試験2の試験項目は以下のフォーマットで設計します。

ケースNo. シナリオ 試験の流れ 期待される出力結果 テスト日 結果
各試験項目に割り振られた試験番号 試験で確認したいプログラムの運用フロー 試験手順 試験実施後に確認する内容 試験実施日 試験良否結果
  • 「シナリオ」には各試験パターンに紐づく着目点の組み合わせを書いていきます。
  • 「試験の流れ」には実際の運用を想定した、プログラムの運用手順を書いていきます。
  • 「期待される出力結果」には試験要領でまとめた、期待される標準出力と結果ファイルの内容を書いていきます。

試験項目の設計例は以下の様になります。

プログラム試験2試験項目設計例

プログラム試験1でもそうでしたが、試験項目中の文言の表記は試験仕様書内で統一するようにします。 一つの仕様書内で同じものを別々に言い表している時、レビューアーは混乱し、レビューに時間が掛かる要因になります。 また、時間を置いて後で見返す時、自分で書いたにも関わらず、自分自身でも分からなくなる時があります。 それらを避けるためにも、試験仕様書中の文言の表記には注意する必要があります。

レビュー

試験項目が設計出来たら、ソースコードと同じくレビューアーにレビューしてもらいます。 レビューで指摘事項があれば、それを試験項目に反映し、再度レビューしてもらいます。 この作業をレビューアーからフォローが貰えるまで繰り返します。

次回予告

ここまでプログラム試験設計について発表しました。
次回は以下のタイトルで続きを発表して行こうと思います。

  • 成績ランク算出プログラムを設計・製造してみた(プログラム試験実践編)