Escrever o pacote de núcleo do banco

Concluído

Agora que temos o projeto base em execução junto com nosso arquivo de teste, vamos começar a escrever o código que implementa os recursos e requisitos da unidade anterior. Aqui, revisitamos alguns assuntos que discutimos anteriormente, como erros, estruturas e métodos.

Abra o arquivo $GOPATH/src/bankcore/bank.go, remova a função Hello() e vamos começar a escrever a lógica do núcleo do nosso sistema bancário online.

Criar estruturas para clientes e contas

Vamos começar criando uma estrutura Customer na qual temos o nome, o endereço e o número de telefone de uma pessoa que quer se tornar um cliente bancário. Além disso, precisamos de uma estrutura para os dados Account. Como um cliente pode ter mais de uma conta, vamos inserir as informações do cliente no objeto de conta. Basicamente, vamos criar o que definimos no teste TestAccount.

As estruturas necessárias podem ser semelhantes ao seguinte exemplo de código:

package bank

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

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

Quando você executar o comando go test -v no seu terminal agora, verá que o teste é aprovado:

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

Esse teste foi aprovado porque implementamos as estruturas para Customer e Account. Agora que temos as estruturas, vamos escrever os métodos para adicionar os recursos necessários na versão inicial do nosso banco. Esses recursos incluem o depósito, a retirada e a transferência de dinheiro.

Implementar o método de depósito

Precisamos começar com um método para permitir a adição de dinheiro à nossa conta. Mas, antes de fazermos isso, vamos criar a função TestDeposit no arquivo bank_test.go:

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")
    }
}

Quando você executar go test -v, deverá ver um teste com falha no resultado:

# 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]

Para satisfazer ao teste anterior, vamos criar um método Deposit para nossa estrutura Account que retornará um erro se o valor recebido for igual ou menor que zero. Caso contrário, basta adicionar o valor recebido ao saldo da conta.

Use o seguinte código para o método 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
}

Quando você executar go test -v, verá que o teste é aprovado:

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

Você também pode escrever um teste que confirma que você recebe um erro ao tentar depositar um valor negativo, como este:

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")
    }
}

Quando você executar o comando go test -v, verá que o teste é aprovado:

=== 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

Observação

Daqui em diante, vamos escrever um caso de teste para cada método. Mas você deve escrever quantos testes para seus programas você quiser. Assim, você pode abordar cenários esperados e inesperados. Por exemplo, nesse caso, a lógica de tratamento de erro é testada.

Implementar o método de saque

Antes de escrevermos a funcionalidade Withdraw, vamos escrever o teste para ela:

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")
    }
}

Quando você executar o comando go test -v, deverá ver um teste com falha no resultado:

# 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]

Vamos implementar a lógica para o método Withdraw, no qual reduzimos o valor que recebemos como parâmetro do saldo da conta. Como fizemos anteriormente, precisamos validar que o número que recebemos é maior que zero e que o saldo na conta é suficiente.

Use o seguinte código para o método 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
}

Quando você executar o comando go test -v, verá que o teste é aprovado:

=== 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

Implementar o método de extrato

Vamos escrever um método para imprimir o extrato que inclua o nome da conta, o número e o saldo. Mas, primeiro, vamos criar a função 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")
    }
}

Quando você executar go test -v, deverá ver um teste com falha no resultado:

# 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]

Vamos escrever o método Statement, que deve retornar uma cadeia de caracteres. (Você precisará substituir esse método mais tarde como um desafio.) Use o seguinte código:

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

Quando você executar go test -v, verá que o teste é aprovado:

=== 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

Vamos passar para a próxima seção e escrever a API Web que expõe o método Statement.