[Study Note] Design and Testability 20100427

[Mock Objects and Stubs: Introduction to Mocks and Stubs]

One of the biggest challenges of using TDD is learning strategies for isolating the code that’s hard to test and writing code that is easy to test. One of the primary strategies to extend unit test coverage into those hard to reach places is to use mock objects, stubs, or other fake objects in place of the exteral resources so that your test don’t involve the external resource at all.

One of the central pillars of Object Oriented programming is polymorphism – the ability to interchangeably utilize multiple implementations of a well-defined interface.

What's the Difference between a Mock and a Stub ?

A stub is a class that is hard-coded to return data from its methods and properties.

A Mock object is a tool for interaction based testing, using a mock object to record and verify the interaction between two class.

usage of a mock object

  1. Create the mock object
  2. Set the expectation on the mock object
  3. Create the class that's being tested
  4. Execute the method that's being tested
  5. Tell each mock object involved in the test to verify that the expected calls were made

Dynamic versus Static

four permutations: dynamic stub, static stub, dynamic mock, static mock

use static mocks whenever faced with a lot of name/value pairs or set based argument.

don't blow off the dynamic mock tools just because they look scary at firs view.

The Case for Dynamic Mocks   

STRONG preference to use dynamic mocks.

The Case for Static Mocks

Using reflection of any kind can easily lead to brittle code that is harder to refactor.

[Mock Objects and Stubs: When and Why to use Mocks ans Stubs]

The benefit of using mock objects

1. increased testability throughout a system.

2. improved ability to do continuous design within your code.

Improved Unit Testing

  • Unit test should be atomic.
  • Order Independent and Isolated.

Never assume that the tests are run in any certain order.

Each test should start from a completely known state and clean up after itself if necessary.

Unit Test Culprit: static properties, singletons, and repositories, testing against the database
The entire purpose of a database is to maintain state, and that's not a particularly good trait inside a unit test.

  • Intention Revealing.

If the responsibilty of the class being tested is to direct or coordinate other classes, then using a mock object inside the test may make the test easier to understand.

Simply pay attention to what you're coding. If it feels like using a mock object is making the test harder, back up and find a different way to write the test or change the class structure.

  • Easy to Setup.

use mock objects is to avoid the need to setup external resources into a known state for each test.

  • Runs Fast.   

Designing for and with Mock Objects

mock as spy

Client First Development

Model View Presenter

  • Presenter - handles the user information flow logic and intermediates communication between the actual screen and the backend services
  • IService/Service - the gateway to the backend business or service layer
  • IView/View - the actual screen

"do you know how nic it is to get piece of code into NUnit or the debugger without having to install every single other service ? " - Jeffrey Palermo

What to Mock?

mock anything that is involves any kind of call or access to things outside of the AppDomain.

Mocking the data access layer when you're testing business or service layer logic.

  1. WinForms code.
  2. ASP.NET objects - user controls, HttpContext objects(but I'd advise against doing this directly)
  3. Web Service proxy classes. Absolute no-brainer.
  4. Database access code, persistence
  5. Active Directory access.
  6. Gateways to weird legacy code or system - and almost all legacy code IS weird
  7. Facades to other subsystems
  8. Remoted classes

[Mock Objects and Stubs: Best and Worst Practices for Mock Objects]

Mocking Interfaces and Classes outside Your Code

Be careful about mocking or stubbing any interface that is outside your own codebase. Best advice is to create your own interface to wrap the interaction with the external API classes.

Avoid Mocking Fine-Grained or Chatty Interface

Just test the data access code against the actual database with integration test.

Excessive numbers of fine-grained expectations will make the tests very brittle to changes in the code.

How Many Mock Objects in any Given Test ?

Don't make a hard and fast rule on the limit, but anything more than 2 or 3 should probably make you question the design.

Excessive mock calls can often be a sign of poor encapsulation.

too much mocking, either in the number of mock objects or the setup code, in any single unit test is a Coding Smell.   

Only Mock your Nearest Neighbor

Ideally you only want to mock the dependencies of the class being tested, not the dependencies of the dependencies.
If the Singleton is in an unknown state or the internal caching mechanisms of the repository change, you will end up rewriting unit tests.

Mock the Right Thing

Watch Out When You Mock Abstract Classes

Only mock interfaces to avoid unwanted side effects.
Dynamic mock object tools like NMock can only override virtual methods, with the .Net languages all methods are non-virtual by default.
The solution is might be simple; look for an opportunity to do an Extract Interface refactoring to create an interface that can be mocked without side effects.

[Mock Objects and Stubs: Mock Objects are your Friend]

Mock objects are a Design Tool

mock objects as the TDD equivalent of drawing a UML sequence diagram, except in code.
Mock objects can be used to define a new interface by simply working through the requirements of its client.

[Testing Granularity, Feedback Cycles, and Holistic Development]

Repid and continuous feedback is one of the best an most important attributes of Agile development.

Test Small Before Testing Big

Granular unit tests are easy to debug.

Do the granular unit tests first to mitigate the "debugger hell" of the large integration tests.

"tests are also a way to preserve behavior in the face of later changes." – Feathers

A rigorous unit test suite tell you exactly when and where your new code changes break existing code.

Coarse-grained tests may only tell you that you've broken something -- somewhere.   

Unit Tests Should Be Fast

write a unit test, make unit test pass, refactor, repeat

Over time the overhead charge of the code consolidation will rapidly pay for itself many times over in improved developer efficiency.

Optimizing Build Time

"Teams that want to be more agile are headed for a train wreck if they have long build times; they'll need to find ways to build all or part of the software more frequently to get the kind of continuous feedback that helps agile teams move quick." - Andy Hunt

dealing with slow unit testing

  1. Aggressively mock the database during unit tests. Persistence Ignorant
  2. Use some sort of Model View Presenter architecture for user interface code, a.k.a. "The Humber Dialog Box"
  3. Always use the Dependency Inversion Principle when accessing web service in code.
  4. Decouple the actual functionality of a web service from the SOAP message transport. The web service should be a thin wrapper delegating to a plain old object.

An experienced TDD team will purposely design an applicaton with ease of testing and build automation as a first class consideration.

Holistic Approach

In the end ,the extra time spent writing unit test code seems to consistenly pay off getting the code to production quality faster.

any technique that gets you to done faster is worth writing more or just different code.

The "Idea Wall"

Pick some visible spot on the wall in the team area, and post any sort of idea for improving the project infrastructure on "Post-It" notes or index cards.

Don't tolerate inefficiency in your build or development.

posted on 2010-04-28 23:40  zhaorui  阅读(192)  评论(0编辑  收藏  举报

导航