Просчитать выгоды и потери криптовалюте используя приложение Coinbase API-интерфейс


Этот класс вычисляет прибылей и убытков (в долларах США) из четырех ведущих криптовалют с помощью API blockchain кошелек. (Полный РЕПО может быть найден здесь)

В дополнение к общей обратной связи есть несколько конкретных пунктов, хотелось бы отзывы о:

  • Что я могу сделать, чтобы улучшить мои тесты? (Например, это глупо с моей стороны, чтобы проверить только, что метод возвращает плавать, как в .crypto_amount_in_wallet? Если так, есть ли другие способы я могу идти о тестировании этот метод, что бы не просто проверка Фонда по API?)
  • Я использую ВКМ камень таким образом, что имеет смысл?
  • Есть ли лучше или более традиционным способом поглумиться данных, чем записи на видеокассеты и изменение ответов вручную?
  • Я должен считать разделение функциональности, что чисто обертывания API для конечных точек в отдельный класс, или оставить их, где они?

Класс

require 'coinbase/wallet'
require 'dotenv'

class Currency
  def initialize(symbol:, api_client:)
    raise ArgumentError 'Must specify currency symbol (BTC BCH LTC ETH)' if symbol.nil? || !([:BTC, :LTC, :BCH, :LTC].include?(symbol))
    raise ArgumentError 'Currency requires a valid coinbase client.' if api_client.nil? || api_client.class != Coinbase::Wallet::Client

    @symbol                   = symbol
    @api_client               = api_client
    @account                  = @api_client.account(symbol)
    @crypto_amount_in_wallet  = @account['balance']['amount']
    @usd_invested             = @account['native_balance']['amount']
  end

  def symbol
    return @symbol
  end

  def api_client
    return @api_client
  end

  def account
    account = self.api_client.account(self.symbol)
    account.refresh!
    return account
  end

  def crypto_amount_in_wallet
    return Float(self.account['balance']['amount'])
  end

  def usd_invested
    transactions   = self.account.transactions
    total_invested = transactions
                       .map { |t| t['native_amount']['amount'].to_f }
                       .reduce(:+)
    return Float(total_invested)
  end

  def current_cash_in_value
    Float(self.account['native_balance']['amount']) ## TODO: Use the buy/quote endpoint instead
  end

  def usd_lost
    loss = self.usd_invested - self.current_cash_in_value
    if loss.negative? # i.e. $1.00 - $10.00 = -$9.00 means that $9.00 have been made as profit, so return a $0.00 loss.
      return 0.0
    else
      return loss
    end
  end

  def usd_gained
    gain = self.current_cash_in_value - self.usd_invested
    if gain.negative? # i.e. $1.00 - $100.00 = -$99.00 means that $99.00 have been lost as profit, so return a $0.00 as a gain.
      return 0.0
    else
      return gain
    end
  end
end

Спец

require 'rspec'
require_relative '../lib/currency.rb'

describe Currency do
  before (:all) do
    VCR.use_cassette('client_and_currency') do
      @api_client = Coinbase::Wallet::Client.new(api_key:    ENV['COINBASE_KEY'],
                                                 api_secret: ENV['COINBASE_SECRET'])
      @currency   = Currency.new(symbol: :BTC, api_client: @api_client)
    end
  end

  describe '#initialize' do
    it 'raises an ArgumentError when a new currency is instantiated without a symbol' do
      expect { Currency.new(api_client: @api_client) }.to raise_error ArgumentError
    end

    it 'raises an ArgumentError if no coinbase client object is passed' do
      expect { Currency.new(symbol: :BTC) }.to raise_error ArgumentError
    end

    it 'returns a new object of type "Currency"' do
      VCR.use_cassette('currency_init') do
        expect(Currency.new(symbol: :BTC, api_client: @api_client)).to be_a_kind_of Currency
      end
    end
  end

  describe '.symbol' do
    it 'returns a symbol' do
      expect(@currency.symbol).to be_a Symbol
    end

    it 'is one of :BTC, :LTC, :BCH, :ETH' do
      expect([:BTC, :LTC, :BCH, :ETH].include?(@currency.symbol)).to be true
    end
  end

  describe '.api_client' do
    it 'properly instantiates a coinbase client' do
      expect(@currency.api_client).to be_a Coinbase::Wallet::Client
    end

    it 'doesn\'t raise an error' do
      expect { @currency.api_client }.not_to raise_error
    end
  end

  describe '.account' do
      it 'returns a hash' do
        VCR.use_cassette('account_hash') do
          expect(@currency.account).to be_a Hash
        end
      end

      it 'has 11 keys' do
        VCR.use_cassette('account_hash') do
          expect(@currency.account.keys.count).to eql(11)
        end
      end

      it 'matches the symbol' do
        VCR.use_cassette('account_hash') do
          expect(@currency.symbol.to_s).to eq(@currency.account['currency'])
        end
      end
  end

  describe '.crypto_amount_in_wallet' do
    it 'is a float' do
      VCR.use_cassette('crypto_amount_in_wallet') do
        expect(@currency.crypto_amount_in_wallet).to be_a Float
      end
    end
  end

  describe '.usd_invested' do
    it 'is a float' do
      VCR.use_cassette('account') do
        expect(@currency.usd_invested).to be_a Float
      end
    end
  end

  describe '.current_cash_in_value' do
    it 'is a float' do
      VCR.use_cassette('current_cash_in_val') do
        expect(@currency.current_cash_in_value).to be_a Float
      end
    end
  end

  describe '.usd_lost' do
    context 'given no loss' do
      it 'should return 0.0' do
        VCR.use_cassette('usd_no_loss') do
          expect(@currency.usd_lost).to eql(0.0)
        end
      end
    end

    context 'given a loss' do
      it 'should return 9.00 as a loss' do
        VCR.use_cassette('usd_loss') do
          expect(@currency.usd_lost).to eql(10.0 - 1.0)
        end
      end
    end
  end  

  describe '.usd_gained' do
    context 'with no gain' do
      it 'returns 0.0 as a gain' do
        VCR.use_cassette('usd_no_gain') do
          expect(@currency.usd_gained).to eql(0.0)
        end
      end
    end


    context 'with a gain' do
      it 'returns 10.0 as a gain' do
        VCR.use_cassette('usd_gain') do
          expect(@currency.usd_gained).to eql(10.0)
        end
      end
    end
  end

end


156
1
задан 23 марта 2018 в 02:03 Источник Поделиться
Комментарии
3 ответа

Очки Rorshark это важно: если ваш класс делает слишком много вещей, это, вероятно, означает, что вы
есть другие классы, там. Прежде чем вы спросите о том, как хороши ваши тесты, вы должны быть
прошу, если вы проверяете правильные вещи.

Определенных методов в вашем классе:


  • символ

  • api_client

  • счета

  • crypto_amount_in_wallet

  • usd_invested

  • current_cash_in_value

  • usd_lost

  • usd_gained

Двух аргументов конструктора (symbol, api_client) не используются, за исключением account
метод. Это говорит мне, что класс может быть слишком абстрактным, или что
класс account, как Rorshark предложил.

У вас есть валюта, но это не главное здесь. Главное, как представляется, счета
и операции по счету (amount_in_wallet, вложенные, cash_value, usd_lost,
usd_gained). Валютная стоимость-это просто аргумент нужен, чтобы получить ручку на счете.

Итак, если вы переименовали класса "счет", сделал account частный метод или просто превратил его в
переменная класса вы бы что-нибудь более сплоченной.

Некоторые стилистические гнид:


  • def symbol ... и def api_client ... может быть, сжатая attr_reader :symbol,
    :api_client

  • это немного больше, рубиново-ЭСК, чтобы пропустить return для конца-способ возврата значения, но не
    критической вещь

Некоторые вещи, чтобы быть осторожным с:


  • используя поплавки, когда дело доходит до валюты будет возвращаться, чтобы укусить вас. Рассмотреть вопрос о преобразовании
    все целые числа в начале (умножить валюту по некоторой точностью-фактор: 100, 1000, 10000 или
    даже 100 000), а затем вы можете просто использовать целочисленную математику везде, пока не пришло время для отображения.

2
ответ дан 18 апреля 2018 в 03:04 Источник Поделиться

Так как большинство из полезных методов предполагает выполнение расчетов, совокупная “учетная запись”, вы можете рассмотреть вопрос о переименовании класса “счета” и передает в счет хэш в инициализаторе, а не проходящей в клиентском API и код валюты.

Что бы отделить логику от фонда полностью API и сделает ваш код намного проще тестировать.

Вам не нужно видео, Если ты это сделал.

1
ответ дан 24 марта 2018 в 09:03 Источник Поделиться


  • Что я могу сделать, чтобы улучшить мои тесты? (Например, это глупо с моей стороны, чтобы проверить только то, что метод возвращает плавать, как в .crypto_amount_in_wallet? Если так, есть ли другие способы я могу идти о тестировании этот метод, что бы не просто проверка Фонда по API?


    Да, тесты слабые, вы должны сосредоточиться на основной логики своего класса и отстаивать крайние случаи. Тестирование тип возвращаемого недостаточно.

  • Я использую ВКМ камень таким образом, что имеет смысл?


    Да, видео-это хороший выбор, когда третьего-услуги не предоставляют тестирования издевается (например, туман камень имеет встроенные средства тестирования глумится). Но вам не нужно, чтобы проверить все с видеомагнитофоном, вы можете издеваться над какой-то код (утверждать крайние случаи), а также использовать магнитофон, чтобы сделать тестирование интеграции.

  • Есть ли лучше или более традиционным способом поглумиться данных, чем записи на видеокассеты и изменение ответов вручную?


    Я думаю, что это предпочтительнее и не изменить ответы вручную, вы должны моделировать свой сценарий с реальными данными. Причина в том, что кассеты должны быть одноразовыми, если вам нужно обновить ваш семенники, или служба изменения в API, нужно просто кинули, то так и записывать новые.

  • Я должен считать разделение функциональности, что чисто обертывания API для конечных точек в отдельный класс, или оставить их, где они?


    @Rorshark только это, вы можете создать AccountWrapper для этого.

Я применил некоторые улучшения в своем классе, применив немного рубиново-стиль-руководство, вот конечный результат:

(к сожалению переименования методов, но вы можете игнорировать это :д)

require 'coinbase/wallet'

class AccountStatus
SUPPORTED_SYMBOLS = [:BTC, :LTC, :BCH, :LTC]

attr_reader :symbol, :api_client

def initialize(symbol:, api_client:)
raise ArgumentError 'Must specify currency symbol (BTC BCH LTC ETH)' unless SUPPORTED_SYMBOLS.include?(symbol)
raise ArgumentError 'Currency requires a valid coinbase client.' unless api_client.is_a?(Coinbase::Wallet::Client)

@symbol = symbol
@api_client = api_client
end

def account
@account ||= api_client.account(symbol).tap(&:refresh!)
end

def crypto_balance
account['balance']['amount']
end

def cash_invested
account.transactions.map { |t| t['native_amount']['amount'] }.sum
end

def cash_current
account['native_balance']['amount'] # TODO: Use the buy/quote endpoint instead
end

def usd_lost
# returns 0.0 on negative values
[cash_invested - cash_current, 0.0].max
end

def usd_gained
# returns 0.0 on negative values
[cash_current - cash_invested, 0.0].max
end
end

Несколько вещей, чтобы заметить:


  • Я удалены некоторые неиспользуемые переменные и .nil? проверка на конструктор параметрами (значения nil будет выполнена логическое Регистрация)

  • Кэшированные аккаунт @переменная, я думаю, что это не хорошая идея, чтобы сохранить освежающий, но вы должны решить, на что

  • Удалена зависимость от потеряли/получили методы с использованием массива#Макс

  • Сняли поплавок преобразования, я читал, что компания гем уже переводит денежные суммы на то bigdecimal Реф

Я буду работать в тестовый файл позже и обновлять этот ответ с ним, к сожалению у меня нет времени сейчас.

1
ответ дан 25 марта 2018 в 02:03 Источник Поделиться