zoukankan      html  css  js  c++  java
  • 单元测试如何保证了易用的API

    一般而言TDD的好处是以输出为导向及早发现问题,以及方便重构(单元测试保证).
    我理解,还有一个比较重要的意义是: 客观上强制了程序员写出更加友好的接口 方便测试和联调.

    问题

    这里我以c++举例,需求就用最简单的: 实现一个单例类(比如说一个读取数据库的单例).
    好,拿到这个需求了,考虑到c++11之后static本身就是多线程安全的,所以实现一个单例模式就很简单了,如下:

    // 手打不保证编译ok
    class SingleDb {
    public:
        int getMoney(){...}
        SinlgeDb(const SingleDb &) = delete;
        void operator=(const SingleDb &) = delete;
        static SingleDb &get() {
            static SingleDb db;
            return db;
        }
    };

    ok, 单例类的主体工作基本上就完成了,代码中直接可以用SingleDb::get()就可以获得这个单例,再补充和业务相关的读取等成员函数即可.
    好了,本来这样就ok了,但是老板现在要求大家每个新功能都要求写单元测试,客户端程序员(这里的客户端指代的是使用这个单例模式的程序员)调用了这个API之后就不爽了,因为他想要对他自己的业务代码进行单元测试,97影院但是在读数据库的时候无法进行打桩测试。具体遇到的问题如下:

    struct Client{
     int doSth() const {
        SingleDb &db = SingleDb::get();
        if(db.getMoney() < 0) return -1;
      }
    };

    完全无法测试,因为我们在写单元测试的时候无法控制db.getMoney()的输出进行控制. 因为需要做如下改造:

    API适配

    数据库的对象不通过静态成员函数获取,而修改成注入的方式,这样方便构造输入.
    数据库单例类没有抽象基类,无法用构造类(或者说是Mock对象)替换, 需改改写成有继承体系的类.
    实现:
    针对以上两点,修改之后的样子

    struct DbBase {
        virtual int getMoney() = 0;
    };
    class SingleDb: public DbBase {
    // 没啥变化
    };
    
    //客户端
    struct Client {
        DbBase &db;
        explicit Client(DbBase &db): db(db) {}
        int doSth() const {
            if(db.getMoney() < 0) return -1;
        }
    };

    使用(单元测试)

    这样处理之后,单例类已经变成了一个更加易于单元测试的类了,以一个比较简单的单元测试框架作为例子给出(catch+fakeit)

    TEST_CASE("", "")
    {
        using namespace fakeit;
        Mock mock;
        when(Method(mock, getMoney())).Return(-1);
        Client c(mock.get());
        REQUIRE(c.doSth() == -1);
    }

    这实际上是为了单元测试而把API的接口改了,不仅仅是更加易于单元测试了. 更有意义的是,把接口提升至抽象类,以后想扩展实现类,直接就新增继承类即可,单元测试都不用动(因为传入的是抽象基类).
    或者哪一天用到的单元测试框架没人维护了需要切换单元测试代码,那业务代码根本也不需要动,因为抽象接口已经固定. 不用Mock框架,自己打个桩都能单元测试, 例子如下:

     
    // 构造打桩继承类
    struct DataBaseMock :www.97yingyuan.org public DbBase
    int getMoney() {return -1;}
    };
    
    // 测试
    ...
    DataBaseMock db;
    Client c(&db);
    REQUIRE(c.doSth() == -1);

    总结:

    实际上,撰写一个好的API的好处本身又是另外一个话题了(不仅仅有助于单元测试),但是TDD这个开发模式能够强迫程序员写出一个更加易用的API.

  • 相关阅读:
    C语言-typedef和#define
    C语言-指针
    C语言-数据结构-结构体
    数据结构笔记
    树的存储结构
    树的基本知识
    关于矩阵
    LaTex
    札记-碎碎念
    一个大神的个人博客 博客桌面风格很棒 二次元
  • 原文地址:https://www.cnblogs.com/tianshifu/p/7840979.html
Copyright © 2011-2022 走看看