Is this a good practice to extract class extension into a separate header file for unit testing purposes in Objective-C?

StackOverflow https://stackoverflow.com/questions/21952895

문제

Related to previous question about naming convention of class extensions:

In terms of API design, is this a good practice to extract class extension into a separate header file to provide private property access for unit tests?

// MyClass_Private.h
@interface MyClass()

@property (nonatomic, strong) NSString *myString;

@end

MyClass_Private indicate that this is a private class extension of MyClass. Is there any drawback of separating the class extension into a separate file? It fits perfectly with my needs, but I have never seen this in practice.

Update:

My question was theoretical, but to be concrete: I would like to mock some configuration value inside the implementation of MyClass. I would like to avoid passing these configuration values as parameters through the public interface because I would like to hide the class's implementation details.

For example to pass NSUserDefaults as parameters to every class under test seems a bit overcomplicated. That's why I decided to provide property access for unit tests through the class extension. Then I can mock NSUserDefaults used in class implementation that otherwise would come from the real user defaults.

도움이 되었습니까?

해결책 2

With NSUserDefaults, your example makes sense as it's a dependency which should be replaced by a mock in unit tests. I discovered that I can do what you want to achieve in other way. Instead of keeping a separate header file with private property to be exposed to unit tests, I can define the property in the unit tests!

In MyClass.m:

@interface MyClass ()
@property(nonatomic, weak) NSUserDefaults* userDefaults;
@end

@implementation MyClass

@end

And then in unit tests you can also define a class extension for MyClass with userDefaults property. Then you can set the property to a mock object instead of the 'real' NSUserDefaults.

@interface MyClass ()
@property(nonatomic, weak) NSUserDefaults* userDefaults;
@end

@interface MyClassTest : XCTestCase
@property(nonatomic, strong) MyClass* testedObject;
@end

@implementation MyClassTest

- (void)setUp
{
    [super setUp];
    self.testedObject = [[MyClass alloc] init];
    //I use OCMock in my example, of course any framework can be used
    self.testedObject.userDefaults = OCMockObject mockForClass:[NSUserDefaults class]];
}

Of course it's not a perfect solution. Its advantage is that you don't have to split private interface of MyClass into two files. On the other hand, it's kind of a code duplication but the property is only duplicated in the test code, not the production code. It's a trade-off and you have to decide which approach is better for you.

다른 팁

Yes and no...

When unit testing, your aim is to prove that the unit correctly conforms to the interface it offers. You shouldn't need to know the internal details to do that. You can run tests passing a range of values to the unit and verify the response / result. If an interface is private then it is considered out of scope for the test.

You can run tests which include the private interface, that is a choice. From an implementation standpoint you can make the private interface publicly available to support this. If you aren't releasing a library and making the interface public then there is little cost to you (documentation of the purpose should suffice). An alternative is to keep the interface private and use the dynamic features of the language (runtime method lookup and invocation) to run the tests (be defining the private interface is a category in the test). The only disadvantage of that is that you won't learn that API changes in the class have broken the tests till you run them.

This is the difference between black box and white box testing. You need to decide which is appropriate for your situation and what level of interaction you want to allow.

Note that nothing is truly private. Using introspection and runtime lookup, your private methods are fully accessible...

I think that private methods should remain private: from the outside world you are testing that all available methods behave correctly. You don't have access to the private methods.

If your tests start failing on a public method, it will likely indicate a failure at the private level.

Test that a function returns a reversed string; don't test that charAtIndex() doesn't return an int - it isn't your current responcibility.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top