编写银行核心程序包

已完成

现在,我们已经有了与我们的测试文件一起运行的基础项目,接下来我们将开始编写代码来实现上一个单元的功能和需求。 本文回顾了之前讨论过的几个主题,例如错误、结构和方法。

打开 $GOPATH/src/bankcore/bank.go 文件,删除 Hello() 函数,然后开始编写网上银行系统的核心逻辑。

为客户和帐户创建结构

让我们先创建一个 Customer 结构,其中将包含要成为银行客户的人员的姓名、地址和电话号码。 此外,我们还需要 Account 数据的结构。 由于一个客户可以有多个帐户,因此让我们将客户信息嵌入到帐户对象中。 基本上,我们将创建已在 TestAccount 测试中定义的内容。

我们需要的结构可能类似于以下代码示例:

package bank

// Customer ...
type Customer struct {
    Name    string
    Address string
    Phone   string
}

// Account ...
type Account struct {
    Customer
    Number  int32
    Balance float64
}

现在,在终端中运行 go test -v 命令时,你应该会看到测试通过:

=== RUN   TestAccount
--- PASS: TestAccount (0.00s)
PASS
ok      github.com/msft/bank    0.094s

由于我们已实现了 CustomerAccount 的结构,因此此测试通过。 现在我们已经有了这些结构,接下来让我们编写一些方法,用于在银行的初始版本中添加所需的功能。 这些功能包括存款、取款和转账。

实现存款方法

我们需要从一种允许将资金添加到帐户的方法开始。 但在执行此操作之前,让我们在 bank_test.go 文件中创建 TestDeposit 函数:

func TestDeposit(t *testing.T) {
    account := Account{
        Customer: Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number:  1001,
        Balance: 0,
    }

    account.Deposit(10)

    if account.Balance != 10 {
        t.Error("balance is not being updated after a deposit")
    }
}

运行 go test -v 时,应该会在输出中看到一个将失败的测试:

# github.com/msft/bank [github.com/msft/bank.test]
./bank_test.go:32:9: account.Deposit undefined (type Account has no field or method Deposit)
FAIL    github.com/msft/bank [build failed]

为了满足前面的测试,让我们创建 Account 结构的 Deposit 方法。如果收到的金额等于或小于零,该方法会返回一个错误。 否则,只需将收到的金额添加到帐户的余额。

将以下代码用于 Deposit 方法:

// Deposit ...
func (a *Account) Deposit(amount float64) error {
    if amount <= 0 {
        return errors.New("the amount to deposit should be greater than zero")
    }

    a.Balance += amount
    return nil
}

运行 go test -v 时,应该会看到测试通过:

=== RUN   TestAccount
--- PASS: TestAccount (0.00s)
=== RUN   TestDeposit
--- PASS: TestDeposit (0.00s)
PASS
ok      github.com/msft/bank    0.193s

你还可以编写一个测试,用于确认当尝试存入的金额为负时会出现错误,如下所示:

func TestDepositInvalid(t *testing.T) {
    account := Account{
        Customer: Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number:  1001,
        Balance: 0,
    }

    if err := account.Deposit(-10); err == nil {
        t.Error("only positive numbers should be allowed to deposit")
    }
}

运行 go test -v 命令时,应该会看到测试通过:

=== RUN   TestAccount
--- PASS: TestAccount (0.00s)
=== RUN   TestDeposit
--- PASS: TestDeposit (0.00s)
=== RUN   TestDepositInvalid
--- PASS: TestDepositInvalid (0.00s)
PASS
ok      github.com/msft/bank    0.197s

备注

从这里开始,我们将为每个方法编写一个测试用例。 但是,你应该为你的程序编写尽可能多的测试,以便涵盖预期的和意外的场景。 例如,在本例中,将对错误处理逻辑进行测试。

实现取款方法

在编写 Withdraw 功能之前,让我们为其编写测试:

func TestWithdraw(t *testing.T) {
    account := Account{
        Customer: Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number:  1001,
        Balance: 0,
    }

    account.Deposit(10)
    account.Withdraw(10)

    if account.Balance != 0 {
        t.Error("balance is not being updated after withdraw")
    }
}

运行 go test -v 命令时,应该会在输出中看到一个将失败的测试:

# github.com/msft/bank [github.com/msft/bank.test]
./bank_test.go:67:9: account.Withdraw undefined (type Account has no field or method Withdraw)
FAIL    github.com/msft/bank [build failed]

让我们实现 Withdraw 方法的逻辑,该方法将帐户余额减少的金额就是以参数方式收到的金额。 像之前一样,我们需要验证收到的数字是否大于零,以及帐户中的余额是否足够。

将以下代码用于 Withdraw 方法:

// Withdraw ...
func (a *Account) Withdraw(amount float64) error {
    if amount <= 0 {
        return errors.New("the amount to withdraw should be greater than zero")
    }

    if a.Balance < amount {
        return errors.New("the amount to withdraw should be less than the account's balance")
    }

    a.Balance -= amount
    return nil
}

运行 go test -v 命令时,应该会看到测试通过:

=== RUN   TestAccount
--- PASS: TestAccount (0.00s)
=== RUN   TestDeposit
--- PASS: TestDeposit (0.00s)
=== RUN   TestDepositInvalid
--- PASS: TestDepositInvalid (0.00s)
=== RUN   TestWithdraw
--- PASS: TestWithdraw (0.00s)
PASS
ok      github.com/msft/bank    0.250s

实现对账单方法

我们将编写一种方法来输出对账单,其中包含帐户名称、帐号和余额。 但是,首先让我们创建 TestStatement 函数:

func TestStatement(t *testing.T) {
    account := Account{
        Customer: Customer{
            Name:    "John",
            Address: "Los Angeles, California",
            Phone:   "(213) 555 0147",
        },
        Number:  1001,
        Balance: 0,
    }

    account.Deposit(100)
    statement := account.Statement()
    if statement != "1001 - John - 100" {
        t.Error("statement doesn't have the proper format")
    }
}

运行 go test -v 时,应该会在输出中看到一个将失败的测试:

# github.com/msft/bank [github.com/msft/bank.test]
./bank_test.go:86:22: account.Statement undefined (type Account has no field or method Statement)
FAIL    github.com/msft/bank [build failed]

让我们编写 Statement 方法,该方法应返回一个字符串。 (你稍后必须覆盖此方法,这是一项挑战。)使用以下代码:

// Statement ...
func (a *Account) Statement() string {
    return fmt.Sprintf("%v - %v - %v", a.Number, a.Name, a.Balance)
}

运行 go test -v 时,应该会看到测试通过:

=== RUN   TestAccount
--- PASS: TestAccount (0.00s)
=== RUN   TestDeposit
--- PASS: TestDeposit (0.00s)
=== RUN   TestDepositInvalid
--- PASS: TestDepositInvalid (0.00s)
=== RUN   TestWithdraw
--- PASS: TestWithdraw (0.00s)
=== RUN   TestStatement
--- PASS: TestStatement (0.00s)
PASS
ok      github.com/msft/bank    0.328s

让我们转到下一部分,并编写公开 Statement 方法的 Web API。