24、自动化测试、JavaScript 基础与 TypeScript 编译器指南

自动化测试、JavaScript 基础与 TypeScript 编译器指南

自动化测试要点

自动化测试是软件开发中确保代码质量的重要手段。在各类测试中,自动化单元测试相较于集成测试或回归测试更为有效。不过,为了达到最佳的缺陷检测率,综合运用多种测试方法是一个不错的策略。

对于 JavaScript 和 TypeScript 而言,存在众多的测试框架。如果想要缩小选择范围,可以考虑 Jest、Jasmine 和 Mocha。其中,Jest 不仅可以用于编写测试,还能作为文档使用。通过编写规范来驱动实现,能确保当代码行为不正确时测试会失败;而在实现之后再编写测试,并不能保证测试一定会失败。

在开发过程中,不仅要对生产代码进行重构,测试代码同样需要重构。此外,使用简单对象来隔离依赖是一个不错的选择,这比那些可能会使测试与实现过度绑定的复杂工具更为可取。

JavaScript 快速参考

如果你对 JavaScript 还不太熟悉,下面将为你介绍在 TypeScript 程序中会用到的 JavaScript 核心特性。

变量

在 JavaScript 中,变量用于存储应用程序的状态,它可以包含从字符串、数字到对象和函数等任意类型的数据。
变量的声明和赋值可以在同一条语句中完成,也可以分开进行。变量的类型取决于最后一次赋值的值。如果没有赋值,变量的值将为 undefined
声明变量时,可以使用 let const 这两个关键字。它们都是块级作用域,与大多数使用花括号的语言类似。使用 let 声明的变量可以在后续代码中重新赋值,而使用 const 声明的变量则不能重新赋值。不过, const 变量并非不可变,只是变量本身不能被覆盖。在选择关键字时,建议优先使用 const ,当需要重新赋值时再升级为 let let const 都比 var 更受青睐,因为 var 是函数作用域,而非块级作用域。

示例代码如下:

// Variable declaration
let variable;
// Variable assignment
variable = 'A string is assigned';
// Dynamic assignment (changes variable’s type to a number)
variable = 10;
数组

可以将数组存储在变量中。可以使用空字面量 [] 创建一个新的空数组,并使用 array.push 方法添加元素。也可以通过在数组字面量中直接放置值来一次性创建并填充数组。

示例代码如下:

// Creating an empty array and adding values
const myArray = [];
myArray.push(1);
myArray.push(3);
myArray.push(5);
// Adding values using an array literal
const myLiteralArray = [1, 3, 5];
对象

对象可用于表示复杂结构的数据。对象包含的属性类似于变量,可以包含字符串、数字、数组、函数和其他对象。与数组类似,可以使用空对象字面量 {} 创建一个空对象,也可以在对象字面量中放置属性来一次性创建并填充对象。

示例代码如下:

// Objects
const myObject = {};
myObject.title = 'Example Object';
myObject.value = 5;
// Object literals
const myLiteralObject = {
    title: 'Example Object',
    value: 5
};

在 JavaScript 中,推荐使用字面量语法来创建数组、对象和字符串,而不是使用 new 关键字。

函数

函数可用于在程序中创建可复用的代码块。可以使用函数声明或函数表达式来创建函数。

函数声明示例:

// Function declaration
function myFunction(name) {
    return 'Hello ' + name;
}
// 'Hello Steve'
let greeting = myFunction('Steve');

函数表达式示例:

// Function expression
var myFunctionExpression = function (name) {
    return 'Hi ' + name;
};
// 'Hi Steve'
greeting = myFunctionExpression('Steve');

使用函数声明创建的函数在解析时就已创建,在其作用域内的任何地方都可以使用;而使用函数表达式创建的函数在运行时才会被计算,只能在其作用域内且调用代码出现在函数表达式之后的地方调用。

条件语句

条件语句可用于在程序中实现逻辑分支,只有当特定条件满足时才会执行相应的代码。

if 语句可以根据自定义条件(结果为 true false )来分支代码。示例如下:

// If statements
const age = 21;
if (age > 18) {
    // Code to execute if age is greater than 18
}
if (age > 40) {
    // Code to execute if age is greater than 40
} else if (age > 18) {
    // Code to execute if age is greater than 18
    // but less than 41
} else {
    // Code to execute in all other cases
}

switch 语句适用于根据单个变量的特定值来控制多个分支。示例如下:

// Switch statements
const styles = {
    tranditional: 1,
    modern: 2,
    postModern: 3,
    futuristic: 4
};
const style = styles.tranditional;
switch (style) {
    case styles.tranditional:
        // Code to execute for traditional style
        break;
    case styles.modern:
        // Code to execute for modern style
        break;
    case styles.postModern:
        // Code to execute for post modern style
        break;
    case styles.futuristic:
        // Code to execute for futuristic style
        break;
    default:
        throw new Error('Style not known: ' + style);
}

需要注意的是, switch 语句与 TypeScript 枚举结合使用效果更佳。

循环

循环用于在程序中重复执行一段代码。JavaScript 中最常见的循环是 for 循环,可用于对数组中的每个元素执行操作。

for 循环示例:

const names = ['Lily', 'Rebecca', 'Debbye', 'Ann'];
for (let i = 0; i < names.length; i++) {
    console.log(names[i]);
}

可以使用 for-of 循环简化上述代码,它无需使用变量来跟踪索引,会直接将每次迭代的值赋给一个变量。示例如下:

const names = ['Lily', 'Rebecca', 'Debbye', 'Ann'];
for (let name in names) {
    console.log(name);
}

while 循环会重复执行一段代码,直到条件不满足为止。示例如下:

let counter = 10;
while (counter > 0) {
    counter--;
    console.log(counter);
}

do-while 循环与 while 循环类似,但它至少会执行一次代码,即使条件一开始就不满足。示例如下:

let counter = 0;
do {
    counter--;
    console.log(counter);
} while (counter > 0);
字符串

虽然大量的字符串操作可能暗示设计存在问题,但 JavaScript 中处理字符串的一些新特性值得了解。

传统字符串可以使用单引号或双引号。反引号字符串可以接受换行符,并允许使用 ${} 进行字符串插值,从而可以在字符串中嵌入变量和表达式。示例如下:

const name = 'Jamie';
// Traditional strings
const classicString = 'Line One\n' +
    'Line Two\n' +
    'Line Three. Hello ' + name + '!';
// Back-ticked strings
const backtickedString = `Line One
Line Two
Line Three. Hello ${name}!`;
// 'Five plus seven is 12.'
const expressionString = `Five plus seven is ${5 + 7}.`;
承诺(Promises)

承诺是 JavaScript 应用程序中快速发展的核心特性,它为协调异步操作提供了一种简单的机制。

使用 fetch API 从 URL 获取数据的承诺示例:

fetch('/Your/URL/').then(function (response) {
    // Response received
    if (response.status >= 200 && response.status < 300) {
        // We got a success status code
    }
}).catch(function (error) {
    // Request failed
});

一个承诺包含三个部分:调用一个返回承诺的函数(如 fetch 函数)、成功时调用的 then 结果处理程序以及出错时调用的 catch 结果处理程序。

自定义承诺示例:

let promise = new Promise(function (resolve, reject) {
    window.setTimeout(function () {
        if (true) {
            resolve('Success');
        } else {
            reject('Failed');
        }
    }, 1000);
});
promise.then(function (message) {
    alert(message);
}).catch(function (error) {
    alert(error);
});
TypeScript 编译器

TypeScript 编译器虽然可能隐藏在开发工具之后,但了解其各种编译器选项是很有必要的。目前有超过 70 种不同的编译器标志,下面将介绍一些常见的选项。

编译器位置与运行方式

在 Windows 系统中,TypeScript 编译器 tsc.exe 通常位于 C:\Program Files (x86)\Microsoft SDKs\TypeScript\[Version]\tsc.exe 。要运行编译器,可以在命令行中调用 tsc 并传入程序的根文件名,例如 tsc app.ts 。如果觉得每次输入完整路径很麻烦,可以将 TypeScript 文件夹的路径添加到环境变量中。

也可以在任何操作系统上使用 Node 运行 TypeScript 编译器,需要输入 tsc.js 文件的完整路径(而非 .ts 文件),例如 node tsc.js app.ts

获取帮助

如果忘记了编译器的所有选项,可以使用以下命令获取帮助,它们会显示所有可用的编译器标志列表:
- tsc --help
- node tsc.js

示例文件

为了说明不同编译器选项之间的关键差异,使用以下示例 TypeScript 代码:

import * as Dependency from './module';
export class Example extends Dependency.BaseClass {
    exampleMethod(): number {
        return 5;
    }
}
// Comment
let example = new Example();
const val = example.exampleMethod();

通过查看该代码生成的 JavaScript 代码,可以了解模块加载和向下编译的差异,向下编译可以将 TypeScript 的最新特性转换为与旧版本 ECMAScript 规范兼容的 JavaScript 代码。

常见标志
  • 模块类型(Module Kind) module 编译器标志用于生成使用 CommonJS 或 AMD 模块模式加载外部模块的代码。有效的模块类型值包括 commonjs amd
    • UMD(Universal Module Definition) tsc --module UMD app.ts 会创建可以在 AMD 和 CommonJS 两种主流模块风格中运行的代码。例如,这样的代码可以在浏览器中由 RequireJS 加载,也可以在服务器上由 Node 加载。此模式的输出比 AMD 或 CommonJS 更冗长,因为它包含用于功能检查以使用正确加载风格的代码。示例如下:
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./module"], factory);
    }
})(function (require, exports) {
    "use strict";
    exports.__esModule = true;
    var Dependency = require("./module");
    // Removed for brevity
    exports.Example = Example;
    // Comment
    var example = new Example();
    var val = example.exampleMethod();
});
- **AMD**:AMD 模块输出更简洁,因为它不需要进行功能检查。此代码与 RequireJS 和 Dojo Toolkit 等模块加载器兼容。示例如下:
define(["require", "exports", "./module"], function (require, exports, Dependency) {
    "use strict";
    exports.__esModule = true;
    // Removed for brevity
    exports.Example = Example;
    // Comment
    var example = new Example();
    var val = example.exampleMethod();
});
- **CommonJS**:CommonJS 模块最著名的实现是 Node,在 MongoDB 和 CouchDB 等中也有使用。示例如下:
"use strict";
exports.__esModule = true;
var Dependency = require("./module");
// Removed for brevity
exports.Example = Example;
// Comment
var example = new Example();
var val = example.exampleMethod();
- **ESNext**:ECMAScript 模块语法与原始 TypeScript 代码最为相似。示例如下:
import * as Dependency from './module';
// Removed for brevity
export { Example };
// Comment
var example = new Example();
var val = example.exampleMethod();
AMD 和 System 模块类型还可以与 `--outFile` 标志一起使用,该标志可以将程序合并为一个 JavaScript 文件。
  • ECMAScript 目标版本(ECMAScript Target Version) target 标志允许设置目标 ECMAScript 版本,目前可以针对多个版本的 ECMAScript 规范,包括 ES3、ES5、ES2015、ES2016、ES2017 或使用 ESNext 针对最新版本。对于大多数语言特性,编译器会将代码向下编译到指定的版本,但少数特性可能不可用。
    • ES5 目标 tsc --target ES5 app.ts 会将代码向下编译到 ECMAScript 5 规范。由于 ES5 不支持 const let 关键字,它们会被 var 关键字替代。在会影响变量作用域的情况下,会生成额外的代码将 var 关键字封装在一个新的函数作用域中。类会被替换为立即调用的函数表达式(IIFE)。示例如下:
import * as Dependency from './module';
var Example = (function (_super) {
    __extends(Example, _super);
    function Example() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    Example.prototype.exampleMethod = function () {
        return 5; 
    };
    return Example;
}(Dependency.BaseClass));
export { Example };
// Comment
var example = new Example();
var val = example.exampleMethod();
- **ESNext 目标**:当针对支持 TypeScript 中使用的模块加载、类语法和变量声明的最新版本 ECMAScript 规范时,生成的 JavaScript 代码与 TypeScript 代码几乎相同。示例如下:
import * as Dependency from './module';
export class Example extends Dependency.BaseClass {
    exampleMethod() {
        return 5;
    }
}
// Comment
let example = new Example();
const val = example.exampleMethod();
TypeScript 会跟踪 ECMAScript 提案,这意味着最新版本的 TypeScript 和 JavaScript 会非常相似。编译代码的唯一真正区别是类型擦除,因为目前没有计划将类型注解添加到 JavaScript 中。
  • 生成声明文件(Generate Declarations) declaration 标志会生成一个后缀为 .d.ts 的额外文件,其中包含代码的环境声明。示例如下:
import * as Dependency from './module';
export declare class Example extends Dependency.BaseClass {
    exampleMethod(): number;
}

这个特性在通过 NPM 等包管理器打包程序时非常有用。

  • 移除注释(Remove Comments) removeComments 标志会从输出中删除所有注释,如果源代码中有大量注释,这会使输出文件变小。示例命令: tsc --removeComments true app.ts

  • 合并输出(Combined Output) :可以使用 out 编译器标志将整个 TypeScript 程序合并为一个输出文件,使用该标志时必须指定合并文件的名称。示例命令: tsc --out final.js app.ts 。不过,将整个程序压缩为一个输出文件与 TypeScript 编写大型程序的目的相悖,即使是使用 TypeScript 编写小型程序来学习习惯,模块加载也是需要培养的关键习惯之一。

  • 代码质量标志(Code Quality Flags) :以下标志可以帮助提高代码质量,并在编译时捕获潜在问题。如果是从头开始编写新程序,启用这些标志并遵循编译器的指导应该很简单。对于现有程序,可以选择启用这些标志,修复编译器发现的一些问题后再关闭,这样可以在多个离散的会话中逐步优化代码,直到最终可以永久启用这些标志。

自动化测试、JavaScript 基础与 TypeScript 编译器指南

代码质量标志详解

代码质量标志在软件开发过程中起着至关重要的作用,它们能够帮助开发者在编译阶段就发现潜在的问题,从而提高代码的可靠性和可维护性。以下为你详细介绍几个常见的代码质量标志及其作用。

--strict 标志

--strict 标志是一个综合性的严格模式开关,它会开启一系列严格的类型检查选项,包括 --strictNullChecks --strictFunctionTypes --strictBindCallApply --strictPropertyInitialization --noImplicitAny 等。开启这个标志后,TypeScript 编译器会对代码进行更严格的检查,减少潜在的运行时错误。

例如,在不开启 --strictNullChecks 时,以下代码可以正常编译:

let name: string;
console.log(name.length); // 不会报错,但运行时可能出错

而开启 --strict 后,编译器会提示 name 可能为 undefined ,需要进行初始化或检查:

let name: string;
console.log(name.length); // 编译错误:'name' is possibly 'undefined'.

使用方法:在命令行中运行 tsc --strict app.ts 即可开启严格模式。

--noImplicitAny 标志

--noImplicitAny 标志会禁止 TypeScript 编译器在没有明确指定类型时默认将变量类型推断为 any 。这有助于提高代码的类型安全性,避免因隐式的 any 类型而导致的潜在错误。

例如,以下代码在不开启 --noImplicitAny 时可以正常编译:

function add(a, b) {
    return a + b;
}

开启 --noImplicitAny 后,编译器会提示 a b 缺少类型注解:

function add(a, b) { // 编译错误:Parameter 'a' implicitly has an 'any' type.
    return a + b;
}

正确的做法是明确指定参数类型:

function add(a: number, b: number) {
    return a + b;
}

使用方法:在命令行中运行 tsc --noImplicitAny app.ts 即可开启该检查。

--noImplicitThis 标志

--noImplicitThis 标志会禁止 TypeScript 编译器在没有明确指定类型时默认将 this 类型推断为 any 。这有助于避免因 this 指向不明确而导致的错误。

例如,以下代码在不开启 --noImplicitThis 时可以正常编译:

const person = {
    name: 'John',
    greet() {
        console.log(`Hello, my name is ${this.name}`);
    }
};
const greetFunction = person.greet;
greetFunction(); // 运行时可能出错,this 指向全局对象

开启 --noImplicitThis 后,编译器会提示 this 类型不明确:

const person = {
    name: 'John',
    greet() {
        console.log(`Hello, my name is ${this.name}`); // 编译错误:'this' implicitly has type 'any' because it does not have a type annotation.
    }
};

可以通过明确指定 this 类型来解决:

const person = {
    name: 'John',
    greet(this: { name: string }) {
        console.log(`Hello, my name is ${this.name}`);
    }
};
const greetFunction = person.greet;
greetFunction.call(person); // 正确调用

使用方法:在命令行中运行 tsc --noImplicitThis app.ts 即可开启该检查。

编译器配置文件

为了方便管理 TypeScript 编译器的选项,可以使用 tsconfig.json 文件。这个文件允许你一次性配置所有的编译器选项,而不需要在每次编译时都在命令行中输入。

创建 tsconfig.json 文件

在项目根目录下创建一个 tsconfig.json 文件,以下是一个简单的示例:

{
    "compilerOptions": {
        "target": "ES5",
        "module": "commonjs",
        "strict": true,
        "outDir": "./dist",
        "rootDir": "./src"
    },
    "include": ["src/**/*.ts"],
    "exclude": ["node_modules"]
}
  • compilerOptions :包含了所有的编译器选项,如目标 ECMAScript 版本、模块类型、是否开启严格模式等。
  • include :指定需要编译的文件或目录,支持通配符。
  • exclude :指定需要排除的文件或目录,通常会排除 node_modules 目录。
使用 tsconfig.json 进行编译

创建好 tsconfig.json 文件后,在命令行中只需运行 tsc 命令,编译器就会自动读取 tsconfig.json 文件中的配置选项进行编译。

总结

本文详细介绍了自动化测试的要点、JavaScript 的核心特性以及 TypeScript 编译器的常见选项。自动化测试是保证代码质量的重要手段,综合运用多种测试方法可以提高缺陷检测率。JavaScript 作为 TypeScript 的基础,其变量、数组、对象、函数、条件语句、循环、字符串和承诺等特性在 TypeScript 程序中经常使用。TypeScript 编译器提供了丰富的选项,通过合理配置这些选项,可以实现不同的模块加载方式、向下编译到不同的 ECMAScript 版本、生成声明文件、移除注释和合并输出等功能。同时,使用代码质量标志可以帮助开发者提高代码的质量,避免潜在的错误。最后,通过 tsconfig.json 文件可以方便地管理编译器选项,提高开发效率。

在实际开发中,建议开发者根据项目的需求和特点,选择合适的测试框架和编译器选项,养成良好的编码习惯,不断提高代码的质量和可维护性。

流程图:TypeScript 编译流程

graph LR
    A[编写 TypeScript 代码] --> B[配置 tsc 选项或使用 tsconfig.json]
    B --> C{选择编译器标志}
    C -->|模块类型| D1(UMD/AMD/CommonJS/ESNext)
    C -->|目标版本| D2(ES3/ES5/ES2015/ES2016/ES2017/ESNext)
    C -->|其他标志| D3(生成声明/移除注释/合并输出/代码质量检查)
    D1 --> E[编译生成 JavaScript 代码]
    D2 --> E
    D3 --> E
    E --> F[部署或运行 JavaScript 代码]

表格:常见编译器标志总结

标志名称 作用 示例命令
--module 指定模块类型 tsc --module UMD app.ts
--target 设置目标 ECMAScript 版本 tsc --target ES5 app.ts
--declaration 生成声明文件 tsc --declaration app.ts
--removeComments 移除输出文件中的注释 tsc --removeComments true app.ts
--out 合并输出为一个文件 tsc --out final.js app.ts
--strict 开启严格类型检查 tsc --strict app.ts
--noImplicitAny 禁止隐式的 any 类型 tsc --noImplicitAny app.ts
--noImplicitThis 禁止隐式的 this 类型 tsc --noImplicitThis app.ts
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值