【问题标题】:How to improve this js function so as to make it faster and also better unit-testable?如何改进这个 js 功能,使其更快,更好的单元测试?
【发布时间】:2020-12-03 23:13:41
【问题描述】:

我有一个简单的程序:

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();

有人要求我逐步改进上述功能:

  1. 减少执行时间,并且
  2. 为其编写单元测试

这导致我更改函数以使其适合编写单元测试。

以下是朝那个方向的尝试:

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. 使其执行速度更快,
  2. 并且可以进行单元测试;

你们有我的尝试吗?

【问题讨论】:

  • 我不认为快照对于直接测试这个功能是必要的。因为据我所知,它看起来像是一个纯函数,所以输出总是相同的。不过,我倾向于遵循 KentCDodds 测试范例,所以这当然是你的特权。
  • 关于更快的执行:en.wikipedia.org/wiki/Fizz_buzz - 这是一个非常众所周知的问题,在互联网上的某个地方,您可能想到的每种语言的每种解决方案都可能存在。
  • @Jacob - 感谢您的意见;我想到了一个用例的快照测试,以便将来更改功能-但规格是相同的;或者相反,如果我们有一个结果开始并且功能体正在发展;另外,将检查 KentCDodds - 这对我来说是新的。

标签: javascript performance unit-testing execution-time


【解决方案1】:

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();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-03
    • 2015-01-16
    • 1970-01-01
    • 2013-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多