GoogleTestで単体テスト自動化

###はじめに
GoogleTestを使ってCSVパーサクラスのテストをしてみる。

GoogleTestの詳細はこちらを参照。
公式
http://opencv.jp/googletestdocs/primer.html
インストール等
https://qiita.com/shohirose/items/30e39949d8bf990b0462

###ソースコード
```c++:CsvParser.hpp
#ifndef _CSVPARSER_HPP_
#define _CSVPARSER_HPP_

#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <time.h>
#include <vector>

namespace CsvParser {

class Row {

public:
  Row();
  ~Row();

public:
  // setter
  void push_back(const std::string &);

  // getter
  uint32_t size() const;
  template <typename T> const T getValue(uint32_t pos) const;

  // operator
  const std::string operator[](uint32_t) const;
  friend std::ostream &operator<<(std::ostream &os, const Row &row);
  friend std::ostringstream &operator<<(std::ostringstream &os, const Row &row);

private:
  std::vector<std::string> f_Values;
};

class Parser {

private:
  static const uint32_t RESERVE_ROW = 100000;

public:
  Parser(const char *, const char delim = ',');
  ~Parser();

public:
  bool parse();
  void parseHeader(std::string &);
  void parseContent(std::string &);
  bool checkContent() const noexcept;

  // gettters
  Row *getRow(uint32_t row) const;
  uint32_t countRow() const;
  uint32_t countColumn() const;
  std::vector<std::string> getHeader();
  const std::string getHeaderElement(uint32_t pos);
  const std::string &getFileName() const;
  const std::string dumpAllData() const noexcept;
  const std::string toString() const noexcept;

  // operator
  Row &operator[](uint32_t row) const;

private:
  const std::string f_FileName;
  const char f_Delim;
  bool f_IsParsed;
  std::vector<std::string> f_Header;
  std::vector<Row *> f_Content;
};
}

#endif /* _CSVPARSER_HPP_ */
```

```c++:CsvParser.cpp
#include "CsvParser.hpp"

namespace CsvParser {

/*
 *  Row class
 */

Row::Row() {}

Row::~Row() {}

void Row::push_back(const std::string &val) { this->f_Values.push_back(val); }

uint32_t Row::size() const { return this->f_Values.size(); }

template <typename T> const T Row::getValue(uint32_t pos) const {
  T retval;
  std::stringstream ss;
  ss << this->f_Values[pos];
  ss >> retval;
  return retval;
}

const std::string Row::operator[](uint32_t pos) const {
  if (this->f_Values.size() < pos) {
    return "";
  }
  return this->f_Values[pos];
}

std::ostream &operator<<(std::ostream &os, const Row &row) {
  for (uint32_t i = 0; i < row.f_Values.size(); i++)
    os << row.f_Values[i] << " | ";
  return os;
}

std::ostringstream &operator<<(std::ostringstream &oss, const Row &row) {
  for (uint32_t i = 0; i < row.f_Values.size(); i++)
    oss << row.f_Values[i] << " | ";
  return oss;
}

/*
 *  Parser class
 */

Parser::Parser(const char *fileName, const char delim)
    : f_FileName(fileName), f_Delim(delim), f_IsParsed(false) {
  this->f_Content.reserve(RESERVE_ROW);
}

Parser::~Parser() {
  for (Row *row : this->f_Content) {
    delete row;
  }
}

bool Parser::parse() {
  std::ifstream ifs(this->f_FileName.c_str());
  std::string line;
  bool IsParsedHeader = false;

  if (ifs.good()) {
    while (std::getline(ifs, line)) {
      if (line == "") {
        continue;
      }

      if (!IsParsedHeader) {
        this->parseHeader(line);
        IsParsedHeader = true;
      } else {
        this->parseContent(line);
      }
    }
  } else {
    std::cout << "Error! ";
    std::cout << this->f_FileName << "can not open.";
    std::cout << std::endl;
  }
  ifs.close();

  return this->checkContent();
}

void Parser::parseHeader(std::string &line) {
  boost::char_separator<char> sep(",");
  boost::tokenizer<boost::char_separator<char>> tokens(line, sep);
  for (std::string token : tokens) {
    this->f_Header.push_back(token);
  }
}

void Parser::parseContent(std::string &line) {
  boost::char_separator<char> sep(",");
  boost::tokenizer<boost::char_separator<char>> tokens(line, sep);
  Row *row = new Row();
  for (std::string token : tokens) {
    row->push_back(token);
  }
  this->f_Content.push_back(row);
}

bool Parser::checkContent() const noexcept {
  for (Row *row : this->f_Content) {
    if (this->f_Header.size() != row->size()) {
      return false;
    }
  }
  return true;
}

Row *Parser::getRow(uint32_t rowpos) const {
  if (this->f_Content.size() > rowpos) {
    return this->f_Content[rowpos];
  }
  return nullptr;
}

uint32_t Parser::countRow() const { return this->f_Content.size(); }

uint32_t Parser::countColumn() const { return this->f_Header.size(); }

std::vector<std::string> Parser::getHeader() { return this->f_Header; }

const std::string Parser::getHeaderElement(uint32_t pos) {
  if (this->f_Header.size() > pos) {
    return this->f_Header[pos];
  }
  return "";
}

const std::string Parser::dumpAllData() const noexcept {
  std::ostringstream oss;
  for (Row *row : this->f_Content) {
    oss << *row << std::endl;
  }
  return oss.str();
}

const std::string Parser::toString() const noexcept {
  std::ostringstream oss;
  oss << "FileName:[" << this->f_FileName << "]" << std::endl;
  oss << "Delimiter:[" << this->f_Delim << "]" << std::endl;
  oss << "NumOfColumn[" << this->countColumn() << "]" << std::endl;
  oss << "NumOfRow:[" << this->countRow() << "]" << std::endl;
  return oss.str();
}
}
```


####テストしてみる
####テストデータ
```c++:test.sv
a,b,c,d,e,f
1,2,3,4,5,6
11,22,33,44,55,66
```
```c++:test_error.csv
a,b,c,d,e,f,g
1,2,3,5
11,,55,66
```
###テストコード（お試し）
```c++:CsvParser_unittest.cpp
#include "gtest/gtest.h"
#define private public
#define protected public
#include "CsvParser.hpp"
#include "Defs.h"

class CsvParserTest : public ::testing::Test {
protected:
  virtual void SetUp() {}
  virtual void TearDown() {}
  CsvParser::Parser cp1 = CsvParser::Parser("test.csv", ',');
  CsvParser::Parser cp2 = CsvParser::Parser("notexist.csv", ',');
  CsvParser::Parser cp3 = CsvParser::Parser("test_error.csv", ',');

  void parseAll() {
    cp1.parse();
    cp2.parse();
    cp3.parse();
  }
};

TEST_F(CsvParserTest, parseFailed) {
  double d = 0.1;
  EXPECT_DOUBLE_EQ(d, 0.1);
  EXPECT_TRUE(d == 0.1);

  EXPECT_EQ(true, cp1.parse());
  EXPECT_EQ(false, cp2.parse());
  EXPECT_EQ(false, cp3.parse());
}

TEST_F(CsvParserTest, checkContent) {
  parseAll();
  EXPECT_EQ(true, cp1.checkContent());
  EXPECT_EQ(false, cp3.checkContent());
}

TEST_F(CsvParserTest, dumptest) {
  parseAll();
  EXPECT_EQ(2, cp1.countRow());
  EXPECT_EQ(6, cp1.countColumn());
  std::cout << "cp.Is" << cp1.f_IsParsed << std::endl;
  std::cout << "cp.Delim" << cp1.f_Delim << std::endl;
  std::cout << cp1.toString() << std::endl;
}

TEST_F(CsvParserTest, header) {
  parseAll();
  std::vector<std::string> v = cp1.getHeader();
  EXPECT_EQ("a", v[0]);
  EXPECT_EQ("b", v[1]);
  EXPECT_EQ("c", v[2]);
  EXPECT_EQ("d", v[3]);
  EXPECT_EQ("e", v[4]);
  EXPECT_EQ("f", v[5]);
}
```
###ビルド
GoogleTestのビルド済みのオブジェクトファイルにリンクする。

```c++:build.sh
export GTEST_DIR=/Users/<USERDIR>/googletest/googletest

g++ -I/opt/local/include -c CsvParser.cpp
g++ -I/opt/local/include -I${GTEST_DIR}/include -c CsvParser_unittest.cpp
g++ -I${GTEST_DIR}/include -lpthread -o CsvParser_test CsvParser.o Csvparser_unittest.o gtest_main.a
```

###実行
全てのテストケースで結果OK
<img width="771" alt="googletest_sample.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402891/14bba7a8-5113-b476-eaa7-c4aa1eea597a.png">

ちなみに失敗すると次のように赤く出力されます。（1ケース失敗）
<img width="712" alt="googletest_failed.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402891/619e3dbd-5a9e-ce2e-8fc1-3de11ddf69a8.png">