第五章:测试与部署

在前面的章节中,我们已经完成了去中心化投票系统的智能合约开发和前端实现。在本章中,我们将重点关注如何对系统进行全面测试,并将其部署到以太坊测试网和主网上。
在这里插入图片描述

5.1 测试策略

一个完整的测试策略应该包括多个层次的测试,确保系统的各个部分都能正常工作,并且它们能够协同工作。对于我们的去中心化投票系统,我们将实施以下测试策略:

5.1.1 智能合约测试

智能合约测试是最关键的部分,因为一旦部署到区块链上,合约代码就无法更改。我们将使用Hardhat和Chai进行智能合约测试,测试内容包括:

  1. 单元测试:测试每个合约函数的正确行为
  2. 集成测试:测试合约之间的交互
  3. 边界条件测试:测试极端情况和边界条件
  4. 安全测试:测试合约的安全性,包括权限控制、重入攻击防护等

5.1.2 前端测试

前端测试确保用户界面能够正确地与智能合约交互,并提供良好的用户体验。我们将使用Jest和React Testing Library进行前端测试,测试内容包括:

  1. 组件测试:测试UI组件的渲染和交互
  2. 钩子测试:测试自定义钩子的逻辑
  3. 集成测试:测试前端与智能合约的交互
  4. 端到端测试:模拟用户操作,测试整个应用流程

5.1.3 用户验收测试

用户验收测试是确保系统满足用户需求的最后一道防线。我们将在测试网上部署系统,并邀请真实用户进行测试,收集反馈并进行改进。

5.2 智能合约测试实现

在这一节中,我们将实现智能合约的测试用例,确保合约的功能正确性和安全性。

5.2.1 设置测试环境

首先,我们需要设置测试环境。在项目根目录下创建test文件夹,并在其中创建测试文件。

// test/VotingSystem.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("VotingSystem", function () {
  let VotingSystem;
  let votingSystem;
  let owner;
  let voter1;
  let voter2;
  let nonVoter;
  
  beforeEach(async function () {
    // 获取合约工厂
    VotingSystem = await ethers.getContractFactory("VotingSystem");
    
    // 获取测试账户
    [owner, voter1, voter2, nonVoter] = await ethers.getSigners();
    
    // 部署合约
    votingSystem = await VotingSystem.deploy();
    await votingSystem.deployed();
    
    // 添加投票者
    await votingSystem.addVoter(voter1.address);
    await votingSystem.addVoter(voter2.address);
  });
  
  // 测试用例将在这里添加
});

5.2.2 测试合约部署

首先,我们测试合约部署是否正确,包括所有者设置和初始状态。

it("Should set the right owner", async function () {
  expect(await votingSystem.owner()).to.equal(owner.address);
});

it("Should initialize with zero ballots", async function () {
  expect(await votingSystem.ballotCount()).to.equal(0);
});

it("Should grant voting rights to added voters", async function () {
  expect(await votingSystem.hasVotingRights(voter1.address)).to.be.true;
  expect(await votingSystem.hasVotingRights(voter2.address)).to.be.true;
  expect(await votingSystem.hasVotingRights(nonVoter.address)).to.be.false;
});

5.2.3 测试投票创建和管理

接下来,我们测试投票的创建和管理功能。

it("Should create a new ballot", async function () {
  const title = "Test Ballot";
  const description = "This is a test ballot";
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400; // 1 day later
  
  await votingSystem.createBallot(title, description, startTime, endTime);
  
  expect(await votingSystem.ballotCount()).to.equal(1);
  
  const [ballotTitle, ballotDesc, ballotStart, ballotEnd, finalized, creator] = 
    await votingSystem.getBallotDetails(0);
  
  expect(ballotTitle).to.equal(title);
  expect(ballotDesc).to.equal(description);
  expect(ballotStart).to.equal(startTime);
  expect(ballotEnd).to.equal(endTime);
  expect(finalized).to.be.false;
  expect(creator).to.equal(owner.address);
});

it("Should add candidates to a ballot", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  // 添加候选人
  await votingSystem.addCandidate(0, "Candidate 1", "Info 1");
  await votingSystem.addCandidate(0, "Candidate 2", "Info 2");
  
  expect(await votingSystem.candidateCounts(0)).to.equal(2);
  
  const [name1, info1, votes1] = await votingSystem.getCandidateDetails(0, 0);
  expect(name1).to.equal("Candidate 1");
  expect(info1).to.equal("Info 1");
  expect(votes1).to.equal(0);
  
  const [name2, info2, votes2] = await votingSystem.getCandidateDetails(0, 1);
  expect(name2).to.equal("Candidate 2");
  expect(info2).to.equal("Info 2");
  expect(votes2).to.equal(0);
});

it("Should not allow non-owners to create ballots", async function () {
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  
  await expect(
    votingSystem.connect(voter1).createBallot("Test", "Test", startTime, endTime)
  ).to.be.revertedWith("Ownable: caller is not the owner");
});

it("Should not allow non-owners to add candidates", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  await expect(
    votingSystem.connect(voter1).addCandidate(0, "Candidate", "Info")
  ).to.be.revertedWith("Ownable: caller is not the owner");
});

5.2.4 测试投票过程

现在,我们测试投票过程,包括投票权验证、投票记录和重复投票检查。

it("Should allow voters to cast votes", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  // 添加候选人
  await votingSystem.addCandidate(0, "Candidate 1", "Info 1");
  await votingSystem.addCandidate(0, "Candidate 2", "Info 2");
  
  // 投票
  await votingSystem.connect(voter1).vote(0, 0);
  await votingSystem.connect(voter2).vote(0, 1);
  
  // 验证投票结果
  const [, , votes1] = await votingSystem.getCandidateDetails(0, 0);
  const [, , votes2] = await votingSystem.getCandidateDetails(0, 1);
  
  expect(votes1).to.equal(1);
  expect(votes2).to.equal(1);
  
  // 验证投票记录
  expect(await votingSystem.hasVotedInBallot(0, voter1.address)).to.be.true;
  expect(await votingSystem.hasVotedInBallot(0, voter2.address)).to.be.true;
});

it("Should not allow non-voters to cast votes", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  // 添加候选人
  await votingSystem.addCandidate(0, "Candidate 1", "Info 1");
  
  // 尝试投票
  await expect(
    votingSystem.connect(nonVoter).vote(0, 0)
  ).to.be.revertedWith("No voting rights");
});

it("Should not allow voters to vote twice in the same ballot", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  // 添加候选人
  await votingSystem.addCandidate(0, "Candidate 1", "Info 1");
  
  // 第一次投票
  await votingSystem.connect(voter1).vote(0, 0);
  
  // 尝试第二次投票
  await expect(
    votingSystem.connect(voter1).vote(0, 0)
  ).to.be.revertedWith("Already voted in this ballot");
});

5.2.5 测试投票结束和结果验证

最后,我们测试投票结束和结果验证功能。

it("Should finalize a ballot", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  // 添加候选人
  await votingSystem.addCandidate(0, "Candidate 1", "Info 1");
  await votingSystem.addCandidate(0, "Candidate 2", "Info 2");
  
  // 投票
  await votingSystem.connect(voter1).vote(0, 0);
  await votingSystem.connect(voter2).vote(0, 1);
  
  // 结束投票
  await votingSystem.finalizeBallot(0);
  
  // 验证投票已结束
  const [, , , , finalized] = await votingSystem.getBallotDetails(0);
  expect(finalized).to.be.true;
});

it("Should not allow voting after ballot is finalized", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  // 添加候选人
  await votingSystem.addCandidate(0, "Candidate 1", "Info 1");
  
  // 结束投票
  await votingSystem.finalizeBallot(0);
  
  // 尝试投票
  await expect(
    votingSystem.connect(voter1).vote(0, 0)
  ).to.be.revertedWith("Ballot is finalized");
});

it("Should not allow non-owners to finalize a ballot", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  // 尝试结束投票
  await expect(
    votingSystem.connect(voter1).finalizeBallot(0)
  ).to.be.revertedWith("Ownable: caller is not the owner");
});

5.2.6 测试事件

我们还应该测试合约是否正确地触发了事件。

it("Should emit BallotCreated event", async function () {
  const title = "Test Ballot";
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  
  await expect(votingSystem.createBallot(title, "Test", startTime, endTime))
    .to.emit(votingSystem, "BallotCreated")
    .withArgs(0, title, owner.address);
});

it("Should emit CandidateAdded event", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  const candidateName = "Candidate 1";
  
  await expect(votingSystem.addCandidate(0, candidateName, "Info"))
    .to.emit(votingSystem, "CandidateAdded")
    .withArgs(0, 0, candidateName);
});

it("Should emit VoteCast event", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  // 添加候选人
  await votingSystem.addCandidate(0, "Candidate 1", "Info");
  
  await expect(votingSystem.connect(voter1).vote(0, 0))
    .to.emit(votingSystem, "VoteCast")
    .withArgs(0, 0, voter1.address);
});

it("Should emit BallotFinalized event", async function () {
  // 创建投票
  const startTime = Math.floor(Date.now() / 1000);
  const endTime = startTime + 86400;
  await votingSystem.createBallot("Test", "Test", startTime, endTime);
  
  await expect(votingSystem.finalizeBallot(0))
    .to.emit(votingSystem, "BallotFinalized")
    .withArgs(0);
});

5.2.7 运行测试

要运行测试,我们可以使用Hardhat的测试命令:

npx hardhat test

如果所有测试都通过,我们将看到类似以下的输出:

  VotingSystem
    ✓ Should set the right owner
    ✓ Should initialize with zero ballots
    ✓ Should grant voting rights to added voters
    ✓ Should create a new ballot
    ✓ Should add candidates to a ballot
    ✓ Should not allow non-owners to create ballots
    ✓ Should not allow non-owners to add candidates
    ✓ Should allow voters to cast votes
    ✓ Should not allow non-voters to cast votes
    ✓ Should not allow voters to vote twice in the same ballot
    ✓ Should finalize a ballot
    ✓ Should not allow voting after ballot is finalized
    ✓ Should not allow non-owners to finalize a ballot
    ✓ Should emit BallotCreated event
    ✓ Should emit CandidateAdded event
    ✓ Should emit VoteCast event
    ✓ Should emit BallotFinalized event

  17 passing (3s)

5.3 前端测试实现

在这一节中,我们将实现前端的测试用例,确保用户界面能够正确地与智能合约交互。

5.3.1 设置前端测试环境

首先,我们需要设置前端测试环境。我们将使用Jest和React Testing Library进行测试。

npm install --save-dev jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdom

然后,在package.json中添加测试脚本:

"scripts": {
  "test": "jest"
}

创建Jest配置文件jest.config.js

module.exports = {
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
  transform: {
    '^.+\\.(js|jsx)$': 'babel-jest',
  },
  moduleNameMapper: {
    '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
  },
};

创建src/setupTests.js文件:

import '@testing-library/jest-dom';

5.3.2 测试Web3Context

首先,我们测试Web3Context,确保它能够正确地管理与区块链的连接。

// src/__tests__/contexts/Web3Context.test.js
import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import Web3Provider, { Web3Context } from '../../contexts/Web3Context';

// 模拟ethers库
jest.mock('ethers', () => ({
  ethers: {
    providers: {
      Web3Provider: jest.fn().mockImplementation(() => ({
        getSigner: jest.fn().mockReturnValue({
          getAddress: jest.fn().mockResolvedValue('0x123...'),
        }),
      })),
    },
    Contract: jest.fn().mockImplementation(() => ({
      owner: jest.fn().mockResolvedValue('0x123...'),
      hasVotingRights: jest.fn().mockResolvedValue(true),
    })),
  },
}));

// 模拟window.ethereum
global.window.ethereum = {
  request: jest.fn().mockImplementation((request) => {
    if (request.method === 'eth_requestAccounts') {
      return Promise.resolve(['0x123...']);
    }
    return Promise.resolve();
  }),
  on: jest.fn(),
  removeListener: jest.fn(),
};

const TestComponent = () => {
  const { account, connectWallet, isConnecting } = React.useContext(Web3Context);
  
  return (
    <div>
      {account ? (
        <p>Connected: {account}</p>
      ) : (
        <button onClick={connectWallet} disabled={isConnecting}>
          {isConnecting ? 'Connecting...' : 'Connect Wallet'}
        </button>
      )}
    </div>
  );
};

describe('Web3Context', () => {
  it('should render children', () => {
    render(
      <Web3Provider>
        <TestComponent />
      </Web3Provider>
    );
    
    expect(screen.getByText('Connect Wallet')).toBeInTheDocument();
  });
  
  it('should connect wallet when button is clicked', async () => {
    render(
      <Web3Provider>
        <TestComponent />
      </Web3Provider>
    );
    
    fireEvent.click(screen.getByText('Connect Wallet'));
    
    await waitFor(() => {
      expect(screen.getByText('Connected: 0x123...')).toBeInTheDocument();
    });
  });
});

5.3.3 测试自定义钩子

接下来,我们测试自定义钩子,确保它们能够正确地与智能合约交互。

// src/__tests__/hooks/useBallots.test.js
import { renderHook, act } from '@testing-library/react-hooks';
import { QueryClient, QueryClientProvider } from 'react-query';
import { useBallots } from '../../hooks/useBallots';
import { useContract } from '../../hooks/useContract';

// 模拟useContract钩子
jest.mock('../../hooks/useContract', () => ({
  useContract: jest.fn(),
}));

// 模拟合约方法
const mockContract = {
  ballotCount: jest.fn().mockResolvedValue(2),
  getBallotDetails: jest.fn().mockImplementation((id) => {
    if (id === 0) {
      return Promise.resolve([
        'Ballot 1',
        'Description 1',
        { toNumber: () => 1630000000 },
        { toNumber: () => 1630086400 },
        false,
        '0x123...',
      ]);
    }
    return Promise.resolve([
      'Ballot 2',
      'Description 2',
      { toNumber: () => 1630100000 },
      { toNumber: () => 1630186400 },
      true,
      '0x456...',
    ]);
  }),
  candidateCounts: jest.fn().mockResolvedValue(2),
  getCandidateDetails: jest.fn().mockImplementation((ballotId, candidateId) => {
    return Promise.resolve([
      `Candidate ${candidateId + 1}`,
      `Info ${candidateId + 1}`,
      candidateId + 1,
    ]);
  }),
  hasVotedInBallot: jest.fn().mockResolvedValue(false),
  createBallot: jest.fn().mockResolvedValue({
    wait: jest.fn().mockResolvedValue({}),
  }),
  addCandidate: jest.fn().mockResolvedValue({
    wait: jest.fn().mockResolvedValue({}),
  }),
  finalizeBallot: jest.fn().mockResolvedValue({
    wait: jest.fn().mockResolvedValue({}),
  }),
};

// 设置测试环境
const queryClient = new QueryClient();
const wrapper = ({ children }) => (
  <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

describe('useBallots', () => {
  beforeEach(() => {
    useContract.mockReturnValue({
      contract: mockContract,
      account: '0x123...',
      isOwner: true,
    });
  });
  
  it('should fetch ballots', async () => {
    const { result, waitFor } = renderHook(() => useBallots(), { wrapper });
    
    await waitFor(() => !result.current.isLoading);
    
    expect(result.current.ballots).toHaveLength(2);
    expect(result.current.ballots[0].title).toBe('Ballot 1');
    expect(result.current.ballots[1].title).toBe('Ballot 2');
  });
  
  it('should get a single ballot', async () => {
    const { result, waitFor } = renderHook(() => useBallots(), { wrapper });
    
    await waitFor(() => !result.current.isLoading);
    
    const ballot = await result.current.getBallot(0);
    
    expect(ballot.title).toBe('Ballot 1');
    expect(ballot.candidates).toHaveLength(2);
    expect(ballot.candidates[0].name).toBe('Candidate 1');
    expect(ballot.candidates[1].name).toBe('Candidate 2');
  });
  
  it('should create a ballot', async () => {
    const { result, waitFor } = renderHook(() => useBallots(), { wrapper });
    
    await waitFor(() => !result.current.isLoading);
    
    await act(async () => {
      await result.current.createBallot({
        title: 'New Ballot',
        description: 'New Description',
        startTime: new Date(),
        endTime: new Date(Date.now() + 86400000),
      });
    });
    
    expect(mockContract.createBallot).toHaveBeenCalled();
  });
});

5.3.4 测试UI组件

最后,我们测试UI组件,确保它们能够正确地渲染和响应用户交互。

// src/__tests__/components/BallotCard.test.js
import React from 'react';
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import BallotCard from '../../components/BallotCard';

const mockBallot = {
  id: 1,
  title: 'Test Ballot',
  description: 'This is a test ballot',
  startTime: new Date(Date.now() - 86400000), // 1 day ago
  endTime: new Date(Date.now() + 86400000), // 1 day later
  finalized: false,
  creator: '0x123...',
  candidates: [],
  hasVoted: false,
};

const renderWithRouter = (ui) => {
  return render(<BrowserRouter>{ui}</BrowserRouter>);
};

describe('BallotCard', () => {
  it('should render ballot details', () => {
    renderWithRouter(<BallotCard ballot={mockBallot} />);
    
    expect(screen.getByText('Test Ballot')).toBeInTheDocument();
    expect(screen.getByText('This is a test ballot')).toBeInTheDocument();
    expect(screen.getByText('进行中')).toBeInTheDocument();
  });
  
  it('should show upcoming status for future ballots', () => {
    const upcomingBallot = {
      ...mockBallot,
      startTime: new Date(Date.now() + 3600000), // 1 hour later
    };
    
    renderWithRouter(<BallotCard ballot={upcomingBallot} />);
    
    expect(screen.getByText('即将开始')).toBeInTheDocument();
  });
  
  it('should show ended status for finalized ballots', () => {
    const finalizedBallot = {
      ...mockBallot,
      finalized: true,
    };
    
    renderWithRouter(<BallotCard ballot={finalizedBallot} />);
    
    expect(screen.getByText('已结束')).toBeInTheDocument();
  });
});

5.3.5 运行前端测试

要运行前端测试,我们可以使用以下命令:

npm test

如果所有测试都通过,我们将看到类似以下的输出:

 PASS  src/__tests__/components/BallotCard.test.js
 PASS  src/__tests__/contexts/Web3Context.test.js
 PASS  src/__tests__/hooks/useBallots.test.js

Test Suites: 3 passed, 3 total
Tests:       7 passed, 7 total
Snapshots:   0 total
Time:        2.5s

5.4 部署流程

在这一节中,我们将介绍如何将去中心化投票系统部署到以太坊测试网和主网上。

5.4.1 部署智能合约

首先,我们需要部署智能合约。我们将使用Hardhat来部署合约到不同的网络。

配置部署网络

hardhat.config.js中配置部署网络:

require("@nomiclabs/hardhat-waffle");
require("dotenv").config();

module.exports = {
  solidity: "0.8.4",
  networks: {
    // 本地开发网络
    hardhat: {
      chainId: 31337,
    },
    // Goerli测试网
    goerli: {
      url: `https://goerli.infura.io/v3/${process.env.INFURA_API_KEY}`,
      accounts: [process.env.PRIVATE_KEY],
    },
    // 主网
    mainnet: {
      url: `https://mainnet.infura.io/v3/${process.env.INFURA_API_KEY}`,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
};

创建.env文件来存储敏感信息:

INFURA_API_KEY=your_infura_api_key
PRIVATE_KEY=your_private_key
创建部署脚本

创建scripts/deploy.js文件:

const hre = require("hardhat");

async function main() {
  // 获取合约工厂
  const VotingSystem = await hre.ethers.getContractFactory("VotingSystem");
  
  // 部署合约
  const votingSystem = await VotingSystem.deploy();
  await votingSystem.deployed();
  
  console.log("VotingSystem deployed to:", votingSystem.address);
  
  // 将合约地址保存到前端配置
  const fs = require("fs");
  const path = require("path");
  
  const contractsDir = path.join(__dirname, "..", "frontend", "src", "contracts");
  
  if (!fs.existsSync(contractsDir)) {
    fs.mkdirSync(contractsDir, { recursive: true });
  }
  
  fs.writeFileSync(
    path.join(contractsDir, "contract-address.json"),
    JSON.stringify({ VotingSystem: votingSystem.address }, undefined, 2)
  );
  
  // 复制合约ABI到前端
  const VotingSystemArtifact = artifacts.readArtifactSync("VotingSystem");
  
  fs.writeFileSync(
    path.join(contractsDir, "VotingSystem.json"),
    JSON.stringify(VotingSystemArtifact, null, 2)
  );
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
部署到测试网

要部署到测试网,我们可以使用以下命令:

npx hardhat run scripts/deploy.js --network goerli

如果部署成功,我们将看到类似以下的输出:

VotingSystem deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

5.4.2 部署前端应用

接下来,我们需要部署前端应用。我们可以使用Vercel、Netlify或GitHub Pages等服务来部署React应用。

使用Vercel部署
  1. 安装Vercel CLI:
npm install -g vercel
  1. 在前端项目目录中运行:
vercel
  1. 按照提示完成部署。
使用Netlify部署
  1. 安装Netlify CLI:
npm install -g netlify-cli
  1. 构建前端项目:
npm run build
  1. 部署到Netlify:
netlify deploy --prod
  1. 按照提示完成部署。

5.4.3 配置前端连接到部署的合约

部署完成后,我们需要确保前端应用能够连接到部署的智能合约。

src/utils/constants.js中更新合约地址:

// 投票系统合约地址
export const VOTING_SYSTEM_ADDRESS = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; // 部署后的合约地址

5.4.4 验证部署

部署完成后,我们需要验证系统是否正常工作:

  1. 访问部署的前端应用
  2. 连接MetaMask钱包
  3. 创建一个新的投票
  4. 添加候选人
  5. 进行投票
  6. 查看投票结果

如果所有功能都正常工作,那么我们的部署就是成功的。

5.5 持续集成与部署

为了简化开发和部署流程,我们可以设置持续集成和部署(CI/CD)管道。

5.5.1 使用GitHub Actions

我们可以使用GitHub Actions来自动化测试和部署流程。

创建.github/workflows/ci.yml文件:

name: CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '14'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Run tests
      run: npm test
  
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
    
    - name: Setup Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '14'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Build
      run: npm run build
    
    - name: Deploy to Netlify
      uses: netlify/actions/cli@master
      with:
        args: deploy --prod
      env:
        NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
        NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

5.5.2 使用Hardhat自动化部署

我们可以使用Hardhat任务来自动化合约部署流程。

hardhat.config.js中添加自定义任务:

task("deploy", "Deploys the contracts and updates the frontend")
  .setAction(async (taskArgs, hre) => {
    const { deploy } = require("./scripts/deploy");
    await deploy(hre);
  });

然后,我们可以使用以下命令来部署合约:

npx hardhat deploy --network goerli

5.6 本章总结

在本章中,我们完成了去中心化投票系统的测试和部署流程。我们实现了以下内容:

  1. 智能合约测试

    • 单元测试:测试每个合约函数的正确行为
    • 集成测试:测试合约之间的交互
    • 边界条件测试:测试极端情况和边界条件
    • 安全测试:测试合约的安全性
  2. 前端测试

    • 组件测试:测试UI组件的渲染和交互
    • 钩子测试:测试自定义钩子的逻辑
    • 集成测试:测试前端与智能合约的交互
  3. 部署流程

    • 部署智能合约到测试网和主网
    • 部署前端应用到托管服务
    • 配置前端连接到部署的合约
    • 验证部署是否成功
  4. 持续集成与部署

    • 使用GitHub Actions自动化测试和部署流程
    • 使用Hardhat任务自动化合约部署

通过这些测试和部署步骤,我们确保了系统的可靠性和安全性。测试覆盖了系统的各个方面,从智能合约的功能正确性到前端的用户体验。部署流程则确保了系统能够在真实环境中正常运行。

在实际开发中,测试和部署是非常重要的环节,尤其是对于区块链应用来说。由于区块链的不可变性,一旦智能合约部署到主网上,就无法更改。因此,在部署之前进行全面的测试是至关重要的。

此外,我们还可以考虑以下改进:

  1. 更全面的测试覆盖

    • 添加更多的边界条件测试
    • 使用模糊测试(Fuzzing)来发现潜在的漏洞
    • 进行形式化验证(Formal Verification)
  2. 更安全的部署流程

    • 使用多重签名钱包进行部署
    • 实施时间锁(Timelock)机制
    • 使用代理合约(Proxy Contract)实现可升级性
  3. 更完善的监控和维护

    • 设置监控系统来跟踪合约的状态和事件
    • 准备应急响应计划来处理潜在的问题
    • 定期进行安全审计

通过这些改进,我们可以进一步提高系统的可靠性、安全性和可维护性,为用户提供更好的体验。

在下一章中,我们将总结整个项目,回顾我们所学到的知识,并探讨去中心化投票系统的未来发展方向。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐