This source code is the one I have used as exercises for the Ironhack training. I am responsible for teaching the Core Data and Testing (unit testing, TDD) week and I decided to create this examples that can be used as exercises. Feel free to use them. And if you are interested in more code like this, I do contract work. Contact me:
- Email: jortiz -at- powwau.com
- Twitter: jdortiz
The core contents of the week are about the model part of an application. In the examples I won't purposely focus on the visual part: no autolayout constraints, no fancy animations, no problem with poor ux.
Git is assumed. ALWAYS commit after each section of the exercises. The repo has been created with that philosophy, each section corresponds to a commit. Thus, you can checkout the version before the exercise and compare it with the next commit to check my answer to the exercise.
I do know that not all the answers are optimal, some will have to be improved for performance in production code. However:
- I am against of premature optimization.
- I rather favor legibility than performance.
- These examples are meant for other people to understand what is going on and learn from them.
Learn how to write code the TDD way.
-
Create new project in Xcode. Use the "Empty application" (iOS) template.
-
Call it "MalometerTDD" and use Core Data.
-
Close the project window.
-
Create a Podfile in the project directory.
platform :ios, "7.0"
target "MalometerTDD" do
end
target "MalometerTDDTests" do pod 'OCMock' end
-
Execute
pod install
-
Copy the Core Data Test template
$ cd $ tar xjvf /UnitTestTemplates.tar.bz2
-
Open MalometerTDD.xcworkspace
Make an initial test, in order to understand the pieces and the mechanics.
- Create the Agent entity in the model and declare the name, destructionPower and motivation as they were defined before.
- Generate the subclass.
- Generate the Model category.
- Remove the existing test.
- Create a new test file from the new template.
- Replace the imported header.
- Uncomment the createCoreDataStack invocation.
- Change the createSut to use a convenience constructor.
- Run the test and see that it fails.
- Add the source code in the category to create the Agent object.
- Run the test and see that it fails, but notice that it is related to "Agent not being located in the bundle."
- Add the xcdatamodel file to the Tests target.
- Run the test and see that it succeeds.
Now you play to get code from the tests.
Core Data properties need no testing, but the assessment that is calculated, is somthing to be checked.
- Add the transient property to the model as you did before.
- Write the first test to check the assessment value given a combination of destruction power and motivation.
- Run the test. Red.
- Hardcode the result.
- Run the test. Green.
- Add the second test for the transient property with another combination of values.
- Run the test. Red.
- Generalize the resuls.
- Run the test. Green.
will/didAccessValueForKey is required for maintaining relationships and unfaulting, so we must be sure that it works.
- Test behavior using OCMock.
- Understand the problems of mocking and Core Data.
- Replace properties with primitive values and voila!
- Refactor to include constants.
Test that objects observing changes of assessment are notified when the other two properties change.
- Create a boolean iVar to flag changes and reset in the test setUp.
- Create a test that observes changes of assessment when motivation is changed.
- Write the code to pass the test. Notice that it breaks previous tests because the motivation isn't persisted.
- Write another test to persist motivation.
- Write the code to pass the tests.
- Add another test to check full KVO compliance of motivation.
- Repeat the process for destructionPower.
Test other logic of the model.
- Add tests and code for the picture logic.
- Add tests and code for the fetch requests.
Extend the tests to the FreakType entity.
- Create the FreakType entity and the subclass.
- Run the test to confirm that everything is fine.
- Test the convenience constructor, NOT the relationship.
- Create a fixture for the fetches.
Validation is a key part of Core Data. Test that the data is validated only if follows our requirements and understand how to make this custom validations.
- Write the test to confirm that an empty agent name cannot be saved.
- Change the model to disallow empty agent names.
- Make another test to see that a name consisting only on spaces will not be accepted when saving.
- To add the validation include the validateName:error: to the Agent category. Pay attention to the input value: it is a pointer to an NSString *.
- Add another test to verify that the validation returns an error with a code when it doesn't pass.
- Define the error codes in the category header.
- Create the error with the proper information to pass the test.
Undertand a different way to work with the Core Data Stack.
- Create the MalometerDocument class as subclass of UIManagedDocument.
- Create its corresponding test class.
- In the createSut method, create a fake URL and a new document with the initWithFileURL: initializer.
- Verify that the test passes.
- Write a test to ensure that the managed object context that the document provides is the one that you create for the tests.
- Replace the context in the createSut method.
- Run the test. It will fail. Change the context creation to be created with concurrency type NSMainQueueConcurrencyType.
- Test that pictures are deleted when objects are.
Make our object able to work with data in a different format.
Use convenience constructors, that consume the data in the expected format.
- Create a convenience constuctor that takes the MOC and a dictionary as parameters.
- Verify (tests) that the name, destruction power, and motivation are preserved.
Apply the same concepts to the other entities.
- Create a convenience constuctor for the FreakType that takes the MOC and a dictionary as parameters.
- Verify (tests) that the name is preserved.
- Create a convenience constuctor for the Domain that takes the MOC and a dictionary as parameters.
- Verify (tests) that the name is preserved.
Understand how the relationships can be recovered from the data and applied to our model.
- Modify the agent convenience constructor (with tests) so it is related with the (pre-existing) category that has the name provided in the dictionary with the "freakTypeName" key, as name.
- Modify the agent convenience constructor (with tests) so it is related with the (pre-existing) domains that have the names (array) provided in the dictionary with the "domainNames" key, as name.
Make the importer method that performs all the required steps.
- Create the importData as a document method that takes a dictionary.
- Create (and test) as many FreakTypes as indicated in the array contained under the "FreakTypes" of the main dictionary key.
- Create (and test) as many Domains as indicated in the array contained under the "Domains" of the main dictionary key.
- Create (and test) as many Agents as indicated in the array contained under the "Agents" of the main dictionary key.
Understand how to provide seed data in our app.
- Create another lazy loaded property that returns the URL to the initial data. If it doesnt exist, it returns a path to a resource in the main bundle.
- Create a lazy loaded property for the document class for the file manager. If it doesn't exist returns the defaultManager.
- Create a test to verify that if the storeURL passed to configurePersistentStoreCoordinatorForURL:ofType:modelConfiguration:storeOptions:error: doesn't exist, it is copied from the URL to the initial data.
- Remember to call super.
- Create an OSX target that loads a JSON file and uses this to store the data.
- Import images.
- Export methods.