背景
Linux下开发存储系统、网络库的时候会用到一系列Linux的系统调用,每一个系统调用都有一些出错的场景,有些场景很极端,比如内存使用达到上限、磁盘写满等,如果对其进行测试的话,很难去构造这样的一个场景,这个时候集成测试就显得力不存心了,只能靠单元测试来覆盖这些场景。现在的问题就是如何去mock这些系统调用,然后通过程序返回对应场景的错误码来模拟各种场景。也就是将对系统函数的依赖注入到程序中。
系统函数的依赖注入
目前实现系统函数的依赖注入的手段有很多,分为编译期注入,和运行期注入,至于什么是依赖注入可以参考知乎的一篇文章如何用最简单的方式解释依赖注入,下面介绍几种依赖注入的方法:
- 虚函数实现依赖注入(运行期注入)
使用传统的面向对象的手法,借助运行期的延迟绑定实现注入和替换,自己实现一个System接口类,把程序用到的系统调用都用虚函数封装一层,然后在调用的时候不直接调用系统调用,而是调用的System对应的方法。这样代码的主动权就交给了System接口类了。写单元测试的时候将这个System接口类替换成我们自己的mock对象就可以。完整的示例代码如下:
// system.h
class System {
public:
virtual int open(const char *path, int oflag, ...) = 0;
virtual ssize_t read(int fildes, void *buf, size_t nbyte) = 0;
virtual ssize_t write(int fildes, const void *buf, size_t nbyte) = 0;
virtual int close(int fildes) = 0;
static System* GetInstance();
static void set_instance(System* instance) {
instance_ = instance;
}
private:
static System* instance_;
};
// 具体的实现
class FileOps : public System {
public:
int open(const char *path, int oflag, ...) override;
ssize_t read(int fildes, void *buf, size_t nbyte) override;
ssize_t write(int fildes, const void *buf, size_t nbyte) override;
int close(int fildes) override;
static FileOps* GetInstance();
};
// system.cc
System* System::instance_ = nullptr;
// 默认实现是FileOps,mock的时候通过改变这个默认实现从而把主动权从默认实现转到了mock的实现
System* System::GetInstance() {
if (!instance_) {
instance_ = FileOps::GetInstance();
}
assert(instance_);
return instance_;