Skip to content

Commit

Permalink
add unit tests and coverage results
Browse files Browse the repository at this point in the history
  • Loading branch information
dragoon committed Nov 19, 2023
1 parent 6ae3f67 commit bc1bc5b
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 32 deletions.
Binary file added assets/coverage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 33 additions & 32 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,22 +204,20 @@ class BookManager:
def __init__(self, asset_symbol):
self.asset_symbol = asset_symbol

async def connect(self):
self.client = await AsyncClient.create()
self.dcm = DepthCacheManager(self.client, symbol=f'{self.asset_symbol.upper()}USDT',
limit=5000, # initial number of order from the order book
refresh_interval=0, # disable cache refresh
ws_interval=100 # update interval on websocket, ms
)

@asynccontextmanager
async def get_dcm_socket(self):
async with self.dcm as dcm_socket:
yield dcm_socket

async def close(self):
if self.client:
await self.client.close_connection()
async def get_data(self):
try:
self.client = await AsyncClient.create()
self.dcm = DepthCacheManager(self.client, symbol=f'{self.asset_symbol.upper()}USDT',
limit=5000,
refresh_interval=0,
ws_interval=100)
async with self.dcm as dcm_socket:
while True:
data = await dcm_socket.recv()
yield data
finally:
if self.client:
await self.client.close_connection()
```

Next, we have the ``collect_data`` function that starts the collection process and restarts it in case of errors:
Expand All @@ -229,46 +227,49 @@ async def collect_data(self):
retry_count = 0
while True:
try:
await self.book_manager.connect()
self.logger.info(f"Starting order book collection for {self.asset_symbol}-USDT")
async with self.book_manager.get_dcm_socket() as dcm_socket:
await self._process_depth_cache(dcm_socket)

async for data in self.book_manager.get_data():
await self._process_depth_cache(data)
retry_count = 0

# in production the data will always continue
break

except asyncio.TimeoutError as e:
self.logger.error(f"Network error: {e}. Reconnecting...")
await asyncio.sleep(self.retry_delay)

except Exception as e:
self.logger.exception(f"An unexpected error occurred: {e}")
retry_count += 1
if retry_count > self.max_retries:
self.logger.error("Max retries exceeded. Exiting...")
break

# Exponential backoff
wait = self.retry_delay * 2 ** min(retry_count, self.max_retries)
self.logger.info(f"Attempting to reconnect in {wait} seconds...")
await asyncio.sleep(wait)

finally:
await self.book_manager.close()
```

In case we get ``asyncio.TimeoutError``, we simply sleep with a constant delay, and then try to re-connect.
In case of other exceptions, we sleep with exponential backoff delay, and exit completely if the number of retries exceeded pre-configured value.
It is worth noting, that while network errors are somewhat expected, other exception are not,
so the log monitoring system should be configured to notify the dev team in case of such errors.

Finally, ``_process_depth_cache`` function checks the elapsed time and send data entry for storing in minute intervals.
:warning: It is worth noting, that while network errors are somewhat expected, other exceptions are not,
and the generic exception handle will swallow everything, even errors in your implementation.
The log monitoring system should be configured to notify the dev team in case of such errors.

Finally, ``_process_depth_cache`` function checks the elapsed time and sends data entry for storing in minute intervals.


### Unit testing

The new structure made it possible to test each part of the system independently, which is exactly the point of unit testing.
The source code for tests lives under [/tests/datacollector](https://github.com/FarawayTech/faraway-finance/tree/master/tests/datacollector) directory, which you can explore on your own.
Code coverage results below demonstrate my point that the system is now fully testable:
[!image]

### Python-binance parameters
I have also set up a GitHub action workflow to run unit tests automatically and report coverage:
![](assets/coverage.png)

As a bonus, I also added a repository test for mongo, which uses a local mongo instance.

### Python-binance parameters

1 comment on commit bc1bc5b

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
start_data_collector.py25250%1–33
datacollector
   domain.py260100% 
datacollector/repositories
   data_repository.py60100% 
datacollector/services
   collector_service.py641084%16, 19–31
   data_process_service.py400100% 
   datetime_service.py9189%13
TOTAL1703679% 

Tests Skipped Failures Errors Time
6 0 💤 0 ❌ 0 🔥 1.736s ⏱️

Please sign in to comment.