Mocking
Introduction
The session begins with an informal greeting and establishing the atmosphere.
Questions are posed to gauge the progress of the students on assignment two.
Interactive participation: Hands raised for those who have started and those who are facing challenges.
Assignment Two Overview
Student Progress
Many students have started the assignment; some reported having difficulties.
Areas of Difficulty
Common challenges include:
Bug finding: Students are encouraged to share specific bugs encountered.
Cloning the repository: Assistance will be provided through an upcoming video tutorial.
Pigeon test: Deliberately includes bugs for learning purposes; experimentation and debugging strategies are encouraged.
Debugging Tips
If debugging leads to breaking another test, the recommended action is:
Undo the changes to identify what went wrong. Fixing should ideally involve proper refactoring.
Introduction to Mocking
Definition of Mocking
Mocking: The process involves creating test doubles or mock objects that replicate the behavior of real objects in a controlled way for testing.
“Mock” can also referred to a “test double.”
Illustrates concepts with the analogy of a stunt double: someone who stands in for the real thing.
Applications of mocking in system testing, particularly when real-world interactions are involved.
Test Mock Concept
Mock objects can either mimic the behavior of real objects or interfaces, typically implemented in the following ways:
Object Simulation: An instance that behaves similarly to the real object.
Functional Simulation: A function that imitates the real function's interface.
Payment Processor Class Example
Payment Processing Logic
Example setup: Description of a payment processor class that utilizes a credit card service.
A method to make payments checks card validity and communicates with the credit card service.
Validation check: Returns
falseif an expired card is used.
Testing the Pay Method
Discusses a specific test case for making payments with a hypothetical expired credit card.
Two testing approaches are described:
Testing with guaranteed conditions (like using an expired card should yield expected outcomes).
Writing broader tests that involve the real transaction process which incurs costs when using real services. This leads to expensive unit tests.
Mocking to Prevent Side Effects
Importance of Mocking: Inserts fake dependencies to prevent tests from interacting with real-world side effects.
Failure to employ mocking results in tests that could execute real transactions (i.e., charging a credit card).
Preparing for Mocking
Challenges of Direct Object Creation
Issues arising from hidden service dependencies within classes.
Discusses Dependency Injection as a solution to enable easier mocking by allowing the payment processor to accept any credit card service as an argument.
Implementing a Mock Credit Card Service
Define a credit card service interface with a charge method.
Create a mock implementation of this service for testing purposes, enabling the payment processor to utilize this mock instead of the real-world credit card service.
Concepts of Good Software Testing Practices
Good Practices for Testing
Encourages the practice of:
Test-driven development (TDD): Write tests before implementing functionality to ensure testability from the outset.
Types of Mocks
Overview of Mock Types
Three main types: fakes, stubs, and validators.
Fakes: Close approximations of the real object, imitating behavior as realistically as possible.
Example: A
FakeCreditCardServicethat processes payments internally without involving a real bank, perfect for integration tests that need realistic behavior without real transactions.
Stubs: Hard-coded responses to simulate specific scenarios.
Example: A
StubFileSystemServicethat always returnstruefor awriteFilecall or alwaysDiskFullErrorfor a specific test case, useful for testing error handling without needing a physical disk.
Validators: Test interactions to validate that methods were invoked under the right circumstances, although their use should be limited to prevent brittleness.
Example: A
ValidatorLoggerthat records iflogErrorwas called with a specific message within a test, ensuring error reporting logic is triggered.
Implementation Examples and Concepts
File System Service Example
Discusses an example where a file system service is mocked for testing.
Emphasizes the need for mocks to faithfully replicate expected behaviors such as handling storage limits.
Edge Cases: Use stubs to create controlled scenarios for testing (e.g., simulating a full storage disk as exemplified by a
StubFileSystemServicethat explicitly returns an error when storage limits are exceeded).
Ensuring Faithfulness in Mocks
Mocks should behave as closely to real interactions as possible to avoid false positives/negatives in testing.
Emphasizes the need for maintenance and consistency with real-world object behavior.
Reflections on Mock Design
Stresses creating thoughtful, flexible test environments by utilizing effective mocking strategies, thereby preventing flaky tests.
All classes and fakes associated with testing should also undergo consistent unit testing to ensure quality.
Conclusion on Mocking
When to Use Mocking
Use mocking strategically in scenarios involving:
Interactions with real-world objects (e.g., emails, payment processing).
Potentially slow operations (e.g., database access) that should not occur in unit tests.
Non-deterministic behavior that could produce unreliable test results.
The general principle is to prefer real implementations where practical while employing fakes or mocks selectively to isolate unit tests effectively.
Domain Modeling Overview
Importance of Domain Modeling
Domain modeling is crucial for managing complexity in software development.
It involves understanding the problem domain to inform the design and structure of the software.
Key Quote: "The essential intricacy of the problem domain itself controls the complexity of development."
Initial Steps for Domain Modeling
Creating a Domain Model
Begin by extracting nouns (candidates for classes) and verbs (candidates for methods) from domain descriptions.
The example involves developing a model for a Zoo software system:
Classes and attributes are derived from the description, forming initial drafts of software requirements.
Engagement with Domain Experts
Importance of establishing a ubiquitous language between developers and domain experts to enhance communication and clarity in design decisions.
Aim for models that facilitate understanding and reduce ambiguity when discussing requirements and functionalities.
Advanced Domain Design Techniques
Programming with Intent
Focus on designing code structures that reflect domain logic clearly, thereby simplifying future adjustments.
Use naming conventions that explicitly describe functionality to avoid misinterpretation and improve readability.
Using Type Systems
Employ type systems effectively to eliminate invalid state representations within the software.
An enum type example illustrates how using descriptive enumerations can prevent invalid inputs in function calls.
Example: Instead of accepting a generic string for a status in a function like
updateOrder(orderId: string, status: string), define an enumOrderStatus { PENDING, SHIPPED, DELIVERED }. This ensures only valid statuses can be passed, preventing silent failures from typos or incorrect inputs.
Guidelines for Code Maintenance
Performance and Clarity
Write code that prioritizes clarity and expressiveness before attempting optimizations; profile the code to identify performance bottlenecks.
Encourage constructing tight interfaces with minimal public exposure to maintain a clear architecture that’s easier to edit or refactor over time.
Conclusion
The session concludes with a brief recap of key mocking and domain modeling principles, emphasizing the need for clear communication with domain experts and maintaining best practices in software development.