发布者: Bean

在我们的代码里面,多处用到std::map<std::string, double>这样的类型。然而问题在于我们的GUI 往往需要将double的值显示出来,这里便涉及一个问题,需要显示多少位小数呢?事实上我们牵涉的情况多数属于nm的数值,因此三位小数是完全足矣!如果计及um的情况,三位是必需的。不过,从我们的代码,却看不出应该保留多少位。

最近有个ticket,是要求在condition setup时,只需保留3位小数。因此我大胆的写了一个类Milliuno,其作用之一是替代double,但只保留三位小数,其二是提供字符串格式接口,以保 证表示的一致。该类如下:

// milliuno.h
#ifndef _MILLIUNO_H_
#define _MILLIUNO_H_
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <map>
#include <cmath>
class Milliuno
{
public:
    Milliuno ( double m = 0.0 ) {
        m_d = static_cast<int>( m * 1E3 ) * 1E-3;
    }
    Milliuno (int m) : m_d ( m ) {}
    Milliuno ( const char* m ) {
        std::stringstream ss;
        ss << m;
        ss >> m_d;
        m_d = static_cast<int>( m_d * 1E3 ) * 1E-3;
    }
    Milliuno ( const std::string& m ) {
        std::stringstream ss;
        ss << m;
        ss >> m_d;
        m_d = static_cast<int>( m_d * 1E3 ) * 1E-3;
    }
    Milliuno ( const Milliuno& m ) : m_d( m.m_d ) {}
    operator double() const {
        return m_d;
    }
    operator std::string() const {
        std::ostringstream oss;
        oss << std::setprecision( 3 ) << std::fixed << m_d;
        return oss.str();
    }
    Milliuno& operator=( const Milliuno& m ) {
        if ( this == &m ) {
            return *this;
        }
        m_d = m.m_d;
        return *this;
    }
    Milliuno& operator=( double m ) {
        m_d = static_cast<int>( m * 1E3 ) * 1E-3;
        return *this;
    }
    Milliuno& operator=( const std::string& m ) {
        std::stringstream ss;
        ss << m;
        ss >> m_d;
        m_d = static_cast<int>( m_d * 1E3 ) * 1E-3;
        return *this;
    }
    Milliuno& operator=( const char* m ) {
        std::stringstream ss;
        ss << m;
        ss >> m_d;
        m_d = static_cast<int>( m_d * 1E3 ) * 1E-3;
        return *this;
    }
    Milliuno& operator*=( double m ) {
        m_d = static_cast<int>( m_d * m * 1E3 ) * 1E-3;
        return *this;
    }
    bool operator==( const Milliuno& rhs ) {
        return fabs( m_d - rhs.m_d ) < 1E-3;
    }
    bool operator==( const double& rhs ) {
        return fabs( m_d - rhs ) < 1E-3;
    }
    const std::string str() const {
        std::ostringstream oss;
        oss << std::setprecision( 3 ) << std::fixed << m_d;
        return oss.str();
    }
    double val() const {
        return m_d;
    }
    friend std::ostream& operator<<( std::ostream& os, const Milliuno& m ) {
        os << std::string( m );
        return os;
    }
private:
    double m_d;
};
#endif // _MILLIUNO_H_

现在要做的事情是,测试!

这次我不用google的测试框架,试下用Qt的QtTest, 直接上代码:

// milliuno.cpp
#include <QtTest/QtTest>
#include <milliuno.h>
class TestMilliuno: public QObject
{
    Q_OBJECT
private slots:
    void ctorDefault();
    void ctorfromString();
    void ctorfromCString();
    void ctorfromDouble();
    void copyAssignfromString();
    void copyAssignfromCString();
    void copyAssignfromDouble();
    void copyAssignfromMilliuno();
    void equalsToDouble();
    void equalsToMilliuno();
};
void TestMilliuno::ctorDefault()
{
    Milliuno milliuno;
    QCOMPARE(milliuno.val(), 0.0);
}
void TestMilliuno::ctorfromDouble()
{
    Milliuno milliuno (1.2345);
    QCOMPARE(milliuno.val(), 1.234);
}
void TestMilliuno::ctorfromString()
{
    Milliuno milliuno (std::string("1.2345"));
    QCOMPARE(milliuno.val(), 1.234);
}
void TestMilliuno::ctorfromCString()
{
    Milliuno milliuno ("1.2345");
    QCOMPARE(milliuno.val(), 1.234);
}
void TestMilliuno::copyAssignfromString()
{
    Milliuno milliuno;
    milliuno = std::string("1.2345");
    QCOMPARE(milliuno.val(), 1.234);
}
void TestMilliuno::copyAssignfromCString()
{
    Milliuno milliuno;
    milliuno = "1.2345";
    QCOMPARE(milliuno.val(), 1.234);
}
void TestMilliuno::copyAssignfromDouble()
{
    Milliuno milliuno;
    milliuno = 1.2345;
    QCOMPARE(milliuno.val(), 1.234);
}
void TestMilliuno::copyAssignfromMilliuno()
{
    Milliuno m (1.2345);
    Milliuno milliuno = m;
    QCOMPARE(milliuno.val(), 1.234);
}
void TestMilliuno::equalsToDouble()
{
    Milliuno milliuno (1.2345);
    QCOMPARE(milliuno == 1.2349, true);
    QCOMPARE(milliuno == 1.236, false);
}
void TestMilliuno::equalsToMilliuno()
{
    Milliuno milliuno (1.2345);
    Milliuno m (1.2349);
    Milliuno n (1.236);
    QCOMPARE(milliuno == m, true);
    QCOMPARE(milliuno == n, false);
}
QTEST_MAIN(TestMilliuno)
#include "milliuno.moc"

由于使用了Qt,不妨写个qmake project file, 当然用CMake也是可以的。

// milliuno.pro
QT += testlib
SOURCES = milliuno.cpp

直接执行

qmake
make

现在就跑测试了

./milliuno

输出如下

********* Start testing of TestMilliuno *********
Config: Using QTest library 4.8.5, Qt 4.8.5
PASS   : TestMilliuno::initTestCase()
PASS   : TestMilliuno::ctorDefault()
PASS   : TestMilliuno::ctorfromString()
PASS   : TestMilliuno::ctorfromCString()
PASS   : TestMilliuno::ctorfromDouble()
PASS   : TestMilliuno::copyAssignfromString()
PASS   : TestMilliuno::copyAssignfromCString()
PASS   : TestMilliuno::copyAssignfromDouble()
PASS   : TestMilliuno::copyAssignfromMilliuno()
PASS   : TestMilliuno::equalsToDouble()
PASS   : TestMilliuno::equalsToMilliuno()
PASS   : TestMilliuno::cleanupTestCase()
Totals: 12 passed, 0 failed, 0 skipped
********* Finished testing of TestMilliuno *********

在TestMilliuno的每个private slot 函数都是一项测试,对象范围包括构造函数,赋值函数,以及相等比较函数。这里只用到了QtTest的一个功能:QCOMPARE。

QtTest是个强大的框架,提供各种级别的测试。例如基准测试

void TestMilliuno::benchmark()
{
    Milliuno m;
    Milliuno n;
    QBENCHMARK {
       m = n.val();
    }
}

运行结果如下所示

RESULT : TestMilliuno::benchmark():
     0.0000073 msecs per iteration (total: 62, iterations: 8388608)

如果觉得默认的选项没有吸引力,例如迭代次数太少,可以试下./milliuno -help以获取帮助,这里就不细述了。

回顾上面的每个测试函数,里面的测试数据就只有一条,显然没法达到测试的目的。幸好QtTest支持DDT! 数据驱动测试DDT

以ctorfromDouble()为例吧,怎样给这个测试配置用例数据呢?很简单,添加成员函数ctorfromDouble_data()!

void TestMilliuno::ctorfromDouble_data()
{
    QTest::addColumn<double>("milliuno");
    QTest::addColumn<double>("result");
    QTest::newRow("1") << 0.0001 << 0.0;
    QTest::newRow("2") << 0.0009 << 0.0;
    QTest::newRow("3") << 0.0019 << 0.001;
    QTest::newRow("4") << 0.0029 << 0.002;
    QTest::newRow("5") << 5.0095 << 5.009;
    QTest::newRow("6") << 5.0096 << 5.009;
}

当然,我们需要重写ctorfromDouble()

void TestMilliuno::ctorfromDouble()
{
    QFETCH(double, milliuno);
    QFETCH(double, result);
    QCOMPARE(Milliuno(milliuno).val(), result);
}

容易看出,每条数据是按行存放的,而QTest::addColumn("milliuno");表示加一列,用于存放double类型,列的标签叫做“milliuno”。QTest::newRow("2")则是构建一行数据,行标签叫做“2”.

修改之后,测试的结果相应部分将变为

PASS   : TestMilliuno::ctorfromDouble(1)
PASS   : TestMilliuno::ctorfromDouble(2)
PASS   : TestMilliuno::ctorfromDouble(3)
PASS   : TestMilliuno::ctorfromDouble(4)
PASS   : TestMilliuno::ctorfromDouble(5)
PASS   : TestMilliuno::ctorfromDouble(6)

区别就是多了行标签!在测试结果出错的情况下,其作用不需多说!



-EOF-
睿初科技软件开发技术博客,转载请注明出处

blog comments powered by Disqus