Slide 1

Slide 1 text

BEYOND XCTEST ABIZER NASIR | @ABIZERN | ABIZERN.ORG

Slide 2

Slide 2 text

I AM NOT A TESTING PURIST !

Slide 3

Slide 3 text

WHAT’S WRONG WITH XCTEST?

Slide 4

Slide 4 text

NOTHING • It works • It’s always available • It’s easy to understand

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

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)

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

THERE ARE ALTERNATIVES

Slide 9

Slide 9 text

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.

Slide 10

Slide 10 text

SPECTA/EXPECTA

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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"); }); });

Slide 14

Slide 14 text

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(); }]; });

Slide 15

Slide 15 text

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)

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

OCMOCK

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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");

Slide 20

Slide 20 text

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();

Slide 21

Slide 21 text

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.

Slide 22

Slide 22 text

STUBBING NETWORK REQUESTS

Slide 23

Slide 23 text

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.

Slide 24

Slide 24 text

context(@"with a valid username and password", ^{ __block __weak id _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]; });

Slide 25

Slide 25 text

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.

Slide 26

Slide 26 text

FINAL THOUGHTS

Slide 27

Slide 27 text

“If you can’t test, try and write your code as if you will test it.”

Slide 28

Slide 28 text

“Set up the test environment at the start of the project.”

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

“Don’t try and write clever tests. Dumb code, copy and paste are acceptable.”

Slide 31

Slide 31 text

“You get better at testing by writing tests.”

Slide 32

Slide 32 text

“Red. Green. Refactor with confidence. If you don’t see your tests fail at first, you can’t be sure that they are running.”

Slide 33

Slide 33 text

“It’s never too late to test.”

Slide 34

Slide 34 text

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