问题描述
我有一个简单的程序:
function counterPattern() {
for (let i = 1; i <= 100; i++) {
if (i % 3 === 0 && i % 5 === 0) {
console.log("c");
} else if (i % 3 === 0) {
console.log("a");
} else if (i % 5 === 0) {
console.log("b");
}
}
}
counterPattern();
有人要求我逐步改善上述功能:
- 减少执行时间,并且
- 为此编写单元测试
这使我改变了功能使其适合编写单元测试。
以下是该方向的尝试:
function counterPattern(){
const pattern = []; // for making it unit-testable; returning pattern will help with snapshotting/matching without losing the order;
for ( let i=1; i<=100 ; i++ ) {
const rem3 = i % 3; // separate calculation to + the speed; same for next-line;
const rem5 = i % 5;
if ( rem3 === 0 && rem5 === 0 ) {
pattern.push('c');
}else if ( rem3 === 0 ){
pattern.push('a');
}else if ( rem5 === 0){
pattern.push('b');
}
}
// returning array would allow the caller to format the pattern
return pattern;
}
let result = counterPattern();
console.log(result);
我的问题
如何用不同的方式编写原始程序:
- 以便更快地执行,
- 并且可以进行单元测试;
您对我的尝试有何评论?
解决方法
1。速度
关于算法的速度,有以下注意事项:
- 大约一半的循环迭代将导致无输出。这可以改善;
- 有一个模式会每15次重复一次,因为3和5的最小公倍数是15。
因此您可以将该模式硬编码到代码中,并执行与1..15范围相对应的输出,然后重复:
function counterPattern() {
for (let i = 0; i < 90; i += 15) {
console.log("a"); // 3 + i
console.log("b"); // 5 + i
console.log("a"); // 6 + i
console.log("a"); // 9 + i
console.log("b"); // 10 + i
console.log("a"); // 12 + i
console.log("c"); // 15 + i
}
// The remainder: values between 90 and 100:
console.log("a"); // 93
console.log("b"); // 95
console.log("a"); // 96
console.log("a"); // 99
console.log("b"); // 100
}
counterPattern();
2。测试
要使此代码可测试,您有几种选择。像您一样使用数组是其中之一。您也可以考虑将函数转换为生成器,然后使用yield
输出值。
但是还有一种解决方案,您无需触摸函数的代码:使用mocking or spying进入console.log
。单元测试库通常提供此类功能。
第二,没有比原始功能更好的参考测试了。因此,您可以先运行原始函数以收集预期的输出,然后运行改进的实现。最后,应该比较两个输出。
以下是在不使用外部库的情况下模拟的工作方式:
function test() { // Wrapper to keep the mocking local
function orig_counterPattern() { // The reference implementation
for (let i = 1; i <= 100; i++) {
if (i % 3 === 0 && i % 5 === 0) {
console.log("c");
} else if (i % 3 === 0) {
console.log("a");
} else if (i % 5 === 0) {
console.log("b");
}
}
}
function counterPattern() { // Our own implementation
for (let i = 0; i < 90; i += 15) {
console.log("a"); // 3 + i
console.log("b"); // 5 + i
console.log("a"); // 6 + i
console.log("a"); // 9 + i
console.log("b"); // 10 + i
console.log("a"); // 12 + i
console.log("c"); // 15 + i
}
// The remainder: values between 90 and 100:
console.log("a"); // 93
console.log("b"); // 95
console.log("a"); // 96
console.log("a"); // 99
console.log("b"); // 100
}
// Mock console object:
let console = {
log: (value) => output.push(value)
};
// Collect the expected output
let output = [];
orig_counterPattern();
let reference = [...output];
// Run our own implementation
output = [];
counterPattern();
let result = [...output];
// Stop mocking the console object
console = globalThis.console;
// Compare
console.assert(output.length === reference.length,"incorrect number of outputs");
console.assert(reference.every((ref,i) => ref === result[i],"mismatch"));
}
test();
console.log("Test completed.");
短代码
首先,您当然可以完全硬编码输出 ,甚至可以避免循环。但这违反了我们不应该重复自己的原则(DRY)。
这里是一个变种,它有点干:
function counterPattern() {
let pattern = "abaabac".repeat(105/15).slice(0,-2);
for (let c of pattern) console.log(c);
}
counterPattern();