申明:本文转自学习视频bilibiliUp主:夏夜書
参考文章:
[1].https://blog.csdn.net/aaa19890808/article/details/79342259/
[2]https://blog.csdn.net/qq_33829547/article/details/80459480?utm_source=blogxgwz0
源码取自:https://github.com/xieyueshu/Billboard/blob/master/Billboard_test.sol
基于新版的remix开发Remix-Ethereum
一.代码讲解
pragma solidity >=0.4.22 <0.7.0;//solidity版本
contract Billboard {
struct App { //定义一个app的结构体包含app的名字,开发者地址,评分的星星数
string name;
address owner;
uint8[] stars;
mapping(address => uint256) starOf;//难点【1】mapping
uint totalStar;//打分总数
}
App[] public apps;
function publish(string memory name) public {//难点【2】memory*函数中声明并创建结构体需要使用memory关键字
apps.push(
App(
name,
msg.sender,
new uint8[](1),0));//注意点,这里把数组第一个占有可以避免同一个用户打分两次原因暂时没搞懂
}
function star(uint appId,uint8 num) public {//传入评分的id号,和评分的星星数
require(num>=1 && num <=5);//设置两个要求来判断打分的合法性。1.打分必须在1-5
require(apps[appId].starOf[msg.sender]==0);//没有理解
App storage app = apps[appId];
app.stars.push(num);
app.totalStar += num;
app.starOf[msg.sender]=app.stars.length-1;
}
function top() public view returns (uint[] memory topIds)//pai xu
{
topIds = new uint[](10);
for(uint appId=1;appId<apps.length;appId++){
//
uint topLast = appId<topIds.length?appId:topIds.length-1;
if(appId>=topIds.length && apps[appId].totalStar<=apps[topIds[topLast]].totalStar){
continue;
}
//
topIds[topLast] = appId;
for(uint i=topLast;i>0;i--){
if(apps[topIds[i]].totalStar>apps[topIds[i-1]].totalStar){
uint tempAppId = topIds[i];
topIds[i] = topIds[i-1];
topIds[i-1] = tempAppId;
}else{
continue;
}
}
}
}
}
主要重难点讲解:
1.mapping[1]
solidity里的映射可以理解为python里的字典,建立键-值的对应关系,可以通过键来查找值,键必须是唯一的,但值可以重复。
定义方式为:mapping(键类型=>值类型),例如mapping(address=>uint) public balances,这个映射的名字是balances,权限类型为public,键的类型是地址address,值的类型是整型uint,在solidity中这个映射的作用一般是通过地址查询余额。键的类型允许除映射外的所有类型。
例如:映射balances中包括三个键值对(user1:100,user2:145,user3:195),输入user2即可得到145
下面来看一个例子:
contract MappingExample{
mapping(address => uint) public balances;
function update(uint amount) returns (address addr){
balances[msg.sender] = amount;
return msg.sender;
}
}
说明:定义balances为一个映射,msg.sender是合约创建者的地址,函数update有一个整型参数amount(数量),
balances[msg.sender]=amount的意思是将参数amount的值和msg.sender这个地址对应起来。
映射类型,仅能用来定义状态变量,或者是在内部函数中作为storage类型的引用。引用是指你可以声明一个,如var storage mappVal的用于存储状态变量的引用的对象,但你没办法使用非状态变量来初始化这个引用.
回到本文的例子:
mapping(address => uint256) starOf;
这里也就是这个映射的名字是starOf,权限类型这里没有定义(默认一般为public)。键的类型是地址address,值的类型是整型uint256,这里定义这个的映射是为了后面方便将用户打得分映射给对应的app地址。
2.mapping和storage详细介绍[2]
在 Solidity 中,有两个地方可以存储变量 —— storage以及memory。
Storage 变量是指永久存储在区块链中的变量。 Memory 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。
状态变量(在函数之外声明的变量)默认为“storage”形式,并永久写入区块链;而在函数内部声明的变量默认是“memory”型的,它们函数调用结束后消失。
简而言之:
Storage是指针传递
Memory是值传递
回到本文,总共有两次出现了memory和storage
1.在publish方法中有使用memory
function publish(string memory name) public {//难点【2】memory
apps.push(
App(
name,
msg.sender,
new uint8[](1),0));
}
这是因为:*函数中声明并创建结构体需要使用memory关键字
2.在star打分方法中有使用storage
function star(uint appId,uint8 num) public {//传入评分的id号,和评分的星星数
require(num>=1 && num <=5);
require(apps[appId].starOf[msg.sender]==0);
App storage app = apps[appId];//重点
app.stars.push(num);
app.totalStar += num;
app.starOf[msg.sender]=app.stars.length-1;
}
这里app必须要用storage类型保存。因为后面我们要将对应的app中的分值进行一个统计,排序。也就是每一个app的分数要在top这个函数中被引用。如果采用memory后面无法调用,也就没有办法进行排序。所以这里必须用storage类型
二.如何进行测试
测试代码如下:
pragma solidity >=0.4.0 <0.7.0;
import "remix_tests.sol"; // this import is automatically injected by Remix.
import "./Billboard.sol";//this file is your test file
// file name has to end with '_test.sol'
contract Billboard_test_1 {
Billboard billboard;
string[] names = ["N1","N2","N3","N4","N5","N6","N7","N8","N9","N10", "N11","N12","N13"];//app name
uint[] starAppIds = [10,4,5,6,2];//
uint8[] appStars = [5,4,3,2,1];
function beforeAll() public {
// here should instantiate tested contract
billboard = new Billboard();
for(uint i=0;i<names.length;i++){
billboard.publish(names[i]);
}
for(uint i=0;i<starAppIds.length;i++){
billboard.star(starAppIds[i],appStars[i]);
}
}
function checkStar() public returns (bool) {
// use 'Assert' to test the contract
for(uint i=0;i<starAppIds.length;i++){
(,,uint appTotalStar) = billboard.apps(starAppIds[i]);
Assert.equal(appTotalStar, appStars[i], "Star set error.");
}
return true;
}
function checkTop() public returns (bool) {
// use the return value (true or false) to test the contract
uint[] memory topAppIds = billboard.top();
for(uint i=0;i<starAppIds.length;i++){
Assert.equal(topAppIds[i], starAppIds[i], "Top order error.");
}
return true;
}
}
以上就是测试的全过程。关于测试代码的编写还需多在实际代码中多去实践。