1. C++测试框架
1.1. cppunit
cppunit,这是一个与JUnit类似的框架,这个框架很陈旧了,并且有着一些缺点,例如一些类可以消失,一些类名应该修改,一些宏定义应该修改,帮助很少很乱等,这里就不再一一赘述。
1.2. cxxtest
因为cppunit有着一系列的缺点,cppunit的鼻祖之一重写了一套C/C++单元测试框架,这就是cxxtest。与cppunit相比,cxxtest具有如下一些优点:
不需要RTTI(运行时间类型信息);不需要成员模板功能;
不需要异常处理;
不需要任何外部函数库(包括内存管理、文件/控制台的输入/输出和图形库等);
它完全是作为一套头文件的集合而进行发布的。
另外由于cppunit带有Make文件, 所以只能用在主要的操作系统中,而应用到不常见操作系统中源代码及Make文件修改的工作量就会很大。cxxtest不带Make文件, 所以也可用于其他操作系统中,具有更好的可移植性和可用性。Gtest虽然也使用Makefile文件,但是通过libtool动态加载来实现,可以支持Supports Linux, Windows, Mac OS, 以及其他操作系统。
cxxtest也有一些缺点,例如需要用到perl或者python对测试代码的头文件进行文法扫描,生成可执行代码,准备工作比较麻烦。并且cxxtest的绝大多数功能都可以使用gtest来完成,所以一般情况下就更加推荐gtest。
1.3. GTest
gtest是一个跨平台(Liunx、Mac OS X、Windows、Cygwin、Windows CE and Symbian)的C++测试框架,有google公司发布。gtest测试框架是在不同平台上为编写C++测试而生成的。
这是Google的开源C++单元测试框架,是遵循 New BSD License (可用作商业用途)的开源项目。据说google内部的大多数C++代码都已经使用这个测试框架进行单测,gtest 可以支持绝大多数大家所熟知的平台。
2. 安装GTest
下载源码
git clone https://github.com/google/googletest
3. 使用GTest
3.1. 代码
gtest_test.cpp文件
注意:如果有命令空间,main函数要放到命名空间外面,这里也不建议使用main函数
#include "gtest/gtest.h"
#include "fun.h"
TEST(fun, add) {
EXPECT_EQ(1, add(2,-1));
EXPECT_EQ(5, add(2,3));
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
?fun.h文件
#ifndef _FUN_H_
#define _FUN_H_
#include <string.h>
#include <algorithm>
// Returns n! (the factorial of n). For negative n, n! is defined to be 1.
int Factorial(int n);
// Returns true if n is a prime number.
bool IsPrime(int n);
// A simple string class.
class MyString {
private:
const char* c_string_;
const MyString& operator=(const MyString& rhs);
public:
// Clones a 0-terminated C string, allocating memory using new.
static const char* CloneCString(const char* a_c_string);
// C'tors
// The default c'tor constructs a NULL string.
MyString() : c_string_(NULL) {}
// Constructs a MyString by cloning a 0-terminated C string.
explicit MyString(const char* a_c_string) : c_string_(NULL) {
Set(a_c_string);
}
// Copy c'tor
MyString(const MyString& string) : c_string_(NULL) {
Set(string.c_string_);
}
// D'tor. MyString is intended to be a final class, so the d'tor
// doesn't need to be virtual.
~MyString() { delete[] c_string_; }
// Gets the 0-terminated C string this MyString object represents.
const char* c_string() const { return c_string_; }
size_t Length() const {
return c_string_ == NULL ? 0 : strlen(c_string_);
}
// Sets the 0-terminated C string this MyString object represents.
void Set(const char* c_string);
};
// Queue is a simple queue implemented as a singled-linked list.
//
// The element type must support copy constructor.
template <typename E> // E is the element type
class Queue;
// QueueNode is a node in a Queue, which consists of an element of
// type E and a pointer to the next node.
template <typename E> // E is the element type
class QueueNode {
friend class Queue<E>;
public:
// Gets the element in this node.
const E& element() const { return element_; }
// Gets the next node in the queue.
QueueNode* next() { return next_; }
const QueueNode* next() const { return next_; }
private:
// Creates a node with a given element value. The next pointer is
// set to NULL.
explicit QueueNode(const E& an_element) : element_(an_element), next_(NULL) {}
// We disable the default assignment operator and copy c'tor.
const QueueNode& operator = (const QueueNode&);
QueueNode(const QueueNode&);
E element_;
QueueNode* next_;
};
template <typename E> // E is the element type.
class Queue {
public:
// Creates an empty queue.
Queue() : head_(NULL), last_(NULL), size_(0) {}
// D'tor. Clears the queue.
~Queue() { Clear(); }
// Clears the queue.
void Clear() {
if (size_ > 0) {
// 1. Deletes every node.
QueueNode<E>* node = head_;
QueueNode<E>* next = node->next();
for (; ;) {
delete node;
node = next;
if (node == NULL) break;
next = node->next();
}
// 2. Resets the member variables.
head_ = last_ = NULL;
size_ = 0;
}
}
// Gets the number of elements.
size_t Size() const { return size_; }
// Gets the first element of the queue, or NULL if the queue is empty.
QueueNode<E>* Head() { return head_; }
const QueueNode<E>* Head() const { return head_; }
// Gets the last element of the queue, or NULL if the queue is empty.
QueueNode<E>* Last() { return last_; }
const QueueNode<E>* Last() const { return last_; }
// Adds an element to the end of the queue. A copy of the element is
// created using the copy constructor, and then stored in the queue.
// Changes made to the element in the queue doesn't affect the source
// object, and vice versa.
void Enqueue(const E& element) {
QueueNode<E>* new_node = new QueueNode<E>(element);
if (size_ == 0) {
head_ = last_ = new_node;
size_ = 1;
} else {
last_->next_ = new_node;
last_ = new_node;
size_++;
}
}
// Removes the head of the queue and returns it. Returns NULL if
// the queue is empty.
E* Dequeue() {
if (size_ == 0) {
return NULL;
}
const QueueNode<E>* const old_head = head_;
head_ = head_->next_;
size_--;
if (size_ == 0) {
last_ = NULL;
}
E* element = new E(old_head->element());
delete old_head;
return element;
}
// Applies a function/functor on each element of the queue, and
// returns the result in a new queue. The original queue is not
// affected.
template <typename F>
Queue* Map(F function) const {
Queue* new_queue = new Queue();
for (const QueueNode<E>* node = head_; node != NULL; node = node->next_) {
new_queue->Enqueue(function(node->element()));
}
return new_queue;
}
private:
QueueNode<E>* head_; // The first node of the queue.
QueueNode<E>* last_; // The last node of the queue.
size_t size_; // The number of elements in the queue.
// We disallow copying a queue.
Queue(const Queue&);
const Queue& operator = (const Queue&);
};
// A simple monotonic counter.
class Counter {
private:
int counter_;
public:
// Creates a counter that starts at 0.
Counter() : counter_(0) {}
// Returns the current counter value, and increments it.
int Increment();
// Prints the current counter value to STDOUT.
void Print() const;
};
// The prime table interface.
class PrimeTable {
public:
virtual ~PrimeTable() {}
// Returns true iff n is a prime number.
virtual bool IsPrime(int n) const = 0;
// Returns the smallest prime number greater than p; or returns -1
// if the next prime is beyond the capacity of the table.
virtual int GetNextPrime(int p) const = 0;
};
// Implementation #1 calculates the primes on-the-fly.
class OnTheFlyPrimeTable : public PrimeTable {
public:
virtual bool IsPrime(int n) const {
if (n <= 1) return false;
for (int i = 2; i*i <= n; i++) {
// n is divisible by an integer other than 1 and itself.
if ((n % i) == 0) return false;
}
return true;
}
virtual int GetNextPrime(int p) const {
for (int n = p + 1; n > 0; n++) {
if (IsPrime(n)) return n;
}
return -1;
}
};
// Implementation #2 pre-calculates the primes and stores the result
// in an array.
class PreCalculatedPrimeTable : public PrimeTable {
public:
// 'max' specifies the maximum number the prime table holds.
explicit PreCalculatedPrimeTable(int max)
: is_prime_size_(max + 1), is_prime_(new bool[max + 1]) {
CalculatePrimesUpTo(max);
}
virtual ~PreCalculatedPrimeTable() { delete[] is_prime_; }
virtual bool IsPrime(int n) const {
return 0 <= n && n < is_prime_size_ && is_prime_[n];
}
virtual int GetNextPrime(int p) const {
for (int n = p + 1; n < is_prime_size_; n++) {
if (is_prime_[n]) return n;
}
return -1;
}
private:
void CalculatePrimesUpTo(int max) {
::std::fill(is_prime_, is_prime_ + is_prime_size_, true);
is_prime_[0] = is_prime_[1] = false;
for (int i = 2; i <= max; i++) {
if (!is_prime_[i]) continue;
// Marks all multiples of i (except i itself) as non-prime.
for (int j = 2*i; j <= max; j += i) {
is_prime_[j] = false;
}
}
}
const int is_prime_size_;
bool* const is_prime_;
// Disables compiler warning "assignment operator could not be generated."
void operator=(const PreCalculatedPrimeTable& rhs);
};
#endif//_FUN_H_
3.2. 编译
如果test中写了main函数,则编译比较简单;如果test中没有写main函数,则编译时需要链接gtest_main
3.3. 运行
make test CTEST_OUTPUT_ON_FAILURE=TRUE GTEST_COLOR=TRUE
4. GTest的一些基本概念
要测试一个类或函数,我们需要对其行为做出断言。当一个断言失败时,Google Test会在屏幕上输出该代码所在的源文件及其所在的位置行号,以及错误信息。也可以在编写断言时,提供一个自定义的错误信息,这个信息在失败时会被附加在Google Test的错误信息之后。
断言常常成对出现,它们都测试同一个类或者函数,但对当前功能有着不同的效果。ASSERT_*版本的断言失败时会产生致命失败,并结束当前函数。EXPECT_*版本的断言产生非致命失败,而不会中止当前函数。通常更推荐使用EXPECT_*断言,因为它们运行一个测试中可以有不止一个的错误被报告出来。但如果在编写断言如果失败,就没有必要继续往下执行的测试时,你应该使用ASSERT_*断言。 因为失败的ASSERT_*断言会立刻从当前的函数返回,可能会跳过其后的一些的清洁代码,这样也许会导致空间泄漏。
5. GTest的断言
5.1. 布尔值检查
Fatal assertion | Nonfatal assertion | Verifies | ASSERT_TRUE(condition); | EXPECT_TRUE(condition); | condition is true | ASSERT_FALSE(condition); | EXPECT_FALSE(condition); | condition is false |
5.2. 数值型数据检查
Fatal assertion | Nonfatal assertion | Verifies | ASSERT_EQ(expected, actual); | EXPECT_EQ(expected, actual); | expected == actual | ASSERT_NE(val1, val2); | EXPECT_NE(val1, val2); | val1 != val2 | ASSERT_LT(val1, val2); | EXPECT_LT(val1, val2); | val1 < val2 | ASSERT_LE(val1, val2); | EXPECT_LE(val1, val2); | val1 <= val2 | ASSERT_GT(val1, val2); | EXPECT_GT(val1, val2); | val1 > val2 | ASSERT_GE(val1, val2); | EXPECT_GE(val1, val2); | val1 >= val2 |
5.3. 字符串比较
Fatal assertion | Nonfatal assertion | Verifies | ASSERT_STREQ(expected_str, actual_str); | EXPECT_STREQ(expected_str, actual_str); | 两个C字符串有相同的内容 | ASSERT_STRNE(str1, str2); | EXPECT_STRNE(str1, str2); | 两个C字符串有不同的内容 | ASSERT_STRCASEEQ(expected_str, actual_str); | EXPECT_STRCASEEQ(expected_str, actual_str); | 两个C字符串有相同的内容,忽略大小写 | ASSERT_STRCASENE(str1, str2); | EXPECT_STRCASENE(str1, str2); | 两个C字符串有不同的内容,忽略大小写 |
5.4. 异常检查
Fatal assertion | Nonfatal assertion | Verifies | ASSERT_THROW(statement, exception_type); | EXPECT_THROW(statement, exception_type); | statement throws an exception of the given type | ASSERT_ANY_THROW(statement); | EXPECT_ANY_THROW(statement); | statement throws an exception of any type | ASSERT_NO_THROW(statement); | EXPECT_NO_THROW(statement); | statement doesn't throw any exception |
5.5. 浮点型检查
Fatal assertion | Nonfatal assertion | Verifies | ASSERT_FLOAT_EQ(expected, actual); | EXPECT_FLOAT_EQ(expected, actual); | the two float values are almost equal | ASSERT_DOUBLE_EQ(expected, actual); | EXPECT_DOUBLE_EQ(expected, actual); | the two double values are almost equal |
?对相近的两个数比较:
Fatal assertion | Nonfatal assertion | Verifies | ASSERT_NEAR(val1, val2, abs_error); | EXPECT_NEAR(val1, val2, abs_error); | the difference between val1 and val2 doesn't exceed the given absolute error |
5.6. 此外还有类型检查、谓词检查等
6. 注意事项
6.1. 生成链接库时不要编UT文件
很多工程都包含UT文件,当这个工程编译链接库时,注意要把UT文件从链接库中剔除,然后单独把UT文件编成可执行文件。
6.2. 不建议使用main函数
如果链接了gtest_main,则不需要写main函数,建议使用这种方式。
参考文献
|