Date post: | 13-May-2015 |
Category: |
Technology |
Upload: | skills-matter |
View: | 961 times |
Download: | 1 times |
Testing Embedded Systems With Cucumber
Ian Dees • @undeesCukeUp! NYC 2013
Plenty of Ruby, but...
There will be C
There will be C++
There will be C#
http://pragprog.com/titles/dhwcr
discount code:CucumberIanDees
The Embedded Continuum
almost acomputer
chip andsome ROM
Simple devicesNo Ruby, no Cucumber
Gray CodeA simple system to test
Feature: Gray Code Scenario Outline: Counter When I press the button Then the LEDs should read "<leds>"
Examples: | leds | | ..O | | .OO | | .O. | | OO. | | OOO | | O.O | | O.. | | ... |
Arduino
void loop() { button.update(); bool buttonPressed = button.risingEdge();
if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); }
delay(50);}
Drive code directly
void loop() { button.update(); bool buttonPressed = button.risingEdge();
if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); }
delay(50);}
void loop() { button.update(); bool buttonPressed = button.risingEdge();
if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); }
delay(50);}
static bool isFakeButtonPressed;
class Bounce {public: // ...
bool risingEdge() { bool result = isFakeButtonPressed; isFakeButtonPressed = false; return result; }};
extern "C" void press() { isFakeButtonPressed = true;}
const char* leds() { static char buf[LEDS + 1] = {0};
for (int i = 0; i < LEDS; ++i) { buf[i] = STATES[counter][i] == HIGH ? 'O' : '.'; }
return buf;}
require 'ffi'
module Arduino extend FFI::Library ffi_lib 'graycode'
attach_function :press, [], :void attach_function :leds, [], :string
attach_function :setup, [], :void attach_function :loop, [], :voidend
When /^I press the button$/ do Arduino.press Arduino.loopend
Then /^the LEDs should read "(.*?)"$/ do |leds|
expect(Arduino.leds).to eq(leds)end
Cucumber Wire Protocol
Cucumber-CPPhttps://github.com/cucumber/cucumber-cpp
host: localhostport: 3902
features/step_definitions/cucumber.wire
When /^I press the button$/ do Arduino.press Arduino.loopend
Then /^the LEDs should read "(.*?)"$/ do |expected| expect(Arduino.leds).to eq(expected)end
WHEN("^I press the button$") { press(); loop();}
THEN("^the LEDs should read \"(.*?)\"$") { REGEX_PARAM(string, expected); BOOST_CHECK_EQUAL(leds(), expected);}
Bespoke wire server
http://www.2600.com/code/212/listener.c
listen/accept/read/write
while(fgets(buf,sizeof buf,rStream)) { respond_to_cucumber(wStream, buf);}
step_matchesinvoke
Two messages
Then the LEDs should read "..O"
⬇["step_matches", {"name_to_match": "the LEDs should read"}]
⬇["success", [{"id":"1", "args":[{"val":"..O", "pos":"22"}]}]]
json spirithttp://www.codeproject.com/KB/recipes/
JSON_Spirit.aspx
extern "C" void respond_to_cucumber( FILE* stream, const char* message) { string s = message; Value v; read(s, v);
Array& a = v.get_array(); string type = a[0].get_str();
// handle Cucumber message types
report_success(stream);}
if (type == "step_matches") { string name = step_name(v);
if (name == "I press the button") { report_success(stream); return; } else if (...)
// ...
}}
if (type == "step_matches") { string name = step_name(v);
if (name == "I press the button") { report_success(stream); return; } else if (...)
// ...
}}
if (type == "step_matches") { string name = step_name(v);
if (...) { // ...
} else if (name.find("the LEDs") == 0) const int START = 22; string leds = name.substr(START, 3); report_match(leds, START, stream); return; }}
Then the LEDs should read "..O"
⬇["invoke", {"id":"1", "args":["..O"]}]
⬇["success", []]
if (type == "invoke") { string id = step_id(v);
if (id == "0") { press(); loop(); } else if (id == "1") {
// ...
} } }
if (type == "invoke") { string id = step_id(v);
if (id == "0") { press(); loop(); } else if (id == "1") {
// ...
} } }
if (type == "invoke") { string id = step_id(v);
if (id == "0") { // ...
} else if (id == "1") { string expected = step_leds(v); if (expected != leds()) { report_failure("LEDs", stream); return; } } }
https://github.com/hparra/ruby-serialport
Serial
void loop() { button.update(); bool buttonPressed = button.risingEdge();
if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); }
delay(50);}
void loop() { button.update(); bool buttonPressed = button.risingEdge();
if ( buttonPressed ) { counter = (counter + 1) % ENTRIES; updateLeds();
delay(50);}
void loop() { button.update(); bool buttonPressed = button.risingEdge();
int command = (Serial.available() > 0 ? Serial.read() : -1);
if (isIncrement(buttonPressed, command)) { counter = (counter + 1) % ENTRIES; updateLeds(); } else if (isQuery(command)) { Serial.write(leds()); Serial.write('\n'); }
delay(50);}
void loop() { button.update(); bool buttonPressed = button.risingEdge();
int command = (Serial.available() > 0 ? Serial.read() : -1);
if (isIncrement(buttonPressed, command)) { counter = (counter + 1) % ENTRIES; updateLeds(); } else if (isQuery(command)) { Serial.write(leds()); Serial.write('\n'); }
delay(50);}
void loop() { button.update(); bool buttonPressed = button.risingEdge();
int command = (Serial.available() > 0 ? Serial.read() : -1);
if (isIncrement(buttonPressed, command)) { counter = (counter + 1) % ENTRIES; updateLeds(); } else if (isQuery(command)) { Serial.write(leds()); Serial.write('\n'); }
delay(50);}
void loop() { button.update(); bool buttonPressed = button.risingEdge();
int command = (Serial.available() > 0 ? Serial.read() : -1);
if (isIncrement(buttonPressed, command)) { counter = (counter + 1) % ENTRIES; updateLeds(); } else if (isQuery(command)) { Serial.write(leds()); Serial.write('\n'); }
delay(50);}
require 'serialport'
module Arduino @@port = SerialPort.open 2, 9600 at_exit { @@port.close }
def self.press @@port.write '+' end
def self.leds @@port.write '?' @@port.read.strip endend
Ruby, C#, SpecFlow, Cucumber
Almost a computer
Feature: Calculator
Scenario: Add two numbers When I multiply 2 and 3 Then I should get 6
Run Cucumber directly
rsync -av --delete . remote1:test_path
ssh remote1 'cd test_path && cucumber'
Drive the GUI
TestStack Whitehttps://github.com/TestStack/White
namespace Calc.Spec{ [Binding] public class CalculatorSteps { private Window window;
[Before] public void Before() { Application application = Application.Launch("calc.exe"); window = application.GetWindow( "Calculator", InitializeOption.NoCache); } // ... }}
namespace Calc.Spec{ [Binding] public class CalculatorSteps { private Window window;
[Before] public void Before() { Application application = Application.Launch("calc.exe"); window = application.GetWindow( "Calculator", InitializeOption.NoCache); } // ... }}
namespace Calc.Spec{ [Binding] public class CalculatorSteps { private Window window;
[Before] public void Before() { Application application = Application.Launch("calc.exe"); window = application.GetWindow( "Calculator", InitializeOption.NoCache); } // ... }}
[When(@"I multiply (.*) and (.*)")]public void WhenIMultiply(string a, string b){ window.Keyboard.Enter(a + "*" + b + "=");}
[Then(@"I should get (.*)")]public void ThenIShouldGet(string expected){ window.Get<Label>(expected);}
Give your app an API
Mongoose web server
int main(){ struct mg_context *ctx = mg_start(); mg_set_option(ctx, "ports", "33333");
mg_set_uri_callback(ctx, "/", &show_index, 0); mg_set_uri_callback(ctx, "/multiply", &multiply, 0); mg_set_uri_callback(ctx, "/result", &get_result, 0);
getchar(); mg_stop(ctx);
return 0;}
int main(){ struct mg_context *ctx = mg_start(); mg_set_option(ctx, "ports", "33333");
mg_set_uri_callback(ctx, "/", &show_index, 0); mg_set_uri_callback(ctx, "/multiply", &multiply, 0); mg_set_uri_callback(ctx, "/result", &get_result, 0);
getchar(); mg_stop(ctx);
return 0;}
static void multiply( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ char *ap = mg_get_var(conn, "multiplier"); char *bp = mg_get_var(conn, "multiplicand");
int a = atol(ap); int b = atol(bp);
result = a * b;
mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n");
mg_free(multiplier_s); mg_free(multiplicand_s);}
static void multiply( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ char *ap = mg_get_var(conn, "multiplier"); char *bp = mg_get_var(conn, "multiplicand");
int a = atol(ap); int b = atol(bp);
result = a * b;
mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n");
mg_free(multiplier_s); mg_free(multiplicand_s);}
static void multiply( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ char *ap = mg_get_var(conn, "multiplier"); char *bp = mg_get_var(conn, "multiplicand");
int a = atol(ap); int b = atol(bp);
result = a * b;
mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n");
mg_free(multiplier_s); mg_free(multiplicand_s);}
static void multiply( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ char *ap = mg_get_var(conn, "multiplier"); char *bp = mg_get_var(conn, "multiplicand");
int a = atol(ap); int b = atol(bp);
result = a * b;
mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n");
mg_free(multiplier_s); mg_free(multiplicand_s);}
static void get_result( struct mg_connection *conn, const struct mg_request_info *request_info, void *user_data){ mg_printf(conn, "HTTP/1.1 200 OK\r\n\Content-Type: text/plain\r\n\r\n%d", result);}
https://github.com/jnunemaker/httparty
HTTParty
When(/^I multiply (\d+) and (\d+)$/) do |a, b| Calculator.multiply a, bend
Then(/^I should get (\d+)$/) do |n| expect(Calculator.result).to eq(n.to_i)end
require 'httparty'
class Calculator include HTTParty base_uri 'localhost:33333'
def self.multiply(a, b) get "/multiply?multiplier=#{a}\&multiplicand=#{b}" end
def self.result get("/result").to_i endend
(because tl;dr is passé)
In summary:
Have source? Device Technique
yes simple Direct
yes simple Serial
yes mediumCucumber Wire
Protocol
yes powerful Custom TCP
no powerful GUI
Thank youhttps://github.com/undees/cukeup