Beyond XCTest

Beyond XCTest

Slides from my London iOS Developer Group Presentation from May 2014.

F28c4835c9e2277b6e04b86574532a2d?s=128

Abizer Nasir

May 07, 2014
Tweet

Transcript

  1. BEYOND XCTEST ABIZER NASIR | @ABIZERN | ABIZERN.ORG

  2. I AM NOT A TESTING PURIST !

  3. WHAT’S WRONG WITH XCTEST?

  4. NOTHING • It works • It’s always available • It’s

    easy to understand
  5. None
  6. BUT… • It can be long winded to write •

    Different initial states usually require different setups • Tends to be focused on class and method level tests • It’s missing some functionality (sort of)
  7. None
  8. THERE ARE ALTERNATIVES

  9. YOU COULD CONSIDER… • Specta/Expecta or Kiwi to write tests

    in a more BDD, RSpec form. • OCMock for simple mocking. • OHHTTPStubs or Nocilla for stubbing network requests.
  10. SPECTA/EXPECTA

  11. RUBY HAS RSPEC context "Working with files" do ! before(:each)

    do tests_dir = Dir.pwd + "/tmp/spec/" ! if File.exists? tests_dir FileUtils.rm_rf tests_dir end ! FileUtils.mkdir_p tests_dir ! FileUtils.cp_r Dir.pwd + "/spec/fixtures/input/.", tests_dir end ! after(:each) do tests_dir = Dir.pwd + "/tmp/spec/" ! if File.exists? tests_dir FileUtils.rm_rf tests_dir end end ! describe "#process_file" do ! it "manages to update multiple files" do files = Dir.glob Dir.pwd + "/tmp/spec/*.{h,m}" files.each do |f| Fixbraces.process_file f end ! result = `diff -r --brief #{Dir.pwd + "/tmp/spec/"} #{Dir.pwd + "/spec/fixtures/expected/"}` expect(result).to eq "" end
  12. SPECTA PROVIDES THIS SpecBegin(SomeDescription) __block SomeObject *_someObject; ! beforeEach(^{ //

    Global setup }); ! afterEach(^{ // Global Teardown }); ! describe(@"Some property of the SUT", ^{ __block NSDictionary *_availabilityDictionary; // Local variable to scope context(@"Specific case", ^{ beforeEach(^{ _someObject.property = value; }); afterEach(^{ // Any other clean-up }); it(@"Description of the expectation", ^{ BOOL success = [_someObject someMethod]; // expectation }); it(@"Another action with an expectation", ^{ // }); }); ! SpecEnd
  13. SIMILARLY, WE CAN WRITE describe(@"With a basic availability dictionary with

    no tiers", ^{ __block NSDictionary *_availabilityDictionary; ! context(@"given a valid dictionary", ^{ beforeEach(^{ _availabilityDictionary = @{@"time" : @"09:00", @"duration" : @"2H"}; }); ! afterEach(^{ _availabilityDictionary = nil; }); ! it(@"creates a basic availability object", ^{ TXHAvailability *availability = [TXHAvailability updateForDateCreateIfNeeded:@"2013-12-29" withDictionary:_availabilityDictionary productId:_product.objectID inManagedObjectContext:_moc]; expect(availability).toNot.beNil(); expect(availability.dateString).to.equal(@"2013-12-29"); expect(availability.timeString).to.equal(@"09:00"); expect(availability.duration).to.equal(@"2H"); }); });
  14. TEST ASYNCHRONOUS CODE! it(@"creates the suppliers and related products and

    users", ^AsyncBlock{ [_client fetchSuppliersForUsername:@"abc" password:@"cde" withCompletion:^(NSArray *suppliers, NSError *error) { expect(suppliers).to.haveCountOf(2); expect(suppliers[0]).to.beKindOf([TXHSupplier class]); for(TXHSupplier *supplier in suppliers) { expect([supplier.products count]).to.beGreaterThan(0); expect(supplier.user.email).to.equal(@"abc"); } ! done(); }]; });
  15. EXPECTA FOR EXPECTATIONS expect(x).to.equal(y) expect(x).to.beIdenticalTo(y) expect(x).to.beNil() expect(x).to.beTruthy() expect(x).to.beFalsy() expect(x).to.contain(y) expect(x).to.beSupersetOf(y)

    expect(x).to.haveCountOf(y) expect(x).to.beEmpty() expect(x).to.beInstanceOf([Foo class]) expect(x).to.beKindOf([Foo class]) expect([Foo class]).to.beSubclassOf([Bar class]) expect(x).to.beLessThan(y) expect(x).to.beLessThanOrEqualTo (y) expect(x).to.beGreaterThan(y) expect(x).to.beGreaterThanOrEqua lTo(y) expect(x).to.beInTheRangeOf(y,z) expect(x).to.beCloseTo(y) expect(x).to.beCloseToWithin(y, z) expect(^{ /* code */ }).to.raise(@"ExceptionName") expect(^{ /* code */ }).to.raiseAny() expect(x).to.conformTo(y) expect(x).to.respondTo(y) expect(^{ /* code */ }).to.notify(@"NotificationNa me") expect(^{ /* code */ }).to.notify(notification) expect(x).to.beginWith(y) expect(x).to.endWith(y)
  16. None
  17. OCMOCK

  18. WHEN DO I NEED TO MOCK • You want to

    specify the return from a method call. • You want to verify that a method has been called. • You want to verify that only some methods are called
  19. STUB AND RETURN // Create a stubbed class ! id

    mock = [OCMockObject mockForClass:[SomeClass class]] ! // specify a return when a method is called ! [[[mock stub] andReturn:@"Hello"] greet:[OCMArgAny]; ! // You can now test against it. expect([stub greet]).to.equal(@"Hello");
  20. VERIFY METHOD CALLS // Create a stubbed class id mock

    = [OCMockObject mockForClass:[SomeClass class]] ! // Create a expectation [[mock expect] greet:[OCMArg any]]; ! // Do something that might call the method expect([stub greet]).to.equal(@"Hello"); ! // verify that it is called expect([mock verify]).toNot.raiseAny();
  21. CONSIDERATIONS • I don’t use them much. There are other

    ways of getting results back from calls. • Sometimes, their use depends on implementation details, which can be brittle.
  22. STUBBING NETWORK REQUESTS

  23. AN ALTERNATIVE TO MOCKING • Doesn’t send out to the

    network • Store the response as a file, that is loaded in response to a network call • Easier than writing mocks and better for behavioural testing.
  24. context(@"with a valid username and password", ^{ __block __weak id<OHHTTPStubsDescriptor>

    _suppliersStub; ! before(^{ NSDictionary *httpHeaders = @{@"Content-Type" : @"application/json"}; _suppliersStub = [OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) { return [request.URL.lastPathComponent isEqualToString:@“suppliers"]; ! } withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) { return [OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(@"Suppliers.json", nil) statusCode:200 headers:httpHeaders]; }]; _suppliersStub.name = @"stub with suppliers"; }); ! after(^{ [OHHTTPStubs removeStub:_suppliersStub]; });
  25. IT’S STILL A MOCK, THOUGH • It is implementation dependent,

    you need to know the endpoint, and the content of the response. • It is useful if the results change, you just need to keep it up to date. • In the context of testing with expectations, it makes sense. i.e. I do something, and I can text against the eventual expected value.
  26. FINAL THOUGHTS

  27. “If you can’t test, try and write your code as

    if you will test it.”
  28. “Set up the test environment at the start of the

    project.”
  29. “Writing tests is a great way to avoid procrastination.”

  30. “Don’t try and write clever tests. Dumb code, copy and

    paste are acceptable.”
  31. “You get better at testing by writing tests.”

  32. “Red. Green. Refactor with confidence. If you don’t see your

    tests fail at first, you can’t be sure that they are running.”
  33. “It’s never too late to test.”

  34. THANK YOU! NOW GO LOOK THESE UP • https://github.com/specta/specta •

    https://github.com/specta/expecta/ • http://ocmock.org • https://github.com/AliSoftware/OHHTTPStubs • https://github.com/kiwi-bdd/Kiwi • https://github.com/luisobo/Nocilla