My notes from John Reid‘s talk at iOSDevUK.
Barriers to TDD. Two primary:
- Not knowing what it is. Rejecting it as silly without giving it a try. A good try, as there is a learning curve. It will slow you down at first. If you give up before the payoff then you will say “oh that was stupid”.
- UI and Networking. On iOS most of what we do is UI and networking, rules out 90% of app, so not really useful.
EBay Fashion app. All test driven.
3 Types of Unit test:
- Return value test
- State test
- Interaction test
Patterns of testing. The Design Patterns book, the Gang of Four never intended it to be the beginning and end of design patterns.
Not going to be rocket since. About getting through the barrier. Writing unit tests after if necessary, but ideally before.
Return Value Test:
- Arrange: set up object.
- Act: Call method that returns a value.
- Assert: Compare against expected value.
With this alone, you should be able to get a very far distance. Onboarding engineers at Facebook, teach them not to be shy about extracting stand alone functions. Helps overcome that barrier.
- Arrange: Set up object.
- Act: Call method.
- Assert: Compare against expected value.
Since interested in a side effect, just need an additional call to verify state. Should be able to write quite a few tests with these two techniques.
Don’t need to be isolated units. They can be connected, as long as they are fast. Check that the system under test (SUT) is communicating correctly to something else.
Don’t want to talk to the real thing:
- Takes too long.
- Might not be there.
- May not have everything (don’t want to use things up).
- Might want to test the failure (normal end to end tests).
Want a fake thing that the test can control. Need dependency injection, if the middle thing is creating the end thing, it’s hard to test.
- Extract and Override.
- Method injection.
- Property injection.
- Constructor injection.
Difference between having a singleton, and a single way to access a singleton. E.g. NSUserDefaults. Don’t want to access it in this way.
Extract and Override: read “Working Effectively with Legacy Code” (Amazon).
TDD was working for me in a greenfield project, but how many of us get to stay in such a place?
Make a cut – subclass, override “userDefaults”, do what you want. Very powerful. Very effective with legacy code. Very dangerous. Like a drug. But will end up with the bane of testing code, fragile tests, because tests are coupled to implementation.
For getting started, especially with legacy code – good technique.
Better for other things, like calling “[NSDate date]” – will cause havoc with tests. Can swizzle, or just pass in what time you want. Now you will have a method that does more, now it’s tied to any time, not the current time. Helpful as context for injected object is very small. When spans across method, probably want to hang on to it as a property.
Test can inject the fake thing. But what about production code? Can end up with nil. Objective C will be like “whatever”.
Create custom getter with lazy eval. If no value, get the default value.
Inject in constructor – workhorse of dependency injection. Biggest benefit, makes everything explicit.
Can be annoying to have everything explicit. Long chain of dependencies is a code smell – you have too many dependencies.
Even then, you can simplify that, by using a Builder. Builder pattern creates the object you want according to however it is set. Set in any order, or not set and have it have defaults.
Constructor injection is the main one.
Ambient Context. Change something globally. Swizzling is an example of this. You can, sometimes helpful. But dangerous. Have to have your test restore the pre-test condition.
Let’s learn some good things from other people in other disciplines. There are plenty of smart people who are not using Obj-C
Types of Fakes: The Art of Unit Testing
- Stub: Fake that provides a pre-canned answer.
- Mock: Recording how it is called by the SUT, so that it can assert.
- Difference is which way the test is pointing to make it’s assertion.
Don’t need a DI framework in order to do DI as a concept.
Mocking, if never mocked before don’t use OCMock or OCMockito at first. Use them eventually. Meanwhile, you can make your own fake. Subclass and override all methods. Test Driven iOS development, means don’t have to do that in Obj-C. Dynamic language, supports DuckTyping.
Subclass NSObject. Put the method in. Use a simple property to record the number of calls. Have a fake return value (if unspecified is nil). Capture arguments.
Interesting thing about doing by hand, answers question of “what do we do in swift”. No introspection available to us. Do it by hand, laborious, might cry a little bit, but nothing stopping us.
Now we have a mock, use it. Start writing some tests.