Foundry 单元测试
安装 Foundry
如果你还没有安装 Foundry,请按照此处的说明进行操作:Foundry 安装
Foundry Hello World
只需运行以下命令,它将为你设置环境,创建测试并运行它们。(当然,这假设你已经安装了 Foundry)。
forge init
forge test
Solidity 测试最佳实践
无论使用何种框架,solidity 单元测试的质量取决于三个因素:
- 行覆盖率
- 分支覆盖率,以及
- 完全定义的状态转换。
通过理解每一个因素,我们可以说明为何我们专注于 Foundry API 的某些方面。
当然,不可能为所有可能的输入输出范围编写文档。然而,测试质量通常与行覆盖率、分支覆盖率和定义的状态转换相关。在我们的另一篇文章中,我们已经记录了如何使用 Foundry 测量行和分支覆盖率 。我们将在此解释这三个度量指标的重要性:
1. 行覆盖率
行覆盖率就是它字面上的意思。如果代码行在测试中未被执行,则行覆盖率不是 100%。如果代码行从未被执行过,你无法确定它是否按预期工作或会抛出异常。在智能合约中没有不实现 100%行覆盖率的理由。如果你在编写代码,这意味着你期望它在未来的某个时候被执行,那么为什么不对其进行测试呢?
2. 分支覆盖率
即使每一行都被执行了,也不意味着每一种智能合约业务逻辑的变化都被测试了。
考虑以下函数:
function changeOwner(address newOwner) external {require(msg.sender == owner, "onlyOwner");owner = newOwner;
}
如果通过调用此地址的所有者来测试它,你将获得 100%的行覆盖率,但不会获得 100%的分支覆盖率。这是因为 require 语句和所有者分配都被执行了,但 require 抛出异常的情况没有被测试。
这里是一个更微妙的例子。
// @notice anyone can pay off someone else's loan
// @param debtor the person who's loan the sender is making a payment for
function payDownLoan(address debtor) external payable {uint256 loanAmount = loanAmounts[debtor];require(loanAmount > 0, "no such loan");if (msg.value >= debtAmount) {loanAmounts[debtor] = 0;emit LoanFullyRepaid(debtor);} else {emit LoanPayment(debtor, debtAmount, msg.value);loanAmount -= msg.value;}if (msg.value > loanAmount) {msg.sender.call{value: msg.value - loanAmount}("");}
}
在这种情况下有多少个分支需要测试?
- 贷款为零的情况
- 某人支付少于贷款金额的情况
- 某人支付正好等于贷款金额的情况
- 某人支付超过贷款金额的情况
通过发送多于或少于贷款金额的以太币,可以在此测试中获得 100%的行覆盖率。这将执行 if else 语句的两个分支以及最后的 if 语句但这不会测试贷款正好付清为零的 else 语句。
你的函数分支越多,单元测试它们就越困难。技术术语为圈复杂度 。
3. 完全定义的状态转换
高质量的 solidity 单元测试尽可能详细地记录状态转换。状态转换包括:
- 存储变量的改变
- 合约的部署或自毁
- 以太余额的变化
- 事件的触发,带有某些消息
- 交易的回退,带有某些错误消息
如果函数执行了这些动作,修改状态的确切方式应该在单元测试中被捕获,任何偏差都应导致回退。这样,任何意外的修改,无论多么微小,都会自动被捕捉。回到之前的例子,应测试哪些状态转换?
- 合约中的 Ether 增加了借款人偿还贷款的等量
- 跟踪贷款金额的存储变量按预期金额减少
- 当发送者为不存在的贷款付款时,出现预期的错误消息
- 触发相应的事件和相关消息
如果智能合约的业务逻辑发生变化,测试应失败。在其他领域,这通常被认为是一个“脆弱”的单元测试。它可能会减慢源代码的迭代速度。但 Solidity 代码通常是一次编写且永不更改,因此这对智能合约测试来说不是问题。
4. 单元测试最佳实践结论
在记录 Foundry 单元测试工作原理之前,为什么我们要覆盖所有这些?因为这可以帮助我们隔离大多数情况下会使用的高影响测试工具。Foundry 的功能非常广泛,但多数测试用例中只会用到一小部分。
Foundry 断言
为了确保状态转换确实发生,你将需要断言。让我们从你调用forge init后 Foundry 提供的默认测试文件开始。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;import "forge-std/Test.sol";
import "../src/Counter.sol";contract CounterTest is Test {Counter public counter;function setUp() public {counter = new Counter();counter.setNumber(0);}function testIncrement() public {counter.increment();assertEq(counter.number(), 1);}function testSetNumber(uint256 x) public {counter.setNumber(x);assertEq(counter.number(), x);}
}
setUp() 函数部署你正在测试的合约(以及生态系统中的其他合约)。
任何以test开头的函数将被执行为单元测试。不以test开头的函数将不会被执行,除非它们被test或setUp函数调用。
这里 可以找到你可以使用的断言。
你最常用的有:
assertEq,断言相等assertLt,断言小于assertLe,断言小于或等于assertGt,断言大于assertGe,断言大于或等于assertTrue,断言为真
前两个传递给 assert 的参数是比较内容,但你也可以添加一个作为第三个参数的帮助错误信息,你应该总是这样做(尽管默认示例没有显示)。以下是推荐的写断言的方式:
function testIncrement() public {counter.increment();assertEq(counter.number(), 1, "expect x to equal to 1");
}function testSetNumber(uint256 x) public {counter.setNumber(x);assertEq(counter.number(), x, "x should be setNumber");
}
使用 Foundry vm.prank 修改 msg.sender
Foundry 更有趣的方法来改变发送者(账户或钱包)是 vm.prank API(Foundry 称之为作弊码)。
这是一个最小的示例
function testChangeOwner() public {vm.prank(owner);contractToTest.changeOwner(newOwner);assertEq(contractToTest.owner(), newOwner);
}
vm.prank 仅对紧随其后的事务有效。如果你想使用同一个地址进行一系列交易,请使用 vm.startPrank 并在结束后使用 vm.stopPrank。
function testMultipleTransactions() public {vm.startPrank(owner);// 表现为所有者vm.stopPrank();
}
在 Foundry 中定义账户和地址
上面的 owner 变量可以用几种方式定义:
// 将十进制转换为地址创建的地址
address owner = address(1234);// vitalik 的地址
address owner = 0x0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045;// 从已知私钥创建一个地址;
address owner = vm.addr(privateKey);// 创建一个攻击者
address hacker = 0x00baddad;
msg.sender 和 tx.origin 的恶作剧
在上述示例中,msg.sender 被更改。如果你想同时控制 tx.origin 和 msg.sender,vm.prank 和 vm.startPrank 都可以选择性地接受两个参数,其中第二个参数是 tx.origin。
vm.prank(msgSender, txOrigin);
依赖于 tx.origin 通常是一个坏习惯,所以你很少需要使用带两个参数版本的 vm.prank。
检查余额
当你转移以太币时,你应该测量余额是否按预期变化。值得庆幸的是,在 Foundry 中检查余额很容易,因为它是用 Solidity 编写的。
考虑这个合约:
contract Deposit {event Deposited(address indexed);function buyerDeposit() external payable {require(msg.value == 1 ether, "incorrect amount");emit Deposited(msg.sender);}// 逻辑的其他部分
}
测试函数如下所示。
function testBuyerDeposit() public {uint256 balanceBefore = address(depositContract).balance;depositContract.buyerDeposit{value: 1 ether}();uint256 balanceAfter = address(depositContract).balance;assertEq(balanceAfter - balanceBefore, 1 ether, "expect increase of 1 ether");
}
请注意,我们没有测试买家发送的金额不是 1 以太币的情况,这会导致回滚。我们将在下一节讨论测试回滚。
使用 vm.expectRevert 预计回滚
当前形式的上述测试的问题在于,你可以删除 require 语句,测试仍然会通过。让我们改进测试,以使删除 require 语句会导致测试失败。
function testBuyerDepositWrongPrice() public {vm.expectRevert("incorrect amount");depositContract.deposit{value: 1 ether + 1 wei}();vm.expectRevert("incorrect amount");depositContract.deposit{value: 1 ether - 1 wei}();
}
请注意,必须在我们预计要回滚的函数调用之前立即调用 vm.expectRevert。现在,如果我们删除 require 语句,它将回滚,因此我们更好地模拟了智能合约的预期功能。
测试自定义错误
如果我们使用自定义错误而不是 require 语句,测试回滚的方法如下:
contract CustomErrorContract {error SomeError(uint256);function revertError(uint256 x) public pure {revert SomeError(x);}
}
测试文件如下所示:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;import "forge-std/Test.sol";
import "../src/RevertCustomError.sol";contract CounterTest is Test {CustomErrorContract public customErrorContract;error SomeError(uint256);function setUp() public {customErrorContract = new CustomErrorContract();}function testRevert() public {// 5 是一个任意示例vm.expectRevert(abi.encodeWithSelector(SomeError.selector, 5));customErrorContract.revertError(5);}
}
在我们的示例中,我们创建了一个参数化的自定义错误。为了使测试通过,参数需要等于在回滚期间实际使用的参数。
使用 vm.expectEvent 测试日志和事件
虽然 solidity 事件 不会改变智能合约的功能,但错误地实现它们会破坏读取智能合约状态的客户端应用程序。为了确保我们的事件按预期工作,我们可以使用 vm.expectEmit。这个 API 的行为相当反直觉,因为你必须在测试中发出事件,以确保它在智能合约中工作。
这是一个最小的示例。
function testBuyerDepositEvent() public {vm.expectEmit();emit Deposited(buyer);depositContract.deposit{value: 1 ether}();
}
使用 vm.warp 调整 block.timestamp
现在让我们考虑一个时间锁定的提现。卖方可以在 3 天后提现付款。
contract Deposit {address public seller;mapping(address => uint256) public depositTime;event Deposited(address indexed);event SellerWithdraw(address indexed, uint256 indexed);constructor(address _seller) {seller = _seller;}function buyerDeposit() external payable {require(msg.value == 1 ether, "incorrect amount");uint256 _depositTime = depositTime[msg.sender];require(_depositTime == 0, "already deposited");depositTime[msg.sender] = block.timestamp;emit Deposited(msg.sender);}function sellerWithdraw(address buyer) external {require(msg.sender == seller, "not the seller");uint256 _depositTime = depositTime[buyer];require(_depositTime != 0, "buyer did not deposit");require(block.timestamp - _depositTime > 3 days, "refund period not passed");delete depositTime[buyer];emit SellerWithdraw(buyer, block.timestamp);(bool ok, ) = msg.sender.call{value: 1 ether}("");require(ok, "seller did not withdraw");}
}
我们添加了许多需要测试的功能,但现在让我们重点放在时间方面。
我们想测试卖方在存款后的 3 天内不能提取资金。(显然缺少一个买方在该时间窗口前提取的函数,但我们稍后会讨论)。
请注意,block.timestamp 默认从 1 开始。这不是一个实际的测试数字,因此我们应该首先转换到当前日期。
可以使用 vm.warp(x) 来实现这个功能,但我们可以更讲究地使用修饰符。
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;import "forge-std/Test.sol";
import "../src/Deposit.sol";contract DepositTest is Test {Deposit public deposit;Deposit public faildeposit;address constant SELLER = address(0x5E11E7);//address constant Rejector = address(RejectTransaction);RejectTransaction private rejector;event Deposited(address indexed);event SellerWithdraw(address indexed, uint256 indexed);function setUp() public {deposit = new Deposit(SELLER);rejector = new RejectTransaction();faildeposit = new Deposit(address(rejector));}modifier startAtPresentDay() {vm.warp(1680616584);_;}address public buyer = address(this); // DepositTest 合约即为“买家”address public buyer2 = address(0x5E11E1); // 随机地址address public FakeSELLER = address(0x5E1222); // 随机地址function testDepositAmount() public startAtPresentDay {// 此测试检查买家只能存入 1 ethervm.startPrank(buyer);vm.expectRevert();deposit.buyerDeposit{value: 1.5 ether}();vm.expectRevert();deposit.buyerDeposit{value: 2.5 ether}();vm.stopPrank();}
}
使用 vm.roll 调整 block.number
如果你想在 Foundry 中调整区块号 (block.number),使用
vm.roll(blockNumber)
来改变区块号。要向前移动一定数量的区块,请执行以下操作
vm.roll(block.number() + numberOfBlocks)
添加额外的测试
为了完整性,让我们为其余的功能编写单元测试。一些额外的功能需要为存款功能进行测试:
- 公共变量
depositTime与交易时间匹配 - 用户不能重复存款
以及卖家功能的测试:
- 卖家不能为不存在的地址提款
- 买家的条目被删除(这允许买家重新购买)
- 触发
SellerWithdraw事件 - 合约的余额减少 1 ether
- 不是卖家的地址调用
sellerWithdraw会被回滚
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;import "forge-std/Test.sol";
import "../src/Deposit.sol";contract DepositTest is Test {Deposit public deposit;Deposit public faildeposit;address constant SELLER = address(0x5E11E7);//address constant Rejector = address(RejectTransaction);RejectTransaction private rejector;event Deposited(address indexed);event SellerWithdraw(address indexed, uint256 indexed);function setUp() public {deposit = new Deposit(SELLER);rejector = new RejectTransaction();faildeposit = new Deposit(address(rejector));}modifier startAtPresentDay() {vm.warp(1680616584);_;}address public buyer = address(this); // DepositTest 合约即为“买家”address public buyer2 = address(0x5E11E1); // 随机地址address public FakeSELLER = address(0x5E1222); // 随机地址function testDepositAmount() public startAtPresentDay {// 此测试检查买家只能存入 1 ethervm.startPrank(buyer);vm.expectRevert();deposit.buyerDeposit{value: 1.5 ether}();vm.expectRevert();deposit.buyerDeposit{value: 2.5 ether}();vm.stopPrank();}function testBuyerDepositSellerWithdrawAfter3days() public startAtPresentDay {// 此测试检查买家存款后 3 天,卖家能够提款// 买家存款 1 ethervm.startPrank(buyer); // msg.sender == buyerdeposit.buyerDeposit{value: 1 ether}();assertEq(address(deposit).balance, 1 ether, "Contract balance did not increase"); // 检查合约的余额是否增加vm.stopPrank();// 三天后卖家提款vm.startPrank(SELLER); // msg.sender == SELLERvm.warp(1680616584 + 3 days + 1 seconds);deposit.sellerWithdraw(address(this));assertEq(address(deposit).balance, 0 ether, "Contract balance did not decrease"); // 检查合约的余额是否减少}function testBuyerDepositSellerWithdrawBefore3days() public startAtPresentDay {// 此测试检查买家存款后 3 天,卖家能够提款// 买家存款 1 ethervm.startPrank(buyer); // msg.sender == buyerdeposit.buyerDeposit{value: 1 ether}();assertEq(address(deposit).balance, 1 ether, "Contract balance did not increase"); // 检查合约的余额是否增加vm.stopPrank();// 三天前卖家提款vm.startPrank(SELLER); // msg.sender == SELLERvm.warp(1680616584 + 2 days);vm.expectRevert(); // 预期会回滚deposit.sellerWithdraw(address(this));}function testdepositTimeMatchesTimeofTransaction() public startAtPresentDay {// 此测试检查公共变量 depositTime 是否与交易时间匹配vm.startPrank(buyer); // msg.sender == buyerdeposit.buyerDeposit{value: 1 ether}();// 检查它是否存入于正确的时间assertEq(deposit.depositTime(buyer),1680616584, // startAtPresentDay 的时间"Time of Deposit Doesnt Match");vm.stopPrank();}function testUserDepositTwice() public startAtPresentDay {// 此测试检查用户不能重复存款 vm.startPrank(buyer); // msg.sender == buyerdeposit.buyerDeposit{value: 1 ether}();vm.warp(1680616584 + 1 days); // 一天后...vm.expectRevert();deposit.buyerDeposit{value: 1 ether}(); // 应该回滚因为未到 3 天}function testNonExistantContract() public startAtPresentDay {// 此测试检查卖家不能为不存在的地址提款vm.startPrank(SELLER); // msg.sender == SELLERvm.expectRevert();deposit.sellerWithdraw(buyer); }function testBuyerBuysAgain() public startAtPresentDay {// 此测试检查买家的条目是否被删除(这允许买家重新购买)vm.startPrank(buyer); // msg.sender == buyerdeposit.buyerDeposit{value: 1 ether}();vm.stopPrank();// 卖家提款vm.warp(1680616584 + 3 days + 1 seconds);vm.startPrank(SELLER); // msg.sender == SELLERdeposit.sellerWithdraw(buyer);vm.stopPrank();
// 检查 depositTime[buyer] == 0
assertEq(deposit.depositTime(buyer), 0, "买家的条目未被删除");// 买家再次存款
vm.startPrank(buyer); // msg.sender == buyer
vm.expectEmit();
emit Deposited(buyer);
deposit.buyerDeposit{value: 1 ether}();
vm.stopPrank();
}function testSellerWithdrawEmitted() public startAtPresentDay {
// 此测试检查 SellerWithdraw 事件是否被触发// buyer2 存款
vm.deal(buyer2, 1 ether); // msg.sender == buyer2
vm.startPrank(buyer2);
vm.expectEmit(); // 检查 Deposited 事件
emit Deposited(buyer2);
deposit.buyerDeposit{value: 1 ether}();
vm.stopPrank();vm.warp(1680616584 + 3 days + 1 seconds); // 3 天 1 秒后...// 卖家提款 + 检查 SellerWithdraw 事件是否被触发
vm.startPrank(SELLER); // msg.sender == SELLER
vm.expectEmit(); // 期望 SellerWithdraw 事件被触发
emit SellerWithdraw(buyer2, block.timestamp);
deposit.sellerWithdraw(buyer2);
vm.stopPrank();
}function testFakeSeller2Withdraw() public startAtPresentDay {
// 买家存款
vm.startPrank(buyer);
vm.deal(buyer, 2 ether); // 该合约地址是买家
deposit.buyerDeposit{value: 1 ether}();
vm.stopPrank();
assertEq(address(deposit).balance, 1 ether, "以太存款失败");vm.warp(1680616584 + 3 days + 1 seconds); // 3 天 1 秒后...vm.startPrank(FakeSELLER); // msg.sender == FakeSELLER
vm.expectRevert();
deposit.sellerWithdraw(buyer);
vm.stopPrank();
}function testRejectedWithdrawl() public startAtPresentDay {
// 此测试检查买家的条目是否被删除(这允许买家再次购买)vm.startPrank(buyer); // msg.sender == buyer
faildeposit.buyerDeposit{value: 1 ether}();
vm.stopPrank();
assertEq(address(faildeposit).balance, 1 ether, "断言失败");vm.warp(1680616584 + 3 days + 1 seconds); // 3 天 1 秒后...vm.startPrank(address(rejector)); // msg.sender == rejector
vm.expectRevert();
faildeposit.sellerWithdraw(buyer);
vm.stopPrank();
}
测试失败的以太转账
测试买家提款需要额外的技巧来获得完整的行覆盖率。以下是我们正在测试的代码片段,我们将在上面的代码中解释 Rejector 合约。
function buyerWithdraw() external {uint256 _depositTime = depositTime[msg.sender];require(_depositTime != 0, "sender did not deposit");require(block.timestamp - _depositTime <= 3 days);emit BuyerRefunded(msg.sender, block.timestamp);// 这是我们正在测试的分支(bool ok,) = msg.sender.call{value: 1 ether}("");require(ok, "Failed to withdraw");
}
为了测试 require(ok…) 的失败条件,我们需要让以太转账失败。测试通过创建一个调用 buyerWithdraw 函数的智能合约来实现这一点,但其 receive 函数设置为 revert。
Foundry 模糊测试
虽然我们可以指定一个不是卖家的任意地址来测试未授权地址提款的 revert,但尝试许多不同的值更令人放心。
如果我们为测试函数提供参数,Foundry 将尝试许多不同的参数值。为了防止它使用不适用于测试用例的参数(例如当地址被授权时),我们将使用 vm.assume。以下是如何测试未授权卖家的卖家提款。
// notSeller 将被随机选择
function testInvalidSellerAddress(address notSeller) public {vm.assume(notSeller != seller);vm.expectRevert("not the seller");depositContract.sellerWithdraw(notSeller);
}
以下是所有的状态转换
- 合约的
balance减少了 1 ether BuyerRefunded事件被触发- 买家可以在三天内退款
以下是需要测试的分支
- 买家不能在 3 天后提款
- 买家如果从未存款则不能提款
Console.log Foundry
要在 Foundry 中使用 console.log,请导入以下内容
import "forge-std/console.sol";
并使用以下命令运行测试
forge test -vv
测试签名
请参阅我们关于 solidity 签名验证 的教程,因此我们建议你参考该教程。
Solidity 测试内部函数
请参阅我们关于 solidity 测试内部函数 的教程。
使用 vm.deal 和 vm.hoax 设置地址余额
作弊码 vm.hoax 允许你同时恶作剧一个地址并设置其余额。
vm.hoax(addressToPrank, balanceToGive);
// 下一个调用是 addressToPrank 的恶作剧vm.deal(alice, balanceToGive);
Foundry 的一些常见错误
在接收以太时没有回退函数
如果你正在测试从合约中提取以太,它将被发送到运行测试的合约。Foundry 测试本身是一个智能合约,如果你将以太发送到没有 fallback 或 receive 函数的智能合约,则交易将失败。确保在合约中有一个 fallback 或 receive 函数。
在接收代币时没有 onERC…Received
同样,ERC-721 safeTransferFrom 和 ERC-1155 transferFrom 在将代币发送到没有适当传输钩子函数的智能合约时会回滚。如果你想测试将 NFT(或类似 ERC777 的代币)转移给自己,你需要将其添加到测试中。
总结
- 目标是 100% 的行和分支覆盖率
- 完全定义预期的状态转换
- 在断言中使用错误消息
了解更多
要了解超越单元测试和基本模糊测试的高级 solidity 测试,https://t.me/gtokentool。
相关文章:
Foundry 单元测试
安装 Foundry 如果你还没有安装 Foundry,请按照此处的说明进行操作:Foundry 安装 Foundry Hello World 只需运行以下命令,它将为你设置环境,创建测试并运行它们。(当然,这假设你已经安装了 Foundry&…...
idea database连接数据库后看不到表解决方法、格式化sql快捷键
最下面那个勾选上就可以了 或 格式化sql快捷键: 先选中, 使用快捷键格式化 SQL: Windows/Linux: Ctrl Alt L macOS: Cmd Alt L...
【数学二】线性代数-向量-向量组的秩、矩阵得秩
考试要求 1、理解 n n n维向量、向量的线性组合与线性表示的概念. 2、理解向量组线性相关、线性无关的概念,掌握向量组线性相关、线性无关的有关性质及判别法. 3、了解向量组的极大线性无关组和向量组的秩的概念,会求向量组的极大线性无关组及秩. 4、了解向量组等价的概念,…...
ABAP开发-内存管理
系列文章目录 文章目录 系列文章目录前言一、概述二、程序间调用三、外部会话和内部会话四、SAP内存与ABAP内存五、实例总结 前言 一、概述 内存是程序之间为了传递数据而使用的共享存储空间,在每个程序里使用的内存有SAP内存和ABAP内存 SAP内存分类 SAP内存 主会…...
【Ajax】跨域
文章目录 1 同源策略1.1 index.html1.2 server.js 2 如何解决跨域2.1 JSONP2.1.1 JSONP 原理2.1.2 JSONP 实践2.1.3 jQuery 中的 JSONP 2.2 CORS2.2.1 CORS实践 3 server.js 1 同源策略 同源策略(Same-Origin Policy)最早由 Netscape 公司提出,是浏览器的一种安全策…...
yii 常用一些调用
yii 常用一些调用 (增加中) 调用YII框架中 jquery:Yii::app()->clientScript->registerCoreScript(‘jquery’); framework/web/js/source的js,其中registerCoreScript key调用的文件在framework/web/js/packages.php列表中可以查看 在view中得到当前contro…...
网页版五子棋——用户模块(服务器开发)
前一篇文章:网页版五子棋—— WebSocket 协议-CSDN博客 目录 前言 一、编写数据库代码 1.数据库设计 2.配置 MyBatis 3.创建实体类 4.创建 UserMapper 二、前后端交互接口 1.登录接口 2.注册接口 3.获取用户信息 三、服务器开发 1.代码编写 2.测试后端…...
以RK3568为例,ARM核心板如何实现NTP精准时间同步?
背景 网络时间协议NTP(Network TimeProtocol)是用于互联网中时间同步的标准互联网协议,可以把计算机的时间同步到某些时间标准。NTP对于我们产品来说有什么用呢,简单的讲,当你的设备时间不准确了,你可以接…...
Twitter(X)2024最新注册教程
Twitter 现名为X,因为图标是一只小鸟的形象,大家也叫它小蓝鸟(埃隆马斯克于 2023 年对该平台进行了品牌重塑),目前仍然是全球最受欢迎的社交媒体和微博平台之一,全球活跃用户量大概在4.5亿。尤其是欧美国家…...
10.桥接模式设计思想
10.桥接模式设计思想 目录介绍 01.桥接模式基础 1.1 桥接模式由来1.2 桥接模式定义1.3 桥接模式场景1.4 桥接模式思考1.5 解决的问题 02.桥接模式实现 2.1 罗列一个场景2.2 桥接结构2.3 桥接基本实现2.4 有哪些注意点 03.桥接实例演示 3.1 需求分析3.2 代码案例实现3.3 是否可…...
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
这里是Themberfue 在上一节的最后,我们讨论两个线程同时对一个变量累加所产生的现象 在这一节中,我们将更加详细地解释这个现象背后发生的原因以及该如何解决这样类似的现象 线程安全问题 public class Demo15 {private static int count 0;public …...
(已解决)Dependency “ ” not found 细谈
剖析原因:依赖在pom文件中引用后,然后ReLoad,此依赖会在你配置的本地仓库里面找,并下载下来,他报not found就是没有找到。 本地仓库的位置: 进一步深究:在本地仓库找的时候,他又会…...
网络编程、UDP、TCP、三次握手、四次挥手
一、初识网络编程 网络编程的概念:在网络通信协议下,不同计算机上运行的程序,进行的数据传输。 应用场景:即时通信、网游对战、金融证券、国际贸易、邮件等等。 不管是什么场景,都是计算机和计算机之间通过网络进行…...
程序员的生活周刊 #7:耐克总裁被裁记
0. 庙宇 这张图来自 Tianshu Liu, 被树木环绕的宝塔庙宇 1. 耐克总裁 耐克最近的总裁 John Donahoe 干了 5 年,终于被裁掉了。 这位总裁即不了解球鞋文化,也没有零售经验,但不懂事的董事会还是聘用它,寄托把耐克从运…...
sparkSQL的UDF,最常用的regeister方式自定义函数和udf注册方式定义UDF函数 (详细讲解)
- UDF:一对一的函数【User Defined Functions】 - substr、split、concat、instr、length、from_unixtime - UDAF:多对一的函数【User Defined Aggregation Functions】 聚合函数 - count、sum、max、min、avg、collect_set/list - UDTF:…...
【Ubuntu20】VSCode Python代码规范工具配置 Pylint + Black + MyPy + isort
常用工具: 在 Ubuntu20 下,有以下常见的 Python 代码工具: 静态分析工具: Pylint 和 Flake8 功能范围:Pylint功能非常强大,能够检查代码质量、潜在错误、代码风格、复杂度等多个方面, 并生成详细的报…...
游戏提示错误:xinput1_3.dll缺失?四种修复错误的xinput1_3.dll文件
在计算机的运行过程中,我们可能会遇到各种各样的问题,其中与“xinput1_3.dll”相关的问题也并不罕见。“xinput1_3.dll”是一个在许多游戏和多媒体应用程序运行过程中可能会用到的动态链接库文件。当我们启动某些游戏时,可能会突然弹出一个错…...
YOLOv11融合IncepitonNeXt[CVPR2024]及相关改进思路
YOLOv11v10v8使用教程: YOLOv11入门到入土使用教程 一、 模块介绍 论文链接:https://arxiv.org/abs/2303.16900 代码链接:https://github.com/sail-sg/inceptionnext 论文速览:受 ViT 长距离建模能力的启发,大核卷积…...
[Web安全 网络安全]-学习文章汇总导航(持续更新中)
文章目录: 一:学习路线资源 1.路线 2.资源 二:工具 三:学习笔记 1.基础阶段 2.进阶阶段 四:好的参考 五:靶场 博主对网络安全很感兴趣,但是不知道如何取学习,自己一步一步…...
Docker Compose部署Rabbitmq(Docker file安装延迟队列)
整个工具的代码都在Gitee或者Github地址内 gitee:solomon-parent: 这个项目主要是总结了工作上遇到的问题以及学习一些框架用于整合例如:rabbitMq、reids、Mqtt、S3协议的文件服务器、mongodb github:GitHub - ZeroNing/solomon-parent: 这个项目主要是…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)
推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...
FFmpeg avformat_open_input函数分析
函数内部的总体流程如下: avformat_open_input 精简后的代码如下: int avformat_open_input(AVFormatContext **ps, const char *filename,ff_const59 AVInputFormat *fmt, AVDictionary **options) {AVFormatContext *s *ps;int i, ret 0;AVDictio…...
