大化通信

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

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

レビュー

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

次回予告

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

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

Excelの使い勝手が向上するRelaxTools Addinの便利な機能

こんにちは。大化社員の星月です。

今回はExcelの使い勝手が向上するアドインRelaxToolsAddinの便利な機能を個人的にピックアップしてご紹介します。

RelaxToolsAddinとは?

まず最初にRelaxToolsAddinとはExcelに250を超える機能を追加するマクロを体系化し、独自のリボンから各機能を呼び出せるようにするアドインです。

RelaxTools Addinをダウンロードできる窓の社のHPのURLと私が参考にさせていただいたインストール方法が書かれているサイトのURLを下記に記載致します。

【窓の社のHP】

https://forest.watch.impress.co.jp/library/software/relaxtools/

【参考にさせていただいたインストール方法が書かれているサイト】 https://software.opensquare.net/relaxtools/downld/install/

全てのシートでA1セルを選択した状態で保存できる機能

まず紹介させていただく機能は全てのシートでA1セルを選択した状態で保存できる機能です。 Excelを使用して仕事をしていると全てのシートでA1セルを選択して保存してほしいなどと言われた経験があるかもしれません。

しかしシート数が3シート程ならまだしも10シートを超えてしまうと1シートずつ作業するのも面倒だと思います。そんな気にこの機能が役に立ちます。RelaxToolsタブの下記の画像場所をクリックするだけで全てのシートでA1セルを選択した状態で保存までしてくれます。

十字カーソルの表示

次に紹介させていただく機能は十字カーソルの表示です。

この機能はExcelで表を使用している時に表が大きくなっていくと今カーソルがどのデータを示しているのかが分からず困ってしまう時に役に立ちます。

RelaxToolsタブの下記の画像場所をクリックすると選択しているセルの行と列を緑色で見えやすくしてくれますし、行と列の番号も表示してくれます。

Excel ファイルのGrep検索

最後に紹介させていただく機能はExcel ファイルのGrep検索です。 この機能は大量のExcelファイルから特定の情報を一度に探さなくてはいけない時に役に立ってくれます。

使い方としてはまずRelaxToolsタブの置換/検索/修飾の▼をクリックし、ExcelファイルのGrep(マルチプロセス)版をクリックします。

すると以下の起動中ウィンドウが表示され

しばらくすると起動中ウィンドウが消えてGrepウィンドウが表示されます。

※注意点として起動中ウィンドウが消えてもGrepウィンドウが表示されない方は、Excelの後ろを確かめてみてください。GrepウィンドウがExcelの後ろに隠れてしまうことが良くあります。

あとは検索条件に探したい情報を入れてGrepボタンを押せばGrep結果を一覧表で出力してくれます。

結果の一覧表がこちらです。

D列のセル/シェイプはリンクになっているのでヒットしたファイルを開くこともできます。

以上で、私が個人的にピックアップしたRelaxToolsAddinの便利な機能をご紹介させていただきました。

私が紹介させていただいた機能以外にも様々な機能があるのでぜひダウンロードして試してみてください。

【参考・引用元のURL】 【窓の社のHP】 https://forest.watch.impress.co.jp/library/software/relaxtools/ 【参考にさせていただいたインストール方法が書かれているサイト】 https://software.opensquare.net/relaxtools/downld/install/

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

はじめに

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

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

開発環境

今回のプログラム開発の環境を以下にまとめます。

ツール一覧 用途
Visual Studio 2019 Commnuity ソースコードの編集
・プログラムのデバッグ
・プログラムテスト
WinMerge 差分確認
TortoiseSVN バージョン管理

いずれのツールもプログラム開発をする時に開発者が重用しているソフトになりますので、各ツールの詳細はこの記事では省略します。
一応、それぞれの公式HPを以下に掲載しておきます。

開発準備

  1. レポジトリの作成 開発するプロジェクトのソースコードのバージョン管理のためのレポジトリを作成します。 任意の空のフォルダを作成し、その下でTortoiseSVNの「ここにレポジトリを作成」を選択します。 レポジトリを作成すると、下図のようなフォルダ・ファイルが生成されます。

  2. チェックアウト レポジトリが作成出来たら、次は開発するプログラム用にレポジトリをチェックアウトします。 開発用のフォルダを用意し、その下に1.で作成したレポジトリをチェックアウトします。

  3. プロジェクトの作成 チェックアウトが出来たら、開発用のプロジェクトを作成します。 今回の開発では空のプロジェクトを作成し、その中にソースコード等を追加していきます。

コーディング

開発準備が整ったら、いよいよコーディング作業に入ります。 ここからは実際にプログラムの機能を実現するためにソースコードを追加・編集していきます。 尚、ソースコードを追加・編集した時はその都度SVNにコミットし、変更履歴として残していくようにします。 後になって手戻り作業が発生した時、残していた履歴を辿ってリカバリ出来るようにするためです。 コミットする目途としては、

  • 新しいファイル・関数の追加
  • ロジックの大きな変更

を行った時等です。勿論、自分の好みのタイミングでコミットしても問題ありません。 今回のプログラムでは以下のソースコードを追加・編集していきます。

ソースコード ファイル名称 内容
main.cpp プログラム本体ファイル プログラム本体を実装する。
GetGradeRankCom.h 共通ヘッダーファイル プログラム共通で使用する定義、構造体を記載する。
ClassFileReadWrite.h ファイル読み書き機能クラスファイル(*.h) ファイル読み書き機能の関数を宣言する。
ClassFileReadWrite.cpp ファイル読み書き機能クラスファイル(*.cpp) ファイル読み書き機能の関数の実態部分を記載する。
ClassCalcGradeRank.h 成績ランク算出機能クラスファイル(*.h) 成績ランク算出機能の関数を宣言する。
ClassCalcGradeRank.cpp 成績ランク算出機能クラスファイル(*.cpp) 成績ランク算出機能の関数の実態部分を記載する。

変数定義

プログラムの中で使う定義値を以下の様に設定します。

定義名 定義値 用途
COM_SUCCESS 0 戻り値(成功)
COM_FAILED -1 戻り値(失敗)
COM_MODER 0 読込
COM_MODEW 1 書込

構造体宣言

プログラムの中で使う構造体を以下の様に設定しています。

structGradeRank
メンバー変数 データ型 用途
name char配列 名前
grade char配列 点数
rank char配列 成績

構造体のメンバ変数は全部char配列の形で持たせ、各機能の内部処理で必要に応じてキャストして使用していきます。

関数実装

定義値、構造体が設定できたら、次は関数の実装に入ります。 プログラム設計に書いた仕様に基づき、ソースコード上に実装していきます。

ファイル読み書き機能(関数名:FileReadWrite)
項目 名称 データ型 用途 IN・OUT
引数 1 file char * ファイルパス IN
2 mode int モード
・COM_MODER:読込
・COM_MODEW:書込
IN
3 list vector<structGradeRank> 配列データ
(名前、点数、ランク)
IN/OUT
戻り値 COM_SUCCESS int 成功 OUT
COM_FAILED 失敗
成績ランク算出機能(関数名:CalcGradeRank)
項目 名称 データ型 用途 IN・OUT
引数 1 list vector<structGradeRank> 配列データ
(名前、点数、ランク)
IN/OUT
戻り値 COM_SUCCESS int 成功 OUT
COM_FAILED 失敗
実際の実装(main.cpp)
/// <summary>
/// 成績ランク算出プログラムメイン関数
/// </summary>
/// <param name="argc">コマンドライン引数の数</param>
/// <param name="argv">コマンドライン引数(結果ファイル)</param>
/// <returns>戻り値(COM_SUCCESS:成功、COM_FAILED:失敗)</returns>
int main(int argc, char **argv)
{
    // ファイル名
    char FPath[BUFSIZE1024];   memset(FPath, 0x00, BUFSIZE1024);
    // 配列データ
    vector<structGradeRank> ArrGradeRank;    ArrGradeRank.clear();
    // 戻り値
    int Ret = COM_SUCCESS;

    cout << "大化技術通信用プログラム開始" << endl;

    // インスタンスを生成
    ClassFileReadWrite clsFileReadWrite;
    ClassCalcGradeRank clsCalcGradeRank;

    // カレントディレクトリパスを取得
    TCHAR DName[MAX_PATH];
    GetCurrentDirectory(MAX_PATH, DName);
    WideCharToMultiByte(CP_ACP, 0, DName, -1, FPath, BUFSIZE1024, NULL, NULL);
    cout << "カレントディレクトリ:"  << FPath << endl;

    // ファイルの存在チェック
    string FAbsPath = FPath;
    FAbsPath += "\\";
    FAbsPath += argv[1];   // 結果ファイル名
    struct stat statFile;
    cout << "FAbsPath:" << FAbsPath << endl;
    // 結果ファイルのステータス情報取得結果で判定
    if (stat(FAbsPath.c_str(), &statFile) != 0)
    {
        cout << "ファイルがプログラムと同じ場所にありません。" << endl;
        return COM_FAILED;
    }
    memset(FPath, 0x00, BUFSIZE1024);
    strcpy_s(FPath, FAbsPath.c_str());

    // ファイル読込
    Ret = clsFileReadWrite.FileReadWrite(FPath, COM_MODER, ArrGradeRank);
    if (COM_SUCCESS != Ret)
    {
        cout << "ファイル読込に失敗しました。" << endl;
        return Ret;
    }

    // 成績ランク算出
    Ret = clsCalcGradeRank.CalcGradeRank(ArrGradeRank);
    if (Ret != COM_SUCCESS)
    {
        cout << "成績ランク算出に失敗しました。" << endl;
        return Ret;
    }

    // ファイル書込
    Ret = clsFileReadWrite.FileReadWrite(FPath, COM_MODEW, ArrGradeRank);
    if (COM_SUCCESS != Ret)
    {
        cout << "ファイル書込みに失敗しました。" << endl;
        return Ret;
    }

    cout << "大化技術通信用プログラム終了" << endl;

    return Ret;
}
ソースコードの解説
  • 引数で結果ファイル名を指定し、GetCurrentDirectory関数でカレントディレクトリのパスを取得しています。
    ディレクトリのパスはTCHAR型で取得しているので、char型配列のFPathに設定するためにWideCharToMultiByte関数を呼出して設定しています。
  • カレントディレクトリのパスとコマンドライン引数で指定した結果ファイルを結合させ、stat関数を呼出して結果ファイルのステータス情報取得結果の判定でプログラムと同じディレクトリに格納されているか確認しています。
  • ファイル読み書き機能、成績ランク算出機能はクラスファイルで実装しているので、インスタンスを生成後に関数を呼び出しています。
実際の実装(ClassFileReadWrite.cpp)
/// <summary>
/// ファイル読み書き機能
/// </summary>
/// <param name="fname">結果ファイルのファイル名</param>
/// <param name="mode">モード(0:読込、1:書込)</param>
/// <param name="list">配列データ(点数, 成績ランク)</param>
/// <returns>戻り値(0:成功、-1:失敗)</returns>
int ClassFileReadWrite::FileReadWrite(char* file, int mode, std::vector<structGradeRank>& list)
{
    int i;
    char fname[BUFSIZE1024];
    std::string tmpBuf;

    // NULLチェック
    if (NULL == file)
    {
        return COM_FAILED;
    }
    strcpy_s(fname, BUFSIZE1024, file);

    if (COM_MODER == mode)
    {
        // 読込
        std::ifstream ifs;
        std::string r_ss;
        std::vector < std::string > vec;    // 切り出し文字列格納関数
        size_t current;                     // 文字列切り出し開始位置
        size_t found;                       // 文字列切り出し終了位置

        // ファイルオープン
        ifs.open(fname, std::ios::in);
        if (!ifs)
        {
            // ファイルオープン失敗
            return COM_FAILED;
        }

        // データ読み取り
        while (std::getline(ifs, tmpBuf))
        {
            if (tmpBuf.empty()) continue;
            structGradeRank data;
            memset(&data, 0x00, sizeof(structGradeRank));

            // 行データ分割
            // 文字列から半角カンマ「,」を検索し、見つかった箇所までの文字列を抜き出す
            vec.clear();
            current = 0;
            found = tmpBuf.find_first_of(',');
            while (current < found)
            {
                // 文字列切り出し
                std::string subStr(tmpBuf, current, found - current);
                vec.push_back(subStr);

                current = found + 1;
                found = tmpBuf.find_first_of(',', current);

                if (std::string::npos == found)
                {
                    found = tmpBuf.size();
                }
            }

            // 分割成功(リストの要素数が2以上)
            if (2 <= vec.size())
            {
                // リストに格納
                strcpy_s(data.name, vec.at(0).c_str());
                strcpy_s(data.grade, vec.at(1).c_str());
                list.push_back(data);
            }
        }

        // ファイルクローズ
        ifs.close();
    }
    else if (COM_MODEW == mode)
    {
        // 書込
        std::ofstream ofs;

        // ファイルオープン
        ofs.open(fname, std::ios::out);
        if (!ofs)
        {
            // ファイルオープン失敗
            return COM_FAILED;
        }

        // データ書込み
        for (i = 0; i < list.size(); i++)
        {
            ofs << list.at(i).name << "," << list.at(i).grade << "," << list.at(i).rank << std::endl;
        }

        // ファイルクローズ
        ofs.close();
    }
    else
    {
        return COM_FAILED;
    }

    return COM_SUCCESS;
}
ソースコードの解説
  • ファイルのオープンクローズは「open/close」関数を用いています。
  • 実装した関数の第2引数(変数名:mode)で読込/書込を制御するようにしています。
  • 読込時、ファイル中のデータは「getline」関数を使い、行単位でデータを取得しています。データが取れなくなれば、読込処理を終了します。
  • 読込時のファイル中の行方向の分割は半角カンマ「,」を区切り文字として以下の処理フローで行っています。区切り文字の見つけ方には「find_first_of」関数を使用しています。

行データ分割処理フロー図初期化現在位置(current=0)検出位置(found)を取得(find_first_of関数)文字列切り出し(subStr関数)現在位置更新検出位置(found)を取得(find_first_of関数)検出位置(found)を文字列のサイズに更新yes行の末尾に到達?検出ループリストに格納名前成績yes分割成功?

  • 書込時は、リストに格納したデータを「名前」「成績」「ランク」の順に半角カンマ「,」を間に挟みながら書込を行います。
実際の実装(ClassCalcGradeRank.cpp)
/// <summary>
/// 成績ランク算出機能
/// </summary>
/// <param name="list">配列データ(点数, 成績ランク)</param>
/// <returns>戻り値(0:成功、-1:失敗)</returns>
int ClassCalcGradeRank::CalcGradeRank(std::vector<structGradeRank>& list)
{
    int i;

    // 配列チェック
    if (0 == list.size())
    {
        return COM_FAILED;
    }

    // 成績ランク算出
    for (i = 0; i < list.size(); i++)
    {
        std::string strVal = list.at(i).grade;
        try
        {
            // string->doubleに変換
            double dVal = stod(strVal);

            if ((90 <= dVal) && (dVal <= 100))
            {
                list.at(i).rank = 'A';
            }
            else if ((75 <= dVal) && (dVal < 90))
            {
                list.at(i).rank = 'B';
            }
            else if ((60 <= dVal) && (dVal < 75))
            {
                list.at(i).rank = 'C';
            }
            else if ((0 <= dVal) && (dVal < 60))
            {
                list.at(i).rank = 'D';
            }
            else
            {
                list.at(i).rank = 'E';
            }
        }
        catch (...)
        {
            list.at(i).rank = 'E';
        }
    }
    
    return COM_SUCCESS;
}
ソースコードの解説
  • 配列リストがNULL(サイズが0)の時、成績ランク機能は処理を中断し、戻り値「COM_FAILED」を返すようにします。
  • 成績ランク算出機能のループ処理の中で、string型からdouble型に変換する時、0~100点の範囲内にある実数の場合は値に応じて成績ランクを算出します。それ以外は全て例外処理で成績ランク「E」を付けるように実装しています。

コードレビュー

ソースコードの実装が一通り完了したら、ソースコードのレビューをしてもらいます。 レビュー時はソースコード実装時にコミットしていた変更分、あるいは、変更前後のソースコードを比較してその差分を説明しながらレビューアーに説明していきます。 そこでレビューアーから指摘を受けた場合は、その指摘事項を記録し、レビュー後ソースコードに反映します。 大抵は専用のレビューシートが用意され、指摘事項はそこに記述されていきます。 指摘事項を全て反映した後、再レビューの依頼を発行し、指摘事項の反映を確認してもらえたらシートにレビューアーからのフォロー(承認)を記載してもらいます。

次回予告

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

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