Best Practices
This document outlines best practices, conventions, and tips for developing applications with WizLib that may not be fully covered in the core documentation.
Naming Conventions
-
App Class Naming: Name your app class with the "App" suffix, e.g.,
MyDynamoApp
, not justMyDynamo
.# Recommended class MyDynamoApp(WizApp): name = 'mydynamo' ... # Not recommended class MyDynamo(WizApp): name = 'mydynamo' ...
-
Command Naming: Name your command classes with the "Command" suffix, e.g.,
ListTablesCommand
.
Command-Line Argument Handling
- Framework Arguments Order: Always place framework-level arguments (such as
--config
) before the command name:# Correct python -m myapp --config config.yml command-name # Incorrect python -m myapp command-name --config config.yml
Testability and Dependency Injection
-
Dependency Injection: Make your classes testable by accepting dependencies in the constructor:
# Testable with dependency injection class DynamoOps: def __init__(self, config, resource=None, client=None): self.client = client or boto3.client(...) # Harder to test class DynamoOps: def __init__(self, config): self.client = boto3.client(...)
-
Factory Methods: Consider implementing factory methods for cleaner production code while maintaining testability:
class DynamoOps: def __init__(self, config, resource=None, client=None): # Constructor with injection points @classmethod def create(cls, config): """Factory method for production use.""" return cls(config)
Testing Best Practices
-
Test Coverage for Entry Points: Use
# pragma: nocover
in__main__.py
to exclude the entry point code from coverage reports:if __name__ == '__main__': # pragma: nocover MyApp.main()
-
Initializing Apps in Tests: If you need to improve test coverage for your app class, call the initialize method in your test package's
__init__.py
:# test/__init__.py from myapp import MyApp MyApp.initialize()
-
Mocking External Services: When testing code that interacts with external services (like AWS), mock the client libraries rather than your own classes:
@patch('boto3.client') def test_aws_interaction(self, mock_client): mock_aws_client = MagicMock() mock_client.return_value = mock_aws_client # Test code that uses boto3.client
-
Testing Command Execution: Test commands by:
- Creating a command instance
- Injecting any mocked dependencies
- Calling the appropriate methods
- Verifying results
def test_command_with_mocks(self): # Create app with fake config app = MyApp() app.config = ConfigHandler.fake(myapp_endpoint='http://example.com') # Create command command = MyCommand(app) # Inject mocked service command.service = MockService() # Execute and verify result = command.execute() self.assertEqual("Expected result", result)
Configuration Best Practices
-
Sandbox Configuration: Keep development/test configurations in a
sandbox/
directory:project/ ├── myapp/ └── sandbox/ └── myapp.yml # Dev configuration
-
Configuration Defaults: Always provide sensible defaults when reading configuration values:
endpoint = config.get('myapp-endpoint') or 'http://localhost:8000'
Common Pitfalls
-
Command Registration: Command classes are automatically discovered, you don't need to import them in
__init__.py
. Just ensure they're in the command directory. -
Config Access: Always access configuration through
self.app.config.get('key-name')
, not by direct dictionary access. -
Test Inheritance: When testing WizLib components, you may need to use regular
unittest.TestCase
instead ofWizLibTestCase
to avoid unexpected behaviors, especially when patching core components.
Development Workflow
- Define your app class with a clear name and required attributes
- Create the base command class
- Implement individual commands
- Write unit tests for each component
- Ensure high test coverage (typically 95% or higher)
- Create a sandbox configuration for development testing