Google Testing/Mocking frameworkでCプログラム内のWindowsライブラリをモック

## はじめに
どうも！生産技術部のエンジニアです。今回はGoogle C++ Testing/Mocking frameworkを利用して、C言語で記述したプログラム内のWindowsライブラリをモックする方法を紹介します。作成したプログラムは[**lsg1050-controller**][link-controller]の`test`フォルダにあります。

[link-controller]:https://gitlab.com/iyama_yuki/lsg1050-controller

## 環境
* Google Test/Mock framework: 1.8.1
* Microsoft(R) C/C++ Optimizing Compiler: 19.23.28105.4 for x64

## 前提条件
Google Test/Mock frameworkの導入が実施済みであること。

## テスト（Google Testing/Mocking framework）を利用するメリット

私は生産技術部で検査装置の設計を行っています。検査装置は検査する対象の製品に対して、電圧・電流・波形・デジタル信号などを入力し、製品から出力された電圧・電流・波形・デジタル信号などを読み取ることで検査を行います。製品に対する入力および出力は、電圧電流発生器や計測器を制御することで行います。検査装置の開発段階では、これらの計測器が手元にない状態で、計測器の制御プログラムを作成する必要がある場合があります。こういったシチュエーションでも、柔軟に動作検証が出来るようにGoogle Testing/Mocking frameworkを利用します。また、ハードウェアが無くても動作可能である事は、新入社員のプログラム教育においても、ハードウェアを用意する必要がなく低コストで実施できる事に繋がります。

## Windowsライブラリをモック
`controller.c`をテスト対象のプログラムとして、電子負荷装置（[LSG 1050][link-lsg1050]）を制御するプログラムを作成します。`LSG 1050`はUSB CDCプロトコルで制御する仕様となっており、Windows環境に標準で入っている仮想COMポートを生成するドライバ([usbser.sys][link-subser])を用いることで制御する事ができます。

[link-lsg1050]:https://www.texio.co.jp/product/detail/159
[link-subser]:https://docs.microsoft.com/ja-jp/windows-hardware/drivers/usbcon/usb-driver-installation-based-on-compatible-ids
### テスト対象のプログラム
**write_line_lsg_1050**を作成します。この関数は、`LSG 1050`に対して制御コマンドを送信します。制御コマンドを送信するために、Windows用の制御APIである**PurgeComm**と**WriteFile**を利用します。

```C:src/controller.c
/************************/
/*!
 * @brief           電子負荷(LSG 1050)にコマンドを送信
 * @param [in]      hCom        A handle to the communications resource.
 * @param [in]      format      出力コマンド
 * @param [in]      ...         コマンドに設定する引数
 * @return          0           正常終了<br>
 *                  1           PurgeComm異常終了<br>
 *                  2           WriteFile異常終了
 * @ingroup         write_line_group
*/
/************************/
int write_line_lsg_1050(HANDLE *hCom,char *format, ... ){
	va_list va;
	DWORD dwBytesWrite = 10;
	BOOL fSuccess;
	char message[256];
	int messageLenght = 0;
	// Accesses variable-argument lists.
	va_start(va, format);
	// Write formatted output using a pointer to a list of arguments.
	vsprintf((char*)message, format, va);
	va_end(va);
#ifdef DEBUG_LSG_1050
	Print("message == %s",message);
#endif
	messageLenght = strlen(message);
	// EOF is "LF"
	message[messageLenght] = 0xa;
	// Clear send/recv buffer
	fSuccess = PurgeComm(*hCom, PURGE_RXABORT | PURGE_RXCLEAR);
	if(!fSuccess){
		Print("PurgeComm failed with error %d.\n",GetLastError());
		return (1);
	}
	// Send command to LSG1050
	fSuccess = WriteFile(
		*hCom,
		message,
		messageLenght +1,
		&dwBytesWrite,
		NULL
	);
	if(!fSuccess){
		Print("WriteFile failed with error %d.\n",GetLastError());
		return (2);
	}
	return (0);
}
```

### テストプログラム
Google Test/Mock frameworkで**PurgeComm**/**WriteFile**をモックします。これらの関数は、`Windows.h`ライブラリに存在し、C言語でこれらをモックするためには工夫がいります。`Windows.h`内の**PurgeComm**/**WriteFile**を直接利用せず、**PurgeCommMock**/**WriteFileMock**というMock関数を作成し利用します。
#### Mock関数
`Windows.h`をインクルードした後、**PurgeComm**/**WriteFile**を**PurgeCommMock**/**WriteFileMock**と定義し、`#include "../src/controller.c"`とすることで、`controller.c`を書き換えることなくMock関数を利用する事ができます。

```c++:test/test.cc　Mock関数

#include <windows.h>
#include <tchar.h>
#include <stdio.h>

#define PurgeComm PurgeCommMock
BOOL PurgeCommMock(
		HANDLE                  hFile,
		DWORD                   dwFlags
);
#define WriteFile WriteFileMock
BOOL WriteFileMock(
		HANDLE                  hFile,
		LPCVOID                 lpBuffer,
		DWORD                   nNumberOfBytesToWrite,
		LPDWORD                 lpNumberOfBytesWritten,
		LPOVERLAPPED            lpOverlapped
);

#include "../src/controller.c"
```
#### Mockクラス
Mockクラスは、[公式の「モッククラスを書く」][link-mockclass]に従い作成します。

```c++:test/test.cc　Mockクラス
class MockWin{
	public:
		MockWin() {}
		MOCK_METHOD2(PurgeCommMockA, BOOL(
				HANDLE                  hFile,
				DWORD                   dwFlags
				)
		);
		MOCK_METHOD5(WriteFileMockA, BOOL(
				HANDLE                  hFile,
				LPCSTR                  lpBuffer,
				DWORD                   nNumberOfBytesToWrite,
				LPDWORD                 lpNumberOfBytesWritten,
				LPOVERLAPPED            lpOverlapped
				)
		);
	private:
		GTEST_DISALLOW_COPY_AND_ASSIGN_(MockWin);
};
```
[link-mockclass]:http://opencv.jp/googlemockdocs/fordummies.html#fordummies-writing-mock-class

#### Mock関数・Mockクラスを接続
定義したMock関数の**PurgeCommMock**/**WriteFileMock**から、Mockクラスの**PurgeCommMockA**/**WriteFileMockA**を呼び出す事で、Google Mockを利用できるようになります。

```c++:test/test.cc　Mock関数・Mockクラスを接続
MockWin* winLib;

BOOL PurgeCommMock(
		HANDLE                  hFile,
		DWORD                   dwFlags
){
	return winLib->PurgeCommMockA(hFile, dwFlags);
}

BOOL WriteFileMock(
	HANDLE                      hFile,
	LPCVOID                     lpBuffer,
	DWORD                       nNumberOfBytesToWrite,
	LPDWORD                     lpNumberOfBytesWritten,
	LPOVERLAPPED                lpOverlapped
){
	return winLib->WriteFileMockA(hFile, (LPCSTR)lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
}
```
## 最後に
Google Testing/Mocking frameworkを利用し、C言語で記述したプログラム内のWindowsライブラリをモックしました。[GoogleTest Adapter][link-gtestadapter]を利用し、テスト結果を確認しました。正しくモック出来ているんではないでしょうか？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/451049/ab29e71b-f17c-49f5-3f5b-aac7c479945a.png)

[link-gtestadapter]:https://marketplace.visualstudio.com/items?itemName=ChristianSoltenborn.GoogleTestAdapter
## ご参考
* [入門ガイド - Google C++ Testing Frameworkをはじめよう](http://opencv.jp/googletestdocs/primer.html)
* [超入門編 - サルでも分かる Google C++ Mocking Framework](http://opencv.jp/googlemockdocs/fordummies.html#for-dummies)
* [USB serial driver (Usbser.sys)](https://docs.microsoft.com/ja-jp/windows-hardware/drivers/usbcon/usb-driver-installation-based-on-compatible-ids)
* [Googke Test Adapter](https://marketplace.visualstudio.com/items?itemName=ChristianSoltenborn.GoogleTestAdapter)