大化通信

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

PHPの忘れがちな関数備忘録

こんにちは。大化社員のにっしーです。

今回は、よく忘れがちで何回も検索してしまう
よく使うPHPの関数をまとめてみました。

文字列を数値に変換する

intvalを使用する

文字列を整数値に変換するためにはintval関数を使用します。
例えば、以下のようなコードで使用することができます。

$str = "123";
$int = intval($str);
echo $int;

この場合、文字列の「123」が整数の「123」に変換され、変数$intに代入されます。

floatvalを使用する

文字列をfloatに変換するためにはfloatval関数を使用します。
例えば、以下のようなコードで使用することができます。

$str = "123";
$float = floatval($str);
echo $float;

この場合、文字列の「123」がfloatの「123」に変換され、変数$floatに代入されます。


数値を文字列に変換する

strvalを使用する

数値を文字列に変換するためにはstrval関数を使用します。
例えば、以下のようなコードで使用することができます。

$num = 10;
$str = strval($num);
echo $str;

この場合、数値の「10」が文字列の「10」に変換され、変数$strに代入されます。


文字列(数列)を日付に変換する

strtotimeを使用する

文字列(数列)を日付に変換するためにはstrtotime()関数を使用します。
例えば、以下のようなコードで使用することができます。

$time_value = '201703220134';
echo date('Y年m月d日 H:i',strtotime($time_value));

strtotime() 関数は、英文形式の日時をUNIXタイムスタンプに変換することができます。
変換した値は date にセットして次のように表示します。

2017年03月22日 01:34

年月日をドット区切りにする場合は 'Y年m月d日 H:i' を 'Y.m.d H:i' のように編集してください。



文字列の検索をする

strposを使用する

strposは検索対象の文字列に検索する文字列が何文字目に存在するかを戻り値として返します。
検索した文字列が見つからなかった場合はFALSEを返します。

echo strpos('test@domain.com', '@'); // 4

結果は「4」が出力されます。0から数えられるため、"@"が5文字目にあるということが確認できます。

strstrを使用する

strstrはマッチした文字列より後ろの文字列を戻り値として返します。
検索した文字列が見つからなかった場合はFALSEを返します。

echo strstr('test@domain.com', '@'); // @domain.com

また、"@"より前の部分を表示したい場合は、以下のように第3引数にTRUEを渡します。

echo strstr('test@domain.com', '@', TRUE); // test

 

preg_match()を使用する

preg_match正規表現による検索を行います。
検索対象文字列から正規表現にあてはまる文字列が存在するかチェックします。
戻り値はマッチした文字列がある場合は「1」、ない場合は「0」を返します。

第3引数の配列には、正規表現にマッチした文字列が格納されます。第3引数以降は省略することができます。

if ( preg_match('/[^@]+$/', 'name@example.com', $matches) ) {
   echo $matches[0]; // example.com
}

実行結果は、@より後ろの文字が格納されるため、「example.com」と表示されます。


文字列を分割する

explodeを使用する

explodeは文字列を区切り文字で分割します。
explode関数での文字列分割の指定には、文字列を使います。

$fruits = 'apple orange lemon grape';
$result = explode(' ', $fruits);
var_dump($result);

これを実行すると下記のように表示されます。

array(4) {
  [0]=>
  string(5) "apple"
  [1]=>
  string(6) "orange"
  [2]=>
  string(5) "lemon"
  [3]=>
  string(5) "grape"
}

 

str_splitを使用する

str_splitは文字列を指定した文字数で分割します。
例えば、下記のように使います。

$fruits = 'apple orange banana';
$result = str_split($fruits, 5);
var_dump($result);

これを実行すると下記のように表示されます。

array(4) {
  [0]=>
  string(5) "apple"
  [1]=>
  string(5) " oran"
  [2]=>
  string(5) "ge ba"
  [3]=>
  string(4) "nana"
}

それぞれ、配列に5文字ずつ入っていますが、文字数が足りずに最後は4文字になっています。

str_split関数では、マルチバイト文字(日本語など)は使うことができません。
日本語を指定文字数で分割したい場合には、次項で紹介するmb_str_split関数を使います。

mb_str_splitを使用する

mb_str_splitはstr_split関数のマルチバイト対応バージョンです。
日本語もこちらの関数を使うことで、文字数を指定して分割できます。

$fruits = 'りんご オレンジ バナナ';
$result = mb_str_split($fruits, 3);
var_dump($result);

var_dumpで確認すると、下記のように文字化けせずに分割されていることが確認できました。

array(4) {
  [0]=>
  string(9) "りんご"
  [1]=>
  string(7) " オレ"
  [2]=>
  string(7) "ンジ "
  [3]=>
  string(9) "バナナ"
}

それぞれ、配列に5文字ずつ入っていますが、文字数が足りずに最後は4文字になっています。


今回のPHPの忘れがちな関数備忘録は以上になります。
読んでくださってありがとうございます。
参考になれば幸いです。



【参考・引用元のURL】
PHPで数値から文字列、文字列から数値の変換をしてみる。 | ちょっと便利なてっちーノート
PHP 文字列(数列)を日付に変換する方法 - by Takumi Hirashima
PHPで文字列を検索をする:strpos, strstr, preg_match | UX MILK
PHPで文字列を分割する5つの関数!(explodeなど) | コードライク

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点を押さえておけば、方向性の定まった試験項目を設計できるようになります。