{"content":{"title":"Aptos合约开发之单元测试和函数调用","body":"## 一、前言\r\n在我们开始动手对Resource进行增删改查等操作之前，我们有必要先熟悉Move和Aptos的单元测试和函数调用。否则，我们无法保障程序的质量。\r\n\r\n## 二、关键词\r\n模块化、模块和脚本、Move单元测试、Aptos单元测试、函数调用、脚本调用\r\n\r\n## 三、模块化\r\n什么是模块化？对搞过工程项目的来讲，模块化并不陌生。模块化就是把功能相似的代码都放在一起，要用的时候能快速找到。\r\n\r\n在Java web项目里，可以创建Component、Service、Controller、Repository等文件夹来实现模块化。\r\n\r\n在Python项目里，一个.py文件就是一个模块。\r\n\r\n在Move项目里，用`Module`关键字来表示模块。而用Script脚本表示传统语言（python、java、c等）中`main`函数的概念。因为一个程序只能有一个main函数。所以，单个Script脚本文件中的函数数量不能超过一个，这是再自然不过的事情。\r\n\r\n>Move has two different types of programs: **Modules** and **Scripts**. Modules are libraries that define struct types along with functions that operate on these types. \r\nScripts are executable entrypoints similar to a main function in a conventional language. ---引自《Move Book》\r\n\r\n\r\n## 四、Script脚本\r\nScript脚本可以理解为main函数，在脚本里可以进行模块函数调用。\r\n```\r\n\/\/sources\/debug_script.move\r\nscript {\r\n    use std::debug;\r\n    const ONE: u64 = 1;\r\n    \/\/ bool literals (true, false)\r\n    \/\/ u64 literals (e.g., 10, 58)\r\n    \/\/ address literals (e.g., 0x12, 0x0000000000000000000000000000000f)\r\n    \/\/ hexadecimal strings (e.g., 'x\"0012\"' will parse as the vector<u8> value [00, 12])\r\n    \/\/ ASCII strings (e.g., 'b\"hi\"' will parse as the vector<u8> value [68, 69])\r\n    fun debug_script(x:u64 , y:bool ,addr:address) {\r\n        let sum = x + ONE;\r\n        debug::print(&sum);\r\n        debug::print(&y);\r\n        debug::print(&addr);\r\n    }\r\n}\r\n```\r\n### 4.1 在沙盒环境执行script脚本\r\n控制台输入`move sandbox run sources\/debug_script.move --args  33  true  0xf `。\r\n通过使用`--args`向脚本main函数传参。执行结果如下：\r\n```\r\nouhuang@LAPTOP-HM465BTT:~\/learn_move$ move sandbox run sources\/debug_script.move --args  33  true  0xf  \r\n[debug] 34\r\n[debug] true\r\n[debug] 0000000000000000000000000000000F\r\n```\r\n>注意！script脚本的特点\r\n\r\n- 只能定义一个fun\r\n- 该函数不能有返回值\r\n- Scripts have very limited power—they cannot declare friends, struct types or access global storage. Their primary purpose is to invoke module functions.\r\n\r\n## 五、Module模块\r\n`module Test{...}`模块嵌套在`address 0x4{...}`里，这种写法表示Test模块经publish后会被放到账户0x4的名下。所以，才可以使用`0x4::Test::函数名`这种形式进行调用。\r\n```\r\n\/\/ sources\/Test.move\r\nmodule 0x4::Test {\r\n    use std::signer;\r\n    use std::debug;\r\n\r\n    struct Resource has key { i: u64 }\r\n\r\n    public fun publish(account: &signer) {\r\n        move_to(account, Resource { i: 10 });\r\n    }\r\n    \/\/Comments must be written in English, not in Chinese \r\n    public fun write(account: &signer, i: u64) acquires Resource {\r\n        if (!exists<Resource>(signer::address_of(account))){\r\n            debug::print(&b\"not exists\");\r\n            move_to(account, Resource { i: 11 });\r\n        }else{\r\n            borrow_global_mut<Resource>(signer::address_of(account)).i = i;\r\n            debug::print(&borrow_global_mut<Resource>(signer::address_of(account)).i);\r\n        }\r\n\r\n    }\r\n\r\n    public fun unpublish(account: &signer) acquires Resource {\r\n        let Resource { i: _ } = move_from(signer::address_of(account));\r\n    }\r\n\r\n    #[test(account = @0xf)]\r\n    fun  test_publish(account: signer) {\r\n        publish(&account);\r\n    }\r\n  \r\n    #[test(account = @0xf)]\r\n    fun test_write(account: signer) acquires Resource {\r\n        publish(&account);\r\n        write(&account, 333);\r\n    }\r\n}\r\n```\r\n\r\n### 5.1 模块编译\r\n使用`move build`编译**sources**下的move代码。\r\n```\r\nouhuang@LAPTOP-HM465BTT:~\/learn_move$ move build\r\nINCLUDING DEPENDENCY MoveNursery\r\nINCLUDING DEPENDENCY MoveStdlib\r\nBUILDING learn_move\r\n```\r\n### 5.2 模块发布\r\n在沙盒环境使用`move sandbox publish -v`发布字节码到0x4名下。\r\n```\r\nmove sandbox publish -v\r\n\r\nouhuang@LAPTOP-HM465BTT:~\/learn_move$ move sandbox publish -v\r\nFound 1 modules\r\nUpdating an existing module 00000000000000000000000000000004::Test (wrote 354 bytes)\r\nWrote 354 bytes of module ID's and code\r\n```\r\n检查0x4地址下是否保存Test模块。\r\n```\r\nouhuang@LAPTOP-HM465BTT:~\/learn_move$ ls storage\/0x00000000000000000000000000000004\/modules\r\nTest.mv\r\n```\r\n### 5.3 模块单元测试\r\n\r\n在Move语言里，编写单元测试函数需要注意以下几点：\r\n\r\n- test传参只能传signer，不能传其他类型的参数\r\n- 每个测试结束后，会清除相关的上下文。所以write的前提需要先publish。\r\n\r\n以上两点参考自《Move Book》：https:\/\/diem.github.io\/move\/unit-testing.html\r\n>Only parameters with a type of **signer** are supported as test parameters. If a non-signer parameter is supplied, the test will result in an error when run.\r\n\r\n使用`move test`执行单元测试函数。\r\n```\r\nlet value : u64  = 444;\r\n#[test(account = @0xf , i = @value)]\r\nfun test_write(account: signer,i: u64) acquires Resource {\r\n    write(&account,i);\r\n}\r\n\/\/错误 只能传递signer参数\r\n\r\n#[test(account = @0xf)]\r\nfun test_write(account: signer) acquires Resource {\r\n    write(&account, 33);\r\n}\r\n\/\/错误  每个测试结束后，会清除相关的上下文.也就是说每个\r\n测试是独立的。write的前提需要先执行publish。\r\n\r\n#[test(account = @0xf)]\r\nfun test_write(account: signer) acquires Resource {\r\n    publish(&account);\r\n    write(&account, 33);\r\n}\r\n\/\/ 正确  执行结果如下所示：\r\nouhuang@LAPTOP-HM465BTT:~\/learn_move$ move test\r\nINCLUDING DEPENDENCY MoveNursery\r\nINCLUDING DEPENDENCY MoveStdlib\r\nBUILDING learn_move\r\nRunning Move unit tests\r\n[ PASS    ] 0x4::Test::test_publish\r\n[debug] 335\r\n[ PASS    ] 0x4::Test::test_write\r\nTest result: OK. Total tests: 2; passed: 2; failed: 0\r\n```\r\n### 5.4 模块函数调用\r\n编写脚本，调用0x4名下的Test模块的publish函数。\r\n```\r\n\/\/sources\/test_script.move\r\nscript {\r\n    use std::debug;\r\n    use 0x4::Test;\r\n\r\n    fun debug_script(account: signer) {\r\n        Test::publish(&account);\r\n        Test::write(&account, 333);\r\n        debug::print(&b\"Success!\");\r\n    }\r\n}\r\n```\r\n#### 5.4.1 执行函数调用脚本\r\n`move sandbox run sources\/test_script.move   --signers 0xf -v --dry-run`在这条命令中，如果去掉`--dry-run`参数执行，会立即提交结果。\r\n```\r\nouhuang@LAPTOP-HM465BTT:~\/learn_move$ move sandbox run sources\/test_script.move   --signers 0xf -v --dry-run\r\n[debug] 333\r\n[debug] (&) [83, 117, 99, 99, 101, 115, 115, 33]\r\nChanged resource(s) under 1 address(es):\r\n  Changed 1 resource(s) under address 0000000000000000000000000000000F:\r\n    Added type 0x4::Test::Resource: [77, 1, 0, 0, 0, 0, 0, 0] (wrote 40 bytes)\r\n      key 0x4::Test::Resource {\r\n          i: 333\r\n      }\r\nWrote 40 bytes of resource ID's and data\r\nDiscarding changes; re-run without --dry-run if you would like to keep them.\r\n```\r\n\r\n## 六、Aptos项目\r\nAptos是基于Move语言的智能合约框架，链上的合约是要被调用的，用户可以直接调用被`entry`关键字修饰的module中的函数。\r\n>**set_message** is a script function allowing it to be called directly by transactions.\r\n\r\n### 6.1 单元测试\r\n在Aptos框架下，单元测试函数写法和move的写法相同，唯一不同的是，单元测试执行的命令不同。\r\n\r\nmove项目是`move test`。在aptos框架下是`aptos move test --package-dir .`。\r\n```\r\nouhuang@LAPTOP-HM465BTT:~\/learn_move$ aptos move test --package-dir .\r\nINCLUDING DEPENDENCY MoveNursery\r\nINCLUDING DEPENDENCY MoveStdlib\r\nBUILDING learn_move\r\nRunning Move unit tests\r\n[ PASS    ] 0x4::Test::test_publish\r\n[debug] 335\r\n[ PASS    ] 0x4::Test::test_write\r\nTest result: OK. Total tests: 2; passed: 2; failed: 0\r\n{\r\n  \"Result\": \"Success\"\r\n}\r\n\r\nouhuang@LAPTOP-HM465BTT:~\/learn_move$ move test\r\nINCLUDING DEPENDENCY MoveNursery\r\nINCLUDING DEPENDENCY MoveStdlib\r\nBUILDING learn_move\r\nRunning Move unit tests\r\n[ PASS    ] 0x4::Test::test_publish\r\n[debug] 335\r\n[ PASS    ] 0x4::Test::test_write\r\nTest result: OK. Total tests: 2; passed: 2; failed: 0\r\n```\r\n### 6.2 函数调用\r\n经过`aptos move publish`后的合约会部署在Aptos链上，对链上合约的函数调用如果会改变状态，则被叫做**发起交易**。这个概念和Solidity是一样的。调用函数的方式有以下三种：\r\n\r\n- 使用区块链浏览器链接钱包后进行调用\r\n- 在终端使用命令进行调用\r\n- 通过python、js、rust的sdk进行调用\r\n\r\n>例：python sdk地址：https:\/\/aptos.dev\/sdks\/python-sdk\r\n\r\n```\r\n\/\/合约编译\r\nsudo aptos move compile --package-dir contract\/sources\/\r\n\r\n\/\/合约测试\r\nsudo aptos move test --package-dir contract\/\r\n\r\n\/\/合约发布\r\nsudo aptos move publish  --package-dir .\r\n\r\n\/\/函数调用：在终端，使用命令调用某个地址下的函数。\r\nsudo aptos move run --function-id fbd4140a63e8fe7d6e9e25aff78e11876b1b6c21bc8d34b51cbfff40ca03164b::Test2::publish \r\n```\r\n至此，我们对Move项目和Aptos项目的单元测试及函数调用的研究就暂时告一段落。接下来是对Resource的操作教程。相信很快我们就能发现自己的ERC-20合约。^_^\r\n## 七、参考文档：\r\n- https:\/\/diem.github.io\/move\/unit-testing.html\r\n- https:\/\/github.com\/move-language\/move\/tree\/main\/language\/tools\/move-cli#installation\r\n- https:\/\/aptos.dev\/cli-tools\/aptos-cli-tool\/use-aptos-cli#compiling--unit-testing-move\r\n- https:\/\/aptos.dev\/sdks\/python-sdk"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/6721","address":null},"history":null,"timestamp":1663117965,"version":1}