Merge pull request #465 from gcarq/fix/increase_test_coverage

Fix/increase test coverage
This commit is contained in:
Janne Sinivirta 2018-01-29 08:47:26 +02:00 committed by GitHub
commit a5155b3b20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 153 additions and 38 deletions

View File

@ -60,7 +60,7 @@ def common_datearray(dfs):
return np.sort(arr, axis=0) return np.sort(arr, axis=0)
def file_dump_json(filename, data): def file_dump_json(filename, data) -> None:
with open(filename, 'w') as fp: with open(filename, 'w') as fp:
json.dump(data, fp) json.dump(data, fp)
@ -287,27 +287,27 @@ def hyperopt_options(parser: argparse.ArgumentParser) -> None:
def parse_timerange(text): def parse_timerange(text):
if text is None: if text is None:
return None return None
syntax = [('^-(\d{8})$', (None, 'date')), syntax = [(r'^-(\d{8})$', (None, 'date')),
('^(\d{8})-$', ('date', None)), (r'^(\d{8})-$', ('date', None)),
('^(\d{8})-(\d{8})$', ('date', 'date')), (r'^(\d{8})-(\d{8})$', ('date', 'date')),
('^(-\d+)$', (None, 'line')), (r'^(-\d+)$', (None, 'line')),
('^(\d+)-$', ('line', None)), (r'^(\d+)-$', ('line', None)),
('^(\d+)-(\d+)$', ('index', 'index'))] (r'^(\d+)-(\d+)$', ('index', 'index'))]
for rex, stype in syntax: for rex, stype in syntax:
# Apply the regular expression to text # Apply the regular expression to text
m = re.match(rex, text) match = re.match(rex, text)
if m: # Regex has matched if match: # Regex has matched
rvals = m.groups() rvals = match.groups()
n = 0 index = 0
start = None start = None
stop = None stop = None
if stype[0]: if stype[0]:
start = rvals[n] start = rvals[index]
if stype[0] != 'date': if stype[0] != 'date':
start = int(start) start = int(start)
n += 1 index += 1
if stype[1]: if stype[1]:
stop = rvals[n] stop = rvals[index]
if stype[1] != 'date': if stype[1] != 'date':
stop = int(stop) stop = int(stop)
return (stype, start, stop) return (stype, start, stop)

View File

@ -27,8 +27,7 @@ def trim_tickerlist(tickerlist, timerange):
return tickerlist return tickerlist
def load_tickerdata_file(datadir, pair, ticker_interval, def load_tickerdata_file(datadir, pair, ticker_interval, timerange=None):
timerange=None):
""" """
Load a pair from file, Load a pair from file,
:return dict OR empty if unsuccesful :return dict OR empty if unsuccesful

View File

@ -17,13 +17,6 @@ class IStrategy(ABC):
stoploss -> float: optimal stoploss designed for the strategy stoploss -> float: optimal stoploss designed for the strategy
ticker_interval -> int: value of the ticker interval to use for the strategy ticker_interval -> int: value of the ticker interval to use for the strategy
""" """
@property
def name(self) -> str:
"""
Name of the strategy.
:return: str representation of the class name
"""
return self.__class__.__name__
@abstractmethod @abstractmethod
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame) -> DataFrame:

View File

@ -1,3 +1,5 @@
# pragma pylint: disable=attribute-defined-outside-init
""" """
This module load custom strategies This module load custom strategies
""" """
@ -21,7 +23,7 @@ class Strategy(object):
DEFAULT_STRATEGY = 'default_strategy' DEFAULT_STRATEGY = 'default_strategy'
def __new__(cls): def __new__(cls) -> object:
""" """
Used to create the Singleton Used to create the Singleton
:return: Strategy object :return: Strategy object
@ -30,15 +32,7 @@ class Strategy(object):
Strategy.__instance = object.__new__(cls) Strategy.__instance = object.__new__(cls)
return Strategy.__instance return Strategy.__instance
def __init__(self): def init(self, config: dict) -> None:
if Strategy.__instance is None:
self.logger = None
self.minimal_roi = None
self.stoploss = None
self.ticker_interval = None
self.custom_strategy = None
def init(self, config):
""" """
Load the custom class from config parameter Load the custom class from config parameter
:param config: :param config:

View File

@ -1,12 +1,14 @@
# pragma pylint: disable=missing-docstring, protected-access, C0103 # pragma pylint: disable=missing-docstring, protected-access, C0103
import os import os
import json
import logging import logging
import uuid
from shutil import copyfile from shutil import copyfile
from freqtrade import exchange, optimize from freqtrade import exchange, optimize
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.optimize.__init__ import make_testdata_path, download_pairs,\ from freqtrade.optimize.__init__ import make_testdata_path, download_pairs,\
download_backtesting_testdata, load_tickerdata_file download_backtesting_testdata, load_tickerdata_file, trim_tickerlist, file_dump_json
# Change this if modifying BTC_UNITEST testdatafile # Change this if modifying BTC_UNITEST testdatafile
_BTC_UNITTEST_LENGTH = 13681 _BTC_UNITTEST_LENGTH = 13681
@ -225,3 +227,73 @@ def test_tickerdata_to_dataframe():
tickerlist = {'BTC_UNITEST': tick} tickerlist = {'BTC_UNITEST': tick}
data = optimize.tickerdata_to_dataframe(tickerlist) data = optimize.tickerdata_to_dataframe(tickerlist)
assert len(data['BTC_UNITEST']) == 100 assert len(data['BTC_UNITEST']) == 100
def test_trim_tickerlist():
with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file:
ticker_list = json.load(data_file)
ticker_list_len = len(ticker_list)
# Test the pattern ^(-\d+)$
# This pattern remove X element from the beginning
timerange = ((None, 'line'), None, 5)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_list_len == ticker_len + 5
assert ticker_list[0] is not ticker[0] # The first element should be different
assert ticker_list[-1] is ticker[-1] # The last element must be the same
# Test the pattern ^(\d+)-$
# This pattern keep X element from the end
timerange = (('line', None), 5, None)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is ticker[0] # The first element must be the same
assert ticker_list[-1] is not ticker[-1] # The last element should be different
# Test the pattern ^(\d+)-(\d+)$
# This pattern extract a window
timerange = (('index', 'index'), 5, 10)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is not ticker[0] # The first element should be different
assert ticker_list[5] is ticker[0] # The list starts at the index 5
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
# Test a wrong pattern
# This pattern must return the list unchanged
timerange = ((None, None), None, 5)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_list_len == ticker_len
def test_file_dump_json():
file = 'freqtrade/tests/testdata/test_{id}.json'.format(id=str(uuid.uuid4()))
data = {'bar': 'foo'}
# check the file we will create does not exist
assert os.path.isfile(file) is False
# Create the Json file
file_dump_json(file, data)
# Check the file was create
assert os.path.isfile(file) is True
# Open the Json file created and test the data is in it
with open(file) as data_file:
json_from_file = json.load(data_file)
assert 'bar' in json_from_file
assert json_from_file['bar'] == 'foo'
# Remove the file
_clean_test_file(file)

View File

@ -406,8 +406,7 @@ def test_performance_handle(
assert '<code>BTC_ETH\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>BTC_ETH\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
def test_daily_handle( def test_daily_handle(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False)) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
msg_mock = MagicMock() msg_mock = MagicMock()
@ -470,6 +469,25 @@ def test_daily_handle(
assert str(' 2.798 USD') in msg_mock.call_args_list[0][0][0] assert str(' 2.798 USD') in msg_mock.call_args_list[0][0][0]
assert str(' 3 trades') in msg_mock.call_args_list[0][0][0] assert str(' 3 trades') in msg_mock.call_args_list[0][0][0]
def test_daily_wrong_input(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf,
init=MagicMock(),
send_msg=msg_mock)
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker)
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
init(default_conf, create_engine('sqlite://'))
# Try invalid data # Try invalid data
msg_mock.reset_mock() msg_mock.reset_mock()
update_state(State.RUNNING) update_state(State.RUNNING)
@ -478,6 +496,13 @@ def test_daily_handle(
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0] assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
# Try invalid data
msg_mock.reset_mock()
update_state(State.RUNNING)
update.message.text = '/daily today'
_daily(bot=MagicMock(), update=update)
assert str('Daily Profit over the last 7 days') in msg_mock.call_args_list[0][0][0]
def test_count_handle(default_conf, update, ticker, mocker): def test_count_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
@ -691,3 +716,18 @@ def test_send_msg_network_error(default_conf, mocker):
# Bot should've tried to send it twice # Bot should've tried to send it twice
assert len(bot.method_calls) == 2 assert len(bot.method_calls) == 2
def test_init(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: (True, False))
msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf,
init=MagicMock(),
send_msg=msg_mock)
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker)
init(default_conf, create_engine('sqlite://'))

View File

@ -16,6 +16,7 @@ def test_sanitize_module_name():
def test_search_strategy(): def test_search_strategy():
assert Strategy._search_strategy('default_strategy') == '.' assert Strategy._search_strategy('default_strategy') == '.'
assert Strategy._search_strategy('test_strategy') == 'user_data.strategies.'
assert Strategy._search_strategy('super_duper') is None assert Strategy._search_strategy('super_duper') is None

View File

@ -5,11 +5,12 @@ import time
from copy import deepcopy from copy import deepcopy
from unittest.mock import MagicMock from unittest.mock import MagicMock
import datetime
import pytest import pytest
from jsonschema import ValidationError from jsonschema import ValidationError
from freqtrade.analyze import parse_ticker_dataframe
from freqtrade.misc import (common_args_parser, file_dump_json, load_config, from freqtrade.misc import (common_args_parser, file_dump_json, load_config,
parse_args, parse_timerange, throttle) parse_args, parse_timerange, throttle, datesarray_to_datetimearray)
def test_throttle(): def test_throttle():
@ -178,3 +179,18 @@ def test_load_config_missing_attributes(default_conf, mocker):
read_data=json.dumps(conf))) read_data=json.dumps(conf)))
with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'): with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'):
load_config('somefile') load_config('somefile')
def test_datesarray_to_datetimearray(ticker_history):
dataframes = parse_ticker_dataframe(ticker_history)
dates = datesarray_to_datetimearray(dataframes['date'])
assert isinstance(dates[0], datetime.datetime)
assert dates[0].year == 2017
assert dates[0].month == 11
assert dates[0].day == 26
assert dates[0].hour == 8
assert dates[0].minute == 50
date_len = len(dates)
assert date_len == 3