Skip to content

Latest commit

 

History

History
286 lines (210 loc) · 12.7 KB

README.md

File metadata and controls

286 lines (210 loc) · 12.7 KB

Cucumber Roll Objective-C Cucumber Wire Protocol

Build Status

It allows Cucumber to touch your application in intimate places (to quote Cucumber's wire protocol feature description). Goals include and exclude:

  • Implement the Cucumber wire protocol.

    This is the most direct way to connect Cucumber to non-Ruby environments.

  • Not to link against the C++ standard library.

    Just pure Automatic Reference Counting (ARC) Objective-C based on Apple's Foundation framework.

Why the OC name-space prefix? OC stands for Objective-C. It emphasises the underlying dependency as well as the multiplatform capability. OCCukes supports Objective-C on all Apple platforms: iOS and OS X.

The project does not include an expectation framework. It only runs Objective-C coded step definitions. Your step definitions must assert appropriate expectations, possibly by running other step definitions. Since you write your Cucumber step definitions in Objective-C, you can use any kind of assertion framework, or even write your own. Exceptions thrown by the step become Cucumber step failures. See OCExpectations for an expectations library.

OCCukes and its companion OCExpectations sails as close to Cucumber and RSpec shores as is possible. If you are familiar with Cucumber and RSpec, you should find these projects refreshingly familiar; despite the differing implementation languages. Interfaces and implementations mirror their Ruby counterparts.

To use OCCukes, you need Xcode 4.4 or above, Ruby as well as Apple's Command Line Tools package. Install them on your Mac first. See Prerequisites for details.

Usage

OCCukes integrates with Xcode. You launch a Cucumber-based test suite along with any other Xcode project test suite: just press Command+U. Cucumber piggy-backs on the standard SenTestKit (OCUnit) tests.

To make this work, you need to launch Cucumber in the background while your test suite runs. Xcode's pre-actions let you do this. To set up the pre- and post-actions for your test target, just install Cucumber using RVM.

Test scheme pre-action

Make this your pre-action for the Test scheme:

PATH=$PATH:$HOME/.rvm/bin
rvm 1.9.3 do cucumber "$SRCROOT/features" --format html --out "$OBJROOT/features.html"

It sets up the PATH variable so that the shell can find RVM; Xcode resets the PATH when shelling out. It then launches Cucumber using RVM with Ruby 1.9.3; this looks for the latest 1.9.3-version of Ruby installed, but quietly fails if the latest version is not installed. Success assumes you have already installed Cucumber in the Ruby 1.9.3 RVM; adjust according to your local environment and personal preferences. Use gem install cucumber dnssd within the selected Ruby version to install Cucumber and its dependencies.

Test scheme post-action

Then make this your post-action:

open "$OBJROOT/features.html"

Wire protocol configuration

Add a wire configuration to your features/step_definitions folder, a YAML file with a .wire extension. Contents as follows.

host: _occukes-runtime._tcp.

# The default three-second time-out might not help a debugging
# effort. Instead, lengthen the timeouts for specific wire protocol
# messages.
timeout:
  step_matches: 120

Host and port describe where to find the wire socket service. If you want to use Bonjour (DNS Service Discovery, DNSSD) to resolve the host address and the port number, specify a DNS service name as the host. This triggers Bonjour discovery. The Cucumber wire service accepts connections at port 54321 on any interface. So you can connect to non-local hosts as well.

You can override the Cucumber runtime connect and disconnect timeouts at the command line. For example, use

defaults write org.OCCukes OCCucumberRuntimeDisconnectTimeout -float 120.0

to reconfigure the disconnect timeout to two minutes. Express timeouts in units of seconds. Display the current configuration using

defaults read org.OCCukes

Environment Support

Set up your features/support/env.rb. You can copy this code from features/support/env.rb. The Ruby code defines a Cucumber AfterConfiguration block for daemonising the Cucumber process and waiting for the wire server to begin accepting socket connections. This block runs after Cucumber configuration.

Add test case

Finally, integrate Cucumber tests with your standard unit test cases by adding steps.

Basic template for some step definitions, MySteps.m:

#import <OCCukes/OCCukes.h>

__attribute__((constructor))
static void StepDefinitions()
{

}

Make as many such step definition modules as required. Organise the steps around related features.

Register your step definitions before executing the Objective-C Cucumber runtime by sending -run. As you see above, definitions manifest themselves in Objective-C as C blocks. These blocks assert the step's expectations, throwing an exception if any expectations fail. Steps therefore succeed when they encounter no exceptions.

The runtime accepts connections until all connections disappear. By default, it offers an initial 10-second connection window; giving up if Cucumber fails to connect for 10 seconds. The runtime also offers a 1-second disconnection window before shutting down the socket. This allows all connections to disappear temporarily. You can adjust these connect and disconnect timeouts if necessary.

Link your test target against the OCCukes.framework for OS X platforms; or against the libOCCukes.a static library for iOS test targets. For iOS targets, you also need OTHER_LDFLAGS equal to -all_load; the linker does not automatically load Objective-C categories when they appear in a static library but this flag forces it to.

Advantages

Why use OCCukes?

Test bundle injection

Takes advantage of Apple's test bundle injection mechanism. In other words, you do not need to link your target against some other library. Your target needs nothing extra. This obviates any need for maintaining multiple targets, one for the application proper, then another one for Cucumber testing. Xcode takes care of the injection of the wire protocol server along with all other tests and dependencies at testing time.

This prevents a proliferation of targets, making project maintenance easier. You do not need to have a Cucumber'ified target which duplicates your target proper but adds additional dependencies. Bundle injection handles all that for you. One application, one target.

No additional Ruby

The OCCukes approach obviates any additional Ruby-side client gem needed for bridging work between Cucumber and iOS or OS X. Cucumber is the direct client end-point. It already contains the necessary equipment for talking to OCCukes. No need for another adapter. OCCukes talks native Cucumber.

This also means that you do not need to build and maintain a skeletal structure within your features just for adapting and connecting to a remote test system. Cuts out the cruft.

No private dependencies

The software only makes use of public APIs. This makes it far less brittle. Private frameworks can and do change without notice. Projects relying on them can easily become redundant especially as Apple's operating systems advance rapidly.

Multiple subprojects

The OCCukes organisation publishes various Cucumber-related subprojects. Although the OCCukes project lies at the core, complementary projects OCExpectations and a slew of iOS-specific spinoffs exist: UICukes, UIExpectations and UIAutomation. The structure helps to avoid an all-or-nothing mindset. Take whatever best suits your needs. The projects aim at various kinds of development projects: iOS application, Mac application, iOS library, or Mac framework.

OCCukes sub-projects prefixed by OC have Objective-C and Foundation framework dependencies. That means they work on iOS and OS X platforms. They are cross-platform projects and incorporate iOS library targets as well as OS X framework targets.

Projects prefixed by UI have iOS UIKit dependencies. They aim at iOS projects only. Their Xcode projects contain a single iOS static library target. UICukes acts as an umbrella project for iOS dependencies. It pulls in all other sub-projects needed for Cucumber on iOS. iOS developers will therefore normally clone out the UICukes submodule by itself. Doing so pulls in all other dependencies as sub-submodules.

Troubleshooting

Cucumber launches but invokes no steps

You press Cmd+U in Xcode to run your tests. There is a brief pause then you see the Cucumber output. Cucumber has executed and parsed the features and scenarios. Trouble is, Cucumber stops at the first scenario's first step. No steps execute. When you set a breakpoint within your step definitions, sure enough, they never run.

Solution

Make sure that you are not running a Cucumber instance in some other process. For example, you might be running it as part of an IDE for some other project, or some other components of the same project. Terminate the other Cucumbers and re-test.

When and how to launch Cucumber?

Running Cucumber with Mac or iOS software requires two synchronised processes: a Cucumber client in Ruby, and an Objective-C wire server running within an application test bundle. The server needs to run first in order to open a wire server socket. The socket may be local or on another device. Actual iOS devices do not share the same local host. Hence the Cucumber client cannot assume localhost.

Wire server and client need to synchronise their execution. When Cucumber runs, it expects to connect to the wire server when it finds a wire configuration. Hence the Test scheme needs to launch the server beforehand. Best way to launch Cucumber from Xcode: use a pre-action within the Test scheme. Cucumber therefore needs to do three things at launch time:

  1. fork itself into the background so that Xcode testing can proceed, that is, daemonise;
  2. wait for the wire server to set itself up;
  3. close down the wire server when Cucumber finishes running its features and invoking the remote-wire step definitions. This can happen automatically when it server sees no wire connection after a given period of time, say a second.

Step 2 raises issues when Cucumber tests actual iOS devices where the server does not open a port on localhost. Waiting for the wire server to initialise (step 2) requires that the Cucumber run-time environment establishes the wire server's address and port information. Without it, the Cucumber run will fail. The environment needs to wait an acceptable time for the server to appear on the prescribed port. Moreover, the client-side environment needs to attempt and reattempt to connect. The initial attempts will likely fail with a "refuse to connect" exception, simply because the server has not yet opened the socket. Steps 2 and 3 together imply a setting up and tearing down at the Cucumber client side.

MIT Licensing

Copyright © 2012, 2013 The OCCukes Organisation. All rights reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Sponsors

  • Levide Capital Limited, Blenheim, New Zealand

Contributors

  • Roy Ratcliffe, Pioneering Software, United Kingdom
  • Bennett Smith, Focal Shift LLC, United States
  • Terry Tucker, Focal Shift LLC, United States