软件结构与可测试性

软件的结构与可测试性关心密切,这不仅是指大粒度的软件结构,如模块化、接口等等,也包括很多细节,如环境变量与参数传递等。

环境变量以很多种方式存在,包括配置文件中的配置、Session中的变量、process.env以及保存在数据库等持久化机制中的变量,环境变量可以直接访问,或者通过在模块或函数中直接读取获得,比如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const config = require('config');
var MongoClient = require('mongodb').MongoClient;

const mongosettingsurl = process.env.mongo_url || config.mongo.url
const mongosettingsdb = process.env.mongo_db || config.mongo.db
const LeaveApply = require("../domain/leaveApply")

class MongoRepository {
constructor() {

}

//保存请假申请
async save(leaveApply) {
var mongodb = await MongoClient.connect(mongosettingsurl);
var dbase = mongodb.db(mongosettingsdb);

在模块代码中直接读取环境变量,这样做的问题是,当我们需要对这个模块进行测试时,需要使用环境变量传递测试数据库的地址:

1
2
3
4
5
6
7
8
9
before(async function () {
mongoServer = await MongoMemoryServer.create();
const mongoUri = mongoServer.getUri();
process.env.mongo_url=mongoUri
process.env.mongo_db="testdb"
const Repository=require('../../modules/repositories/mongoRepository')
repository=new Repository()
service=new LeaveApplyService(repository)
})

注意上面Repository的引入必须在process.env.mongo_url和 process.env.mongo_db的定义之后,不能在程序开始引入,这是因为模块引入时对内部变量进行了赋值:

1
2
const mongosettingsurl = process.env.mongo_url || config.mongo.url
const mongosettingsdb = process.env.mongo_db || config.mongo.db

如果需要mongosettingsurl使用process.env.mongo_url的值,process.env.mongo_url必须在这段代码之前赋值。
这种结构是很脆弱的,编写测试的人员必须充分了解mongoRepository的代码结构,否则无法进行测试。这还是在我们可以方便地重写process.env的情况下,如果只是从配置文件或数据库中直接读取,那么这段代码可能就无法测试。

因此,这种结构必须进行修改,方法很简单,将参数从构造函数注入:

1
2
3
4
5
6
7

class MongoRepository {
constructor(mongosettingsurl,mongosettingsdb) {
this.mongosettingsurl=mongosettingsurl
this.mongosettingsdb=mongosettingsdb
this.collectionName="leave_apply"
}

这样,MongoRepository不需要知道环境变量的存在,也不关心参数从何处而来,在测试时,也不需要修改的值,测试代码如下:

1
2
3
4
5
6
7
before(async function () {
mongoServer = await MongoMemoryServer.create();
const mongoUri = mongoServer.getUri();
const Repository=require('../../modules/repositories/mongoRepository')
repository=new Repository(mongoUri,"testdb")
service=new LeaveApplyService(repository)
})

代码中对Repository引入位置就不重要了,可以放在前面,因为潜在的耦合关系已经被去掉了。