Towards a Realistic Example

We have started building a comprehensive example using mbeddr C. We have selected Lego Mindstorms together with the nxtOSEK operating system. OSEK is a real-world embedded operating system, so our example does have some real-world relevance.

The main two reasons for builing the thing is that we need a showcase for our talk and booth at Embedded World 2012 and that we want to eat our own dogfood. The latter has already proven a good idea, we’re finding bugs, problems and places for improvements left and right (and we’re addressing them of course).

As of now, we have the motors, the gun and the compass at work (the compass is the thing on the big pole — we try to avoid interferences). Next will be the bumpers, the light sensor and the sonar. A lot of fun :-)

Tesing Components with Stubs and Mocks

As mentioned before, we consider components as one of the most important extensions provided by mbeddr C. A big advantage of the component-based approach is that the specification of behavior is separated from the implementation. This way, the same interface can be implemented by different components. A particular interesting use case for this feature is the implementation of interfaces specifically for testing purposes as stubs and mocks.

A stub component is a component that implements “dummy” behavior to be used in a test case. Essentially, a stub is just a normal implementation, but it provides counters regarding how often an operation (or port, or the instance in total) has been called. The figure below shows an example. The built-in expression opCallCount is used, together with the in operator, that checks a value for membership in a range.

Mocks are more interesting. They are used to verify that another component behaves as expected. To this end, a mock specifies the expected behavior it wants to see on a provided port. The example below shows a mock component that provides a port the implements PersistenceProvider, which provide the isReady, store and flush operations. The mock specifies that, in a given test case for which the mock is defined, it expects 4 operation calls in total. It specifies a sequence of calls; the first one expects isReady, and returns false. The second expected call is again isReady, but this time we return true. We then expect store to be called, and we check that the data argument is not null. Finally, we expect flush to be called.

A mock component can be instantiated and wired up like any other component. Since a mock is expected to be used in a test case, there is a special new statement that verifies whether the actual behaviour conformed to the expected behaviour. The figure below shows an example. It fails the test case if the mock does not validate.

The mock specification language is a nice example of a DSL that makes sense to be integrated with C. We also reuse C’s expressions in mock expectations. Stubs and mocks also illustrate nicely why a separation between interfaces and the implementation is a useful software engineering paradigm.

From Interfaces to Contracts

In a previous post we have talked about components and interfaces. We believe that components are an essential and widely applicable extension to C is the basis for meaningful reuse and variability management. In our approach, the same interface can be provided (implemented) by several different components, in different ways. Of course, the semantics implied by the interface should be realized consistently by all these components. To enforce this, an interface must specify more than just the signature of the operations.

We have implemented two additional facilities: pre- and post conditions as well as protocol state machines.

Preconditions express constraints over the arguments of operations when they are called. The figure below shows an example. In the add operation, two preconditions are used to make sure the arguments are greater than zero. Postconditions ensure that the result is positive and the result is the sum of the two arguments. The Computer component below, which implements the interface, will verify the correctness of these constraints when operations are called. If they are violated, the message associated with the pre or postcondition is reported. Note that the pre and postconditions are expressed over the interface, and all components that provide the interface automatically get the verification code.

Sometimes postconditions have to be expressed over the state of an object. Since interfaces cannot define state, we have to use query operations to access the state. A query operation is an idempotent operation, that can be called at any time to return a value. In the example below, the value operation is marked as a query and hence can be used in pre and postconditions. Note also how the old keyword can be used to access the result of the value operation before the operation is executed.

The other ingredient to contractual interfaces are protocol state machines. These can be used to enforce the sequencing of calls on an interface. Take a look at the code below. It defines a protocol for each operation. The openForWrite operation requires the protocol state machine to be in the init state, and when called, transitions to the writing state. Once in writing, you can call write any number of times and then call close to go back to the init state. Reading works similarly. Essentially, the protocol state machine in the example enforces that the file system can be used for writing only or for reading only. Close resets it. The init state is the default state available in any protocol state machine. The * state is a shorthand for any state. Using the new keyword, new states can be introduced, and those can be referenced from other protocols. An operation can have several protocols, they are or’ed together.

Pre and Postconditions and protocols can be mixed. This way, a relatively complete specification of an interface’s semantics can be provided. The constraints are automatically checked on every implementing component, enforcing the contract.