Testlink : テスト進捗をグラフ化する

Testlink の DB に記録されたテスト実施データからグラフを生成する手順について解説します。

# Testlink DB のアクセス

まず Testlink DB に接続する必要があります。
Testlink Root の config_db.inc に Testlink Database への接続情報が記載されています。

```config_db.inc.php
<?php
// Automatically Generated by TestLink Installer - Mon, 08 Dec 14 07:12:47 +0100
define('DB_TYPE', 'mysql');
define('DB_USER', 'root');
define('DB_PASS', 'root_pass');
define('DB_HOST', 'localhost:3306');
define('DB_NAME', 'testlink');
define('DB_TABLE_PREFIX', '');
?>

```

# グラフ化手順

テストの実行結果は Testlink DB の executions table にテスト計画毎に、実施したテストケースのID、実施日時、結果のステータスが記録されます。
executions table を検索し、開始日から終了日までの実行結果を、テスト計画に対し実施日毎に集計すれば、これを元にグラフを描画することができます。

### executions table の詳細：

|field|用途|参照先|
|-----------|:------------|:------------:|
|id|実行のID||
|build_id|ビルドのID|builds > id|
|tester_id|テスターのID||
|execution_ts|テストを実施した日時||
|status|実行結果（p/b/f…)||
|testplan_id|テスト計画のID|testplans > id|
|tcversion_id|テストケースのID|tcversions > id|
|tcversion_number|テストケースのバージョン|tcversions > Version|
|platform_id|プラットフォームのID|platforms > id|
|execution_type|実行タイプ||
|execution_duration|実行にかかった時間（分）||
|notes|テストの備考||

必要なのは execution_ts / status / testplan_id / tcversion_id です。

## 手順概要

```
1.	プロジェクトを選択する > testproject_id			
2.	選択したプロジェクトに属するテスト計画を選択 > testplan_id
3.	executions/testplan_id=id に記録されている初日と最終日を得る		
4.	Loop: {初日 → 最終実施日			
5.	　　execution table からテスト計画毎に初日→翌日＋＋の実施数を得る（テストケース毎に最新の結果を得る）			
6.	　　結果を１日毎の配列に入れていく(ステータス毎)			
7.	Loop:}			
	
8.	結果の配列を csv 化			
9.	グラフ描画パッケージ（pchart/highcharts等）にCSVのデータ列を渡す

```

# プロジェクト選択

プロジェクトを選択する php のサンプル
* プロジェクトIDは testprojects table から得ます。
* プロジェクト名は nodes_hierarchy table に入っています。
* DB_ACCESS は　PDO で MySQL DB に接続する Object 。
* executeSQL は prepare と execute をまとめて実施するメソッドです。 

```getproject.php
$db = new DB_ACCESS('testlink');
$dbh = $db->connect();
$actfile = getplan.php;
$sql = 'select tp.id as id, nh.name as name from testprojects tp, nodes_hierarchy nh
		where tp.id = nh.id order by tp.id desc;';
$res = $db->executeSQL($dbh,$sql,null);
?>
<form method="get" action="<?php echo $actfile; ?>">
<select name="proj" id="proj">
<?php
    While ($proj = $res -> fetch(PDO::FETCH_ASSOC)){
		print('<option value="'. $proj['id'].'">' . $proj['name'] . '</option>');      
    }
//	$dbh = null;
?>
</select>
<input type="submit" value="選択" />
</form>
    
```

# nodes_hierarchy table の構造

* nodes_hierarchy は Testlink のプロジェクト・テスト計画・テストスイート・テストケース等の階層構造を定義しているテーブルです。


|nodes_hierarchy|用途|参照先|
|-----------|:------------|:------------:|
|id|ノードのID||
|name|ノードの名前（プロジェクト名等）||
|parent_id|親ノードのID||
|node_type_id|ノードのタイプ|node_types > id|
|node_order|ツリーの階層の中での表示位置||

### node_type

|ID|Type|
|-----------|:------------|
|1|testproject|
|2|testsuite|
|3|testcase|
|4|testcase_version|
|5|testplan|
|6|requirement_spec|
|7|requirement|
|8|requirement_version|
|9|testcase_step|
|10|requirement_revision|
|11|requirement_spec_revision|
|12|build|
|13|platform|
|14|user|

# テスト計画の選択

テスト計画を選択する php のサンプル
* テスト計画ID は testplans table から得ます。
* テスト計画名は同様に nodes_hierarchy table に入っています。

```getplan.php
$pjid = $_GET['proj'];
$_SESSION['projectID'] = $proid;　// プロジェクトIDはセッション変数に入れる
$db = new DB_ACCESS('testlink'); 
$dbh = $db->connect();
$actfile = planProgress.php;
$sql = "select pl.id as id, nh.name as name from testplans pl, nodes_hierarchy nh 
	        where  pl.id = nh.id and nh.node_type_id=5 and pl.testproject_id=:pjid and pl.active = 1 order by name";

$res = $db->executeSQL($dbh,$sql,array(':pjid'=>$proid));
<font color="<?php echo 'midnightblue';?>"><br>テスト計画を選択</font><br>
<form method="get" action=<?php echo $actfile; ?>>
<select name="plan" id="plan">
<?php   
    while($plan = $res -> fetch(PDO::FETCH_ASSOC)) {
		print('<option value="' . $plan['id']. '">' . $plan['name'] . '</option>');      
    }
?>
</select>
<input type="submit" value="選択" />
```

# executions からテスト計画の初日と最終日を得る

```planProgress.php
$plid = $_GET['plan'];
$sqlD1 = "SELECT cast(substring(min(`execution_ts`),1,10) as date) Sdate FROM `executions` 
			WHERE `testplan_id` = ? ;";
$sqlD2 = "SELECT cast(substring(max(`execution_ts`),1,10) as date) Edate FROM `executions` 
			WHERE `testplan_id` = ? ;";
$stmtd1 = $db->executeSQL($dbh,$sqlD1,array($plid));
$resultd1 = $stmtd1->fetch(PDO::FETCH_ASSOC);
$stmtd2 = $db->executeSQL($dbh,$sqlD2,array($plid));
$resultd2 = $stmtd2->fetch(PDO::FETCH_ASSOC);

$startDay = $resultd1['Sdate'];
$endDay = $resultd2['Edate'];
```
# 集計用 SQL

SQL の例です。function にしています。
一つのテストケースについて最新の状態を得るため subquery を使います。
パラメータとして、テスト計画ID、開始日、ターゲット日を受け取ります。
標準以外のステータスを使ってる場合、[status = 'p'] の 'p' を標準外のステータスコードにすることで対応できます。

```sql.php
<?php
// トータルケース
function sql_execCount(){
//
    $sql="SELECT count(distinct(tcversion_id)) as eCase 
			FROM `executions` e 
			WHERE `testplan_id`=:plid and e.execution_ts >=:s_date and
			e.execution_ts <=:e_date and 
    		e.execution_ts = (SELECT MAX(x.execution_ts) 
				FROM  executions AS x WHERE e.tcversion_id= x.tcversion_id and x.testplan_id=:plid);";
	return $sql;
}
// 成功ケース
function sql_successCount(){
//
    $sql="SELECT count(distinct(tcversion_id)) as pCase 
			FROM `executions` e 
			WHERE `testplan_id`=:plid and e.execution_ts >=:s_date and
			e.execution_ts <=:e_date and (status = 'p') and 
    		e.execution_ts = (SELECT MAX(x.execution_ts) 
				FROM  executions AS x WHERE e.tcversion_id = x.tcversion_id and x.testplan_id=:plid);";
	return $sql;
}
// ブロックケース
function sql_blockCount(){
//
	$sql="SELECT count(distinct(tcversion_id)) as bCase 
			FROM `executions` e 
			WHERE `testplan_id`=:plid and e.execution_ts >=:s_date and
			e.execution_ts <=:e_date and (status = 'b') and 
    		e.execution_ts = (SELECT MAX(x.execution_ts) 
				FROM  executions AS x WHERE e.tcversion_id= x.tcversion_id and x.testplan_id=:plid);";
	return $sql;
}
// 失敗ケース
function sql_failCount(){
//
	$sql="SELECT count(distinct(tcversion_id)) as fCase 
			FROM `executions` e 
			WHERE `testplan_id`=:plid and e.execution_ts >=:s_date and
			e.execution_ts <=:e_date and status = 'f' and 
    		e.execution_ts = (SELECT MAX(x.execution_ts) 
				FROM  executions AS x WHERE e.tcversion_id= x.tcversion_id and x.testplan_id=:plid);";
	return $sql;
?>
```

# 配列・SQLの準備など

```.php
<?php
//HIGHCHARTSに渡す配列
$aPass = array();
$aBlock = array();
$aNG = array();

// SQL をセット
$sql_Total = sql_execCount(); //トータル実施数
$sql_OK = sql_successCount(); // OKケース
$sql_Block = sql_blockCount(); //　Blockケース
$sql_NG = sql_failCount(); // NGケース

//最初の日
$d1 = $startDay;
//次の日
$d2 = date("Y-m-d",strtotime("+1 day",strtotime($d1)));
$d3 = $startDay;

?>
```

# SQL を実行して日毎の結果を得る

１日ごとのデータ得て配列に入れていきます。
LOOP毎に翌日をセットし、最終日まで繰り返します。


```.php
<?php

while( $d2 <= $endDay ){

// 取得日　$d3 の次の日
   $dd = date("Y-m-d",strtotime("+1 day",strtotime($d3))) . " ";
//配列にDBからの日付を入れる   
   $aDates[] = substr($d3,05);

//Total		
		$stmt_Total = $db->executeSQL($dbh,$sql_Total,array(':plid'=>$plid,':s_date'=>$d1,':e_date'=>$dd));
		$result_Total = $stmt_Total->fetch(PDO::FETCH_ASSOC);
//OK
		$stmt_OK = $db->executeSQL($dbh,$sql_OK,array(':plid'=>$plid,':s_date'=>$d1,':e_date'=>$dd));
		$result_OK = $stmt_OK->fetch(PDO::FETCH_ASSOC);
//Block		
		$stmt_Block = $db->executeSQL($dbh,$sql_Block,array(':plid'=>$plid,':s_date'=>$d1,':e_date'=>$dd));
		$result_Block = $stmt_Block->fetch(PDO::FETCH_ASSOC);
//NG
		$stmt_NG = $db->executeSQL($dbh,$sql_NG,array(':plid'=>$plid,':s_date'=>$d1,':e_date'=>$dd));
		$result_NG = $stmt_NG->fetch(PDO::FETCH_ASSOC);

    $tExec = $result_Total['eCase']; //Total Count
    $tPass = $result_OK['pCase']; // OK
    $tBlock = $result_Block['bCase']; // Block	
	$tNG = $result_NG['fCase']; // NG

// 配列にデータを格納していく
	$aPass[] = $tPass;
    $aBlock[] = $tBlock;
	$aNG[] = $tNG;

//$d3 を翌日
//$d2 を翌々日にセット

    $d3 = $d2;
    $d2 = strtotime($d2);
    $d2 = date("Y-m-d",strtotime("+1 day",$d2));
}
?>
```

# グラフ化(HighChartsでの例）

端折ってるのでこのままでは動作しないかも…

```.php
<?php

// Hichart に渡すデータの作成 : 配列を CSV 化
$chart_pass     = implode(",", $aPass);
$chart_block	= implode(",", $aBlock);
$chart_NG       = implode(",", $aNG);
$chart_dates    = "'" . implode("','", $aDates) . "'";

$wd = $conf['gp']['width']; //グラフの幅 pix
$hi = $conf['gp']['height']; //グラフの高さ 
highchart_graphHeader($wd,$hi,"Testlinkテスト進捗");

drawGraph($chart_dates,$chart_NG,$chart_block,$chart_pass);

?>
```

```.js
<?php
function drawGraph($chart_dates,$chart_NG,$chart_block,$chart_pass){
?>
<script type="text/javascript">
var chart;

function draw() {

      // グラフオプションを指定
      var options = {
              // 出力先を指定
              chart :{renderTo : "container",
			  backgroundColor: 'wheat',
			  plotBackgroundColor: 'lightyellow',
			  borderWidth: 1
			  },

              // タイトルを指定
              title : {text: "テスト進捗"},
              // x軸のラベルを指定
              xAxis : {title: {text: "Day"},
			  categories: [<?php echo $chart_dates;?>]
			  			  },
			  
              // y軸のラベルを指定
              yAxis : {title: {text: "テストケース"},
			  	stackLabels: {
              	enabled: true,
				style: {
                    fontWeight: 'bold',
                    color: (Highcharts.theme && Highcharts.theme.textColor) || 'gray'
                	}
				}
			  },
		  
            plotOptions: {
            area: {
                stacking: 'normal',
                dataLabels: {
                    disabled: true,
                    color: (Highcharts.theme && Highcharts.theme.dataLabelsColor) || 'white',
                    style: {
                        textShadow: '0 0 3px black'
                    }
                }
            }
        },
				
              // データ系列を作成
              // データ系列を作成
              series: [
					  {type: 'column', name: "NG", data: [<?php echo $chart_NG;?>],color: 'red'},
                      {type: 'column', name: "Block", data: [<?php echo $chart_block;?>],color: 'lime'},
                      {type: 'column', name: "OK", data: [<?php echo $chart_pass;?>],color: 'dodgerblue'}
              ]

	}

      // グラフを作成
      chart = new Highcharts.Chart(options);

}

// ページがロードされた後に、グラフを出力する
document.body.onload = draw();

</script>
<?php
}
?>
```


# 参考：Table構造

## testprojects
|testprojects|用途|参照先|
|-----------|:------------|:------------:|
|id|テスㇳプロジェクトのID|nodes_hierarchy > id|
|notes|テストプロジェクトの説明||
|active|有効||
|color|プロジェクトの背景色（無効）|htmlカラーコード|
|option_reqs|要件管理||
|option_priority|テスト優先度||
|	option_automation|自動テスト||
|options|拡張機能||
|prefix|テストプロジェクトの Prefix||
|tc_counter|テストケースの外部IDの最大値<br>（テストケース数ではない）||
|is_public|公開||
|issue_tracker_enabled|バグトラッカー有効||
|reqmgr_integration_enabled|||
|api_key|||

## testplans
|testplanss|用途|参照先|
|-----------|:------------|:------------:|
|id|テスト計画のID|executions > testplan_id|
|testptoject_id|テスト計画が属するテストプロジェクトのID| testprojects > id|
|notes|テスト計画の説明||
|active|有効||
|is_open|||
|is_public|公開||
|api_key|||

# グラフの例

![Testlinkテスト進捗-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3114660/26b277b5-4de8-50ee-d8f6-311f21a6690a.jpeg)