测试具有完整代码覆盖率的简单记录器功能
Test simple logger functions with full code coverage
我正在使用 Chai、Sinon 和 Instanbul 来测试 NodeJS 应用程序。这是 Logger
代码:
import Debug, { IDebugger } from 'debug';
export default class Logger {
private readonly dbg: IDebugger;
constructor(name: string) {
this.dbg = Debug(name);
}
public log(str: string): void {
this.dbg(str);
}
}
这是我构建的开始测试:
import * as fs from 'fs';
import sinon from 'sinon';
import { expect } from 'chai';
import Logger from '../../src/utils/Logger';
import Debug from 'debug';
describe('Unit tests for Logger class', () => {
afterEach(() => {
sinon.restore();
});
describe('#constructor', () => {
it('should set logger', () => {
const setLoggerStub = sinon.stub(Logger.prototype, 'log');
const logExample: string = 'test logger'
const logger = new Logger(logExample);
logger.log('test logger')
sinon.assert.calledWithExactly(setLoggerStub, logExample);
});
});
});
代码覆盖率报告如下:
我不知道为什么 log
函数没有被测试,我该如何测试它并将构造函数的测试和 log
函数的测试分开?
我不确定要测试什么,因为 log
的唯一功能是将字符串传递给 debug
库。在这种情况下应该采取什么方法?
log
未包含在内,因为它 从未被调用过 ,您用 sinon.stub(Logger.prototype, 'log')
.
将其删除
您当前的实现很难测试,因为 Logger
与 Debug
的耦合度太高。在构造函数中创建依赖实例通常不是一个好主意。
如果反转依赖关系,使 Logger
的构造函数采用 IDebugger
而不是 string
名称:
import Debug, { IDebugger } from 'debug';
export default class Logger {
constructor(private readonly dbg: IDebugger) {}
public log(str: string): void {
this.dbg(str);
}
}
然后当你测试Logger
时,你可以很容易地注入一个测试替身:
it("debugs the input", () => {
const debug = sinon.stub();
const logger = new Logger(debug);
const message = "hello, world!";
logger.log(message);
sinon.assert.calledWithExactly(debug, message);
});
这意味着您不需要监视 Logger
本身的任何部分,让您可以更轻松地重构 class 内部。 Logger
class 现在只依赖于抽象的 IDebugger
接口,而不是具体的 Debug
实现。
创建具有 Debug
的 Logger
实例给定特定名称可以只是一个函数,或一个静态方法:
export default class Logger {
static withName(name: string) {
return new Logger(Debug(name));
}
/* ... */
}
我正在使用 Chai、Sinon 和 Instanbul 来测试 NodeJS 应用程序。这是 Logger
代码:
import Debug, { IDebugger } from 'debug';
export default class Logger {
private readonly dbg: IDebugger;
constructor(name: string) {
this.dbg = Debug(name);
}
public log(str: string): void {
this.dbg(str);
}
}
这是我构建的开始测试:
import * as fs from 'fs';
import sinon from 'sinon';
import { expect } from 'chai';
import Logger from '../../src/utils/Logger';
import Debug from 'debug';
describe('Unit tests for Logger class', () => {
afterEach(() => {
sinon.restore();
});
describe('#constructor', () => {
it('should set logger', () => {
const setLoggerStub = sinon.stub(Logger.prototype, 'log');
const logExample: string = 'test logger'
const logger = new Logger(logExample);
logger.log('test logger')
sinon.assert.calledWithExactly(setLoggerStub, logExample);
});
});
});
代码覆盖率报告如下:
我不知道为什么 log
函数没有被测试,我该如何测试它并将构造函数的测试和 log
函数的测试分开?
我不确定要测试什么,因为 log
的唯一功能是将字符串传递给 debug
库。在这种情况下应该采取什么方法?
log
未包含在内,因为它 从未被调用过 ,您用 sinon.stub(Logger.prototype, 'log')
.
您当前的实现很难测试,因为 Logger
与 Debug
的耦合度太高。在构造函数中创建依赖实例通常不是一个好主意。
如果反转依赖关系,使 Logger
的构造函数采用 IDebugger
而不是 string
名称:
import Debug, { IDebugger } from 'debug';
export default class Logger {
constructor(private readonly dbg: IDebugger) {}
public log(str: string): void {
this.dbg(str);
}
}
然后当你测试Logger
时,你可以很容易地注入一个测试替身:
it("debugs the input", () => {
const debug = sinon.stub();
const logger = new Logger(debug);
const message = "hello, world!";
logger.log(message);
sinon.assert.calledWithExactly(debug, message);
});
这意味着您不需要监视 Logger
本身的任何部分,让您可以更轻松地重构 class 内部。 Logger
class 现在只依赖于抽象的 IDebugger
接口,而不是具体的 Debug
实现。
创建具有 Debug
的 Logger
实例给定特定名称可以只是一个函数,或一个静态方法:
export default class Logger {
static withName(name: string) {
return new Logger(Debug(name));
}
/* ... */
}