Open harperreed opened 1 month ago
Let's implement the proposed solution to refactor the SerialProxy class and add unit tests. We'll start by refactoring the src/serial_proxy/serial_proxy.py
file:
src/serial_proxy/serial_proxy.py
---
+++
@@ -1,70 +1,60 @@
import serial
-import json
-from datetime import datetime
+from typing import Union, List, Optional
+from .logger import Logger
class SerialProxy:
- def __init__(self, port, baudrate, log_file='serial_log.json'):
+ def __init__(self, port: str, baudrate: int, log_file: str = 'serial_log.json'):
self.serial = serial.Serial(port, baudrate)
- self.log_file = log_file
+ self.logger = Logger(log_file)
- def write(self, data):
- self.log("WRITE", data)
+ def write(self, data: Union[str, bytes]) -> int:
+ self.logger.log("WRITE", data)
return self.serial.write(data)
- def read(self, size=1):
+ def read(self, size: int = 1) -> bytes:
data = self.serial.read(size)
- self.log("READ", data)
+ self.logger.log("READ", data)
return data
- def readline(self):
+ def readline(self) -> bytes:
data = self.serial.readline()
- self.log("READLINE", data)
+ self.logger.log("READLINE", data)
return data
- def readlines(self):
+ def readlines(self) -> List[bytes]:
data = self.serial.readlines()
- self.log("READLINES", data)
+ self.logger.log("READLINES", data)
return data
- def in_waiting(self):
+ def in_waiting(self) -> int:
return self.serial.in_waiting
- def flush(self):
- self.log("FLUSH", "")
+ def flush(self) -> None:
+ self.logger.log("FLUSH", "")
return self.serial.flush()
- def flushInput(self):
- self.log("FLUSH_INPUT", "")
+ def flushInput(self) -> None:
+ self.logger.log("FLUSH_INPUT", "")
return self.serial.flushInput()
- def flushOutput(self):
- self.log("FLUSH_OUTPUT", "")
+ def flushOutput(self) -> None:
+ self.logger.log("FLUSH_OUTPUT", "")
return self.serial.flushOutput()
- def close(self):
- self.log("CLOSE", "")
+ def close(self) -> None:
+ self.logger.log("CLOSE", "")
return self.serial.close()
- def open(self):
- self.log("OPEN", "")
+ def open(self) -> None:
+ self.logger.log("OPEN", "")
return self.serial.open()
- def __enter__(self):
+ def __enter__(self) -> 'SerialProxy':
return self
- def __exit__(self, exc_type, exc_val, exc_tb):
+ def __exit__(self, exc_type: Optional[type], exc_val: Optional[Exception], exc_tb: Optional[object]) -> None:
self.close()
@property
- def is_open(self):
+ def is_open(self) -> bool:
return self.serial.is_open
-
- def log(self, operation, data):
- log_entry = {
- "timestamp": datetime.now().isoformat(),
- "operation": operation,
- "data": data.hex() if isinstance(data, bytes) else str(data)
- }
- with open(self.log_file, 'a') as f:
- json.dump(log_entry, f)
- f.write('\n') # Add a newline for readability and to separate entries
Now, let's create a new file for the Logger class:
src/serial_proxy/logger.py
---
+++
@@ -1 +1,25 @@
+import json
+from datetime import datetime
+from typing import Union
+class Logger:
+ def __init__(self, log_file: str):
+ self.log_file = log_file
+
+ def log(self, operation: str, data: Union[str, bytes, List[bytes]]) -> None:
+ log_entry = {
+ "timestamp": datetime.now().isoformat(),
+ "operation": operation,
+ "data": self._format_data(data)
+ }
+ with open(self.log_file, 'a') as f:
+ json.dump(log_entry, f)
+ f.write('\n') # Add a newline for readability and to separate entries
+
+ def _format_data(self, data: Union[str, bytes, List[bytes]]) -> str:
+ if isinstance(data, bytes):
+ return data.hex()
+ elif isinstance(data, list):
+ return [self._format_data(item) for item in data]
+ else:
+ return str(data)
Now, let's create the unit tests file:
src/serial_proxy/tests/test_serial_proxy.py
---
+++
@@ -1 +1,89 @@
+import pytest
+from unittest.mock import Mock, patch
+from serial_proxy.serial_proxy import SerialProxy
+@pytest.fixture
+def mock_serial():
+ with patch('serial_proxy.serial_proxy.serial.Serial') as mock:
+ yield mock
+
+@pytest.fixture
+def mock_logger():
+ with patch('serial_proxy.serial_proxy.Logger') as mock:
+ yield mock
+
+def test_initialization(mock_serial, mock_logger):
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ mock_serial.assert_called_once_with('/dev/ttyUSB0', 9600)
+ mock_logger.assert_called_once_with('serial_log.json')
+
+def test_write(mock_serial, mock_logger):
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ proxy.write(b'test')
+ mock_serial.return_value.write.assert_called_once_with(b'test')
+ mock_logger.return_value.log.assert_called_once_with("WRITE", b'test')
+
+def test_read(mock_serial, mock_logger):
+ mock_serial.return_value.read.return_value = b'test'
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ result = proxy.read(4)
+ assert result == b'test'
+ mock_serial.return_value.read.assert_called_once_with(4)
+ mock_logger.return_value.log.assert_called_once_with("READ", b'test')
+
+def test_readline(mock_serial, mock_logger):
+ mock_serial.return_value.readline.return_value = b'test\n'
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ result = proxy.readline()
+ assert result == b'test\n'
+ mock_serial.return_value.readline.assert_called_once()
+ mock_logger.return_value.log.assert_called_once_with("READLINE", b'test\n')
+
+def test_readlines(mock_serial, mock_logger):
+ mock_serial.return_value.readlines.return_value = [b'test1\n', b'test2\n']
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ result = proxy.readlines()
+ assert result == [b'test1\n', b'test2\n']
+ mock_serial.return_value.readlines.assert_called_once()
+ mock_logger.return_value.log.assert_called_once_with("READLINES", [b'test1\n', b'test2\n'])
+
+def test_flush_operations(mock_serial, mock_logger):
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ proxy.flush()
+ proxy.flushInput()
+ proxy.flushOutput()
+ mock_serial.return_value.flush.assert_called_once()
+ mock_serial.return_value.flushInput.assert_called_once()
+ mock_serial.return_value.flushOutput.assert_called_once()
+ mock_logger.return_value.log.assert_has_calls([
+ Mock(args=("FLUSH", "")),
+ Mock(args=("FLUSH_INPUT", "")),
+ Mock(args=("FLUSH_OUTPUT", ""))
+ ])
+
+def test_open_close(mock_serial, mock_logger):
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ proxy.open()
+ proxy.close()
+ mock_serial.return_value.open.assert_called_once()
+ mock_serial.return_value.close.assert_called_once()
+ mock_logger.return_value.log.assert_has_calls([
+ Mock(args=("OPEN", "")),
+ Mock(args=("CLOSE", ""))
+ ])
+
+def test_context_manager(mock_serial, mock_logger):
+ with SerialProxy('/dev/ttyUSB0', 9600) as proxy:
+ pass
+ mock_serial.return_value.close.assert_called_once()
+
+def test_is_open_property(mock_serial):
+ mock_serial.return_value.is_open = True
+
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ assert proxy.is_open == True
+
+def test_in_waiting(mock_serial):
+ mock_serial.return_value.in_waiting = 5
+ proxy = SerialProxy('/dev/ttyUSB0', 9600)
+ assert proxy.in_waiting() == 5
Now, let's update the README.md file to include information about the new test suite:
README.md
---
+++
@@ -34,6 +34,22 @@
- Automatic logging of all serial operations to a JSON file
- Context manager support for easy resource management
+## Development
+
+### Running Tests
+
+To run the test suite, first install the development dependencies:
+
+```
+pip install -r requirements-dev.txt
+```
+
+Then, run the tests using pytest:
+
+```
+pytest src/serial_proxy/tests
+```
+
## License
This project is licensed under the MIT License - see the LICENSE file for details.
Finally, let's create a requirements-dev.txt
file for development dependencies:
requirements-dev.txt
---
+++
@@ -1 +1,2 @@
-
+pytest==7.3.1
+pytest-mock==3.10.0
These changes implement the requested refactoring of the SerialProxy class, add comprehensive unit tests, and update the project documentation. The code is now more organized, testable, and follows best practices such as type hinting and separation of concerns.
Continue the conversation with Sweep here: https://app.sweep.dev/c/9274c699-1e23-4ec5-bdbe-5da16f4685f3.
:book: For more information on how to use Sweep, please read our documentation.
Refactor SerialProxy class and add unit tests
Description:
Refactor the
SerialProxy
class insrc/serial_proxy/serial_proxy.py
to improve code organization and testability. Add comprehensive unit tests to ensure the functionality of the class.Tasks:
Refactor
src/serial_proxy/serial_proxy.py
:log
methodLogger
class to handle logging operationsCreate a new file
src/serial_proxy/tests/test_serial_proxy.py
for unit testsTest:
In
src/serial_proxy/tests/test_serial_proxy.py
, add the following test cases:SerialProxy
classAdditional tasks:
README.md
to include information about the new test suite and how to run testsrequirements-dev.txt
file for development dependencies, including a testing framework like pytest