Original: Istanbul implementation principle of test coverage | AlloyTeam author: TAT. STEPH

JavaScript unit testing is no stranger to front-end development now. After front-end engineering, the code quality of the project has been paid more and more attention. Unit testing is undoubtedly an important measure of code quality, while test coverage is a measure of test integrity: The coverage of the executed code is used to evaluate the reliability and stability of the code. The code blocks that are not executed by test cases can be found in time and possible logical errors can be discovered in advance.

Istanbul is a javascript-based test coverage measurement tool that is used by most testing frameworks such as Jest Mocha. Istanbul has an older version Istanbul. Js (no longer maintained) and a new version NYC. There’s a lot of people using Istanbul, but there’s very little paper on how it works. So what is the process of Istanbul computing and statistical test coverage?

Before dissecting the source code, we first need to understand the four dimensions of measuring test coverage:

  • Statements: statement coverage (execution rate of all Statements)
  • Execution rate of all code Branches such as IF and ternary operation;
  • Functions: function coverage, the called rate of all Functions;
  • Lines: Line coverage, the execution rate of all valid Lines of code, similar to statements but calculated slightly differently;

The four dimensions above are the final output of Istanbul. It can be seen that the core task of Istanbul is to realize the counter of these four indicators. Its internal implementation process can be roughly divided into the following three steps:

Step 1: Construct the source code decorator

“Instrumenter” is the Istanbul core and its purpose is to “decorate” the source code and populate the counter. To inject counters into source code, you need to identify lines, statements, functions, and so on. First read the source code under the specified directory (user configuration) and construct the syntax tree (AST) one by one, distinguish the four dimensions of the code segment and mark, the specific implementation of this function logic is not expanded in detail in this article, interested in the source code or Babel/Parser plug-in. In a nutshell, the workflow of a decorator is:

Or abstract? For example, the source file to be tested is:

function AFunctionThatNeverBeCalled () {
    returnMath. The random () > 0.5?true : false
}
function AFunctionThatWillBeCalled (string) {
    return string
}
module.exports = function sayHello (name) {
    if (name) {
        return AFunctionThatWillBeCalled('Hello, ' + name)
    } else {
        return 'Should pass a name'}}Copy the code

After processing the AST, dimension tags, and so on, the source code is decorated like this:

var cov_1pwyfn0t92 = (function() {// a bit of code is omitted here, which returns a counter object, including the AST parsed data, as described below})();function AFunctionThatNeverBeCalled() {
  cov_1pwyfn0t92.f[0]++;
  cov_1pwyfn0t92.s[0]++;
  returnMath. The random () > 0.2? (cov_1pwyfn0t92.b[0][0]++,true)
    : (cov_1pwyfn0t92.b[0][1]++, false);
}
function AFunctionThatWillBeCalled(string) {
  cov_1pwyfn0t92.f[1]++;
  cov_1pwyfn0t92.s[1]++;
  return string;
}
cov_1pwyfn0t92.s[2]++;
module.exports = function sayHello(name) {
  cov_1pwyfn0t92.f[2]++;
  cov_1pwyfn0t92.s[3]++;
  if (name) {
    cov_1pwyfn0t92.b[1][0]++;
    cov_1pwyfn0t92.s[4]++;
    return AFunctionThatWillBeCalled('Hello, ' + name);
  } else {
    cov_1pwyfn0t92.b[1][1]++;
    cov_1pwyfn0t92.s[5]++;
    return 'Should pass a name'; }};Copy the code

As you can see, the original source code is almost converted to another look, but the original code logic is not changed, just some count statements that have no effect on the execution of the original code. It is obvious that the count code corresponds to the various dimensions of the counter:

| — – | — – | | cov_1pwyfn0t92 files only counting objects | | | cov_1pwyfn0t92. | s Statement counter | | cov_1pwyfn0t92. B | | Branch counter | cov_1pwyfn0t92. F | | Function counter

Careful friends may notice the absence of a line coverage indicator, the Lines counter, whose implementation coverage is calculated from the execution rate of the statement between the start and end Lines in the statement. If we expand the cov_1PWyFn0t92 object to look at the contents, the output and parsing results after the decorator “decorate” are more intuitive:

To sum up, the purpose of decorators is to tamper with source code and inject counters.

Step 2: Intercept the module loader

But you can’t really change the source code. So how does Istanbul get the source code used in the test case to be its own tampered code? When the unit test framework (Jest, Mocha, etc.) starts running (executing) the test case, it simply intercepts the source code that the module loader is loading at the current runtime and replaces it with the Istanbul decorated code, which is a “backseat” to the source code that the test case references:

Istanbul’s approach to addRequireHook is based on an NPM module called Append-Transform, which is similar to nodeJS’s require.extensions and some special processing. I won’t go into details. All you need to know is that it acts as an interceptor loader.

Step 3: Statistics and output coverage reports

Following the previous steps, and having tampered with the source code and injected the counter, it would be a good idea to collect the four metrics coverage for each file after executing the test case, and finally get the results to output a visual statistical report. Istanbul supports multiple types of statistical reports:

Each type has a corresponding independent module to deal with, for example, HTML type reports need to generate intuitive HTML files; Lcov needs to generate binaries and so on.

This is the fundamental Istanbul implementation. This article only describes the backbone of the implementation, and the details such as ignoring blocks, ES6 support, and SourceMap are also worth reading and digging into.

Reference: github.com/istanbuljs/…


AlloyTeam welcomes excellent friends to join. Resume submission: [email protected] For details, please click Tencent AlloyTeam to recruit Web front-end engineers.