普通视图

发现新文章,点击刷新页面。
今天 — 2025年7月4日首页

XPath 进阶:掌握高级选择器与路径表达式

作者 烛阴
2025年7月4日 15:14

在前面的文章中,我们了解了 XPath 的基本概念和语法。现在,我们将深入探讨 XPath 的高级选择器和路径表达式,以便更高效地查询 XML 数据。

高级选择器

位置选择器

XPath 提供了一些位置选择器,可以帮助我们选择特定位置的节点:

  • position():返回符合当前节点在节点的所有位置,是一个数组,可以对数组进行操作。

  • last():返回节点集中的最后一个节点。

  • 选择最后一个scirpt标签内容:

//script[last()]

过滤选择器

使用方括号 [] 可以对节点进行过滤。例如,百度贴吧回帖数大于600的:

//em[@data-num>600]

复杂路径表达式

XPath 允许我们使用复杂的路径表达式来精确定位节点。以下是一些示例:

<bookstore>
  <category name="前端开发">
    <book id="101">
      <title>JavaScript高级程序设计</title>
      <price>89.00</price>
      <stock>15</stock>
    </book>
    <book id="102">
      <title>CSS权威指南</title>
      <price>79.00</price>
      <stock>8</stock>
    </book>
  </category>
  
  <category name="后端开发">
    <book id="201">
      <title>Node.js实战</title>
      <price>69.00</price>
      <stock>12</stock>
    </book>
    <book id="202">
      <title>Python核心编程</title>
      <price>99.00</price>
      <stock>5</stock>
      <discount>0.8</discount>
    </book>
    <book id="203">
      <title>Java并发编程实战</title>
      <price>109.00</price>
      <stock>0</stock>
    </book>
  </category>
</bookstore>
  • 选择所有书籍的标题:
//book/title
  • 选择所有价格大于 20 的书籍的标题:
//book[price > 70]/title
  • 选择所有书籍中作者为 "王五" 的书籍:
//book[author='王五']

使用 XPath 函数提升查询能力:从基础到复杂

XPath 提供了多种内置函数,可以帮助我们进行更复杂的查询。

常用函数

字符串函数

  • contains(string, substring):检查字符串是否包含子字符串。
  • starts-with(string, substring):检查字符串是否以特定子字符串开头。
  • substring(string, start, length):返回字符串的子串。

例如,选择所有标题中包含 "Java" 的书籍:

//book[contains(title, 'Java')]

数学函数

  • sum(node-set):返回节点集的总和。
  • count(node-set):返回节点集中的节点数量。

选择所有书籍的总价格:

sum(//book/price)

XPath 条件表达式与逻辑运算符的应用

XPath 支持条件表达式和逻辑运算符,使得查询更加灵活。

条件表达式

可以使用条件表达式来筛选节点。例如,选择价格在 10 到 30 之间的书籍:

//book[price >= 70 and price <= 100]

逻辑运算符

XPath 支持 andornot() 运算符。例如,选择所有作者为 "John" 或 "Jane" 的书籍:

//book[price=89 or price=109]

小结

如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

昨天 — 2025年7月3日首页

初识XPath——了解XML与HTML文档中的导航神器

作者 烛阴
2025年7月3日 09:08

引言

在Web开发和自动化测试中,常常需要定位和操作页面中的元素。传统上,我们用CSS选择器,但在某些复杂场景下,XPath是一种更强大、更灵活的工具。本文将带你由浅入深,了解XPath的基本概念和用法。


什么是XPath?

XPath(XML Path Language)是一种用于在XML文档中查找信息的语言。由于HTML是HTML5的标准变体,可视为XML的一种,只要遵守标准,同样适用XPath。

XPath的用途

  • 选择特定元素或一组元素
  • 计算元素的路径
  • 提取元素的内容或属性
  • 在自动化测试框架(如Selenium)中定位元素

XPath的基本结构

XPath表达式类似路径,用于从文档的根节点开始,逐层筛选目标。

例子

<html>
  <body>
    <div id="main">
      <h1>标题</h1>
      <p class="text">这是一段文字。</p>
    </div>
  </body>
</html>

对应的XPath:

  • 选择<h1>//h1
  • 选择<p>//p[@class='text']
  • 选择<div id="main">//div[@id='main']

常用的XPath表达式

表达式 描述 示例
/ 从根节点开始,绝对路径 /html/body/div
// 在文档中查找匹配的元素,不考虑层级 //p
. 当前节点 ./span
.. 父节点 ../div
@属性名 指定属性 //a[@href='https://']
* 任意元素 //*/a

结合条件过滤

  • [条件]:筛选出满足条件的元素
  • 例://div[@class='main']:选择class为main的div
  • 叠加过滤://ul/li[1]:第一个li元素

实战演练:用XPath定位元素

如果你安装了谷歌浏览器,可以安装Xpath测试器进行实战演练

screenshot_2025-07-02_19-02-00.png


小结

XPath是网页元素定位的重要工具,掌握其基础语法可以帮助你更高效地进行网页自动化、数据抓取与测试验证。


如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

昨天以前首页

告别盲测:Jest--JavaScript测试之道

作者 烛阴
2025年7月2日 19:04

为什么要测试,测试什么?

为什么要测试?

  • 提升代码质量和可靠性: 尽早发现并修复bug,减少线上事故。
  • 增强重构信心: 有测试用例保驾护航,你可以大胆地优化和重构代码,因为你知道它们会立即发现潜在的回归错误。
  • 提高开发效率: 避免了手动重复测试的繁琐,让你可以更快地迭代新功能。

测试什么?

我们通常将测试分为几个层次:

  • 单元测试 (Unit Tests): 针对代码中最小的可独立测试单元进行测试,如单个函数、类的方法。它们应该快速、独立、可重复。这是JTest的基础和核心。

  • 集成测试 (Integration Tests): 测试多个单元或模块协同工作时的行为,验证它们之间的接口和交互是否正确。例如,测试一个UI组件与数据层API的交互。


JTest初体验——告别盲测的第一步 (Jest入门)

环境搭建

首先,我们来安装Jest。这是一个零配置的测试框架,非常适合快速上手。

# 进入你的项目目录
cd your-js-project

# 安装 Jest
npm install --save-dev jest

然后,在 package.json 中添加一个 test 脚本:

{
  "name": "your-js-project",
  "version": "1.0.0",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "jest": "^30.0.3"
  }
}

2.2 你的第一个JTest用例

让我们从一个最简单的纯函数开始:加法。

src/sum.js

// 这是一个简单的加法函数
function sum(a, b) {
  return a + b;
}

module.exports = sum; // 导出函数

接下来,我们为 sum.js 创建一个测试文件。根据Jest的约定,测试文件通常与源文件放在同一目录下,并以 .test.js.spec.js 结尾。

src/sum.test.js

const sum = require('./sum'); // 导入要测试的函数

// describe 块用于组织相关的测试用例
describe('sum 函数', () => {
    // test (或 it) 定义一个具体的测试用例
    test('应该正确计算两个正数的和', () => {
        // expect(value) 是 Jest 的全局函数,用于声明一个断言
        // .toBe(expected) 是一个匹配器 (matcher),用于比较值是否相等
        expect(sum(1, 2)).toBe(3);
    });

    test('应该正确计算一个正数和一个负数的和', () => {
        expect(sum(5, -3)).toBe(2);
    });

    test('应该正确计算两个零的和', () => {
        expect(sum(0, 0)).toBe(0);
    });
});

2.3 运行测试

现在,打开你的终端,运行测试命令:

npm test

你将看到类似以下的输出:

> jest

 PASS  ./sum.test.js
  sum 函数
     应该正确计算两个正数的和 (2 ms)
     应该正确计算一个正数和一个负数的和
     应该正确计算两个零的和

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        0.387 s, estimated 1 s
Ran all test suites.

恭喜你!你已经成功编写并运行了你的第一个JTest用例。PASS 意味着你的代码通过了测试,一切正常。


深入浅出:JTest核心断言与组织

更多的断言匹配器 (Matchers)

Jest提供了丰富的匹配器,用于检查各种条件。

匹配器 描述 示例
toBe(value) 严格相等 (===),用于基本类型 expect(1).toBe(1);
toEqual(value) 递归比较对象或数组的内容相等,用于引用类型 expect({a:1}).toEqual({a:1});
not.toBe(value) 不严格相等 expect(1).not.toBe(2);
toBeTruthy() 检查是否为真值 (truthy) expect(1).toBeTruthy();
toBeFalsy() 检查是否为假值 (falsy) expect(0).toBeFalsy();
toBeNull() 检查是否为null expect(null).toBeNull();
toBeUndefined() 检查是否为undefined expect(undefined).toBeUndefined();
toBeDefined() 检查是否已定义 expect(1).toBeDefined();
toBeInstanceOf(Class) 检查是否是某个类的实例 expect(new Array()).toBeInstanceOf(Array);
toContain(item) 检查数组中是否包含某个元素 expect([1, 2, 3]).toContain(2);
toMatch(regexp) 检查字符串是否匹配正则表达式 expect('hello').toMatch(/ll/);
toThrow(error?) 检查函数是否抛出错误 expect(() => { throw new Error(); }).toThrow();
resolves.toBe(value) 检查Promise是否成功解决并匹配值 await expect(Promise.resolve(1)).resolves.toBe(1);
rejects.toThrow(error?) 检查Promise是否失败并抛出错误 await expect(Promise.reject('error')).rejects.toThrow('error');

示例:src/stringUtils.js

function capitalize(str) {
  if (typeof str !== 'string' || str.length === 0) {
    throw new Error('Input must be a non-empty string.');
  }
  return str.charAt(0).toUpperCase() + str.slice(1);
}

module.exports = { capitalize };

src/stringUtils.test.js

const { capitalize } = require('./stringUtils');

describe('capitalize 函数', () => {
    test('应该将字符串的第一个字母大写', () => {
        expect(capitalize('hello')).toBe('Hello');
    });

    test('应该返回相同的大写字符串,如果第一个字母已经是大写', () => {
        expect(capitalize('World')).toBe('World');
    });

    test('应该处理单字符字符串', () => {
        expect(capitalize('a')).toBe('A');
    });

    test('应该抛出错误,如果输入不是字符串', () => {
        expect(() => capitalize(123)).toThrow('Input must be a non-empty string.');
        expect(() => capitalize(null)).toThrow('Input must be a non-empty string.');
    });

    test('应该抛出错误,如果输入是空字符串', () => {
        expect(() => capitalize('')).toThrow('Input must be a non-empty string.');
    });
});

测试生命周期函数:beforeEachafterEach

在某些场景下,你可能需要在每个测试用例运行之前或之后执行一些设置或清理工作。Jest提供了 beforeEach, afterEach, beforeAll, afterAll 等生命周期函数。

  • beforeEach(fn): 在每个 test (或 it) 运行之前执行。
  • afterEach(fn): 在每个 test (或 it) 运行之后执行。
  • beforeAll(fn): 在当前 describe 块中的所有 test 运行之前执行一次。
  • afterAll(fn): 在当前 describe 块中的所有 test 运行之后执行一次。

示例:计数器模块的测试

src/counter.js

let count = 0;

function increment() {
    count++;
}

function decrement() {
    count--;
}

function getCount() {
    return count;
}

function reset() {
    count = 0;
}

module.exports = {
    increment,
    decrement,
    getCount,
    reset
};

src/counter.test.js

const counter = require('./counter');

describe('计数器模块', () => {
    // 在每个测试用例运行前,将计数器重置为0,确保每个测试的独立性
    beforeEach(() => {
        counter.reset();
    });

    test('increment 应该使计数器加一', () => {
        counter.increment();
        expect(counter.getCount()).toBe(1);
    });

    test('decrement 应该使计数器减一', () => {
        counter.decrement();
        expect(counter.getCount()).toBe(-1);
    });

    test('连续调用 increment 应该正确累加', () => {
        counter.increment();
        counter.increment();
        expect(counter.getCount()).toBe(2);
    });

    test('reset 应该将计数器重置为0', () => {
        counter.increment();
        counter.increment();
        counter.reset();
        expect(counter.getCount()).toBe(0);
    });
});

测试覆盖率

衡量你的测试质量的一个重要指标是测试覆盖率 (Test Coverage) 。Jest内置了此功能。

package.jsontest 脚本中添加 --coverage 标志:

{
  "scripts": {
    "test": "jest --coverage"
  }
}

运行 npm test 后,你会在终端看到一个报告,也会在项目根目录生成一个 coverage 文件夹,其中包含详细的HTML报告,你可以打开 coverage/lcov-report/index.html 查看。

覆盖率指标:

  • Statements (语句): 代码中的语句有多少被执行了。
  • Branches (分支): if/else, switch, 三元表达式等分支有多少被执行了。
  • Functions (函数): 函数有多少被调用了。
  • Lines (行): 代码行有多少被执行了。

注意: 100% 覆盖率不代表代码没有bug,它只能说明你的测试执行了所有代码路径,但无法保证这些路径的逻辑都是正确的。你应该追求有意义的覆盖率,而不是盲目追求数字。


结语


如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

JavaScript forEach 方法详解:原数组会被改变吗?

作者 烛阴
2025年6月30日 21:53

1. 什么是 forEach

forEach 是JavaScript数组的一个内置方法,用于对数组中的每个元素执行指定的函数。它的基本语法如下:

array.forEach(callback(currentValue, index, array), thisArg);
  • callback: 要执行的函数,接收三个参数:当前元素的值、当前元素的索引和原数组。
  • thisArg: 可选,执行回调时使用的 this 值。

2. forEach 的基本用法

以下是一个简单的 forEach 示例:

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((number) => {
    console.log(number);
});

在这个例子中,forEach 遍历了 numbers 数组,并打印了每个元素的值。

3. forEach 是否会修改原数组?

forEach 本身并不会自动修改原数组。它只是遍历数组并执行回调函数。如果在回调函数中对数组的元素进行了修改,那么原数组的值将会被改变。

4. 示例:forEach 修改数组的情况

让我们看一个示例,展示如何在 forEach 中修改数组的元素:

const numbers = [1, 2, 3, 4, 5];

numbers.forEach((value, index, array) => {
    array[index] = value * 2; // 修改原数组
});

console.log(numbers); // 输出: [2, 4, 6, 8, 10]

在这个例子中,我们在 forEach 的回调函数中修改了原数组的值,将每个元素乘以2。最终,numbers 数组的值被改变了。

5. 其他数组方法与 forEach 的比较

forEach 类似的数组方法还有 mapfilterreduce。这些方法的行为各不相同:

  • map: 返回一个新数组,包含对原数组每个元素调用回调函数的结果。不会修改原数组。
  • filter: 返回一个新数组,包含所有通过测试的元素。不会修改原数组。
  • reduce: 返回一个单一值,累积所有元素的结果。不会修改原数组。

示例对比

const numbers = [1, 2, 3, 4, 5];

// 使用 map
const doubled = numbers.map((value) => value * 2);
console.log(doubled); // 输出: [2, 4, 6, 8, 10]
console.log(numbers); // 输出: [1, 2, 3, 4, 5] (原数组未修改)

// 使用 filter
const evenNumbers = numbers.filter((value) => value % 2 === 0);
console.log(evenNumbers); // 输出: [2, 4]
console.log(numbers); // 输出: [1, 2, 3, 4, 5] (原数组未修改)

// 使用 reduce
const sum = numbers.reduce((acc, value) => acc + value, 0);
console.log(sum); // 输出: 15
console.log(numbers); // 输出: [1, 2, 3, 4, 5] (原数组未修改)

6. 总结

forEach 方法本身不会修改原数组,但如果在回调函数中对数组元素进行了修改,那么原数组的值将会被改变。在需要返回新数组的场景中,建议使用 mapfilterreduce 等方法。


如果你喜欢本教程,记得点赞+收藏!关注我获取更多JavaScript开发干货。

❌
❌