Cukeup nyc ian dees on testing embedded systems with cucumber

1,504 views

Published on

Testing Embedded Systems With Cucumber
Cucumber isn't just for web apps. You can test just about anything with it—including embedded systems! In this talk, we'll look at several different facets of driving hardware from Cucumber, including:
Connecting to an Arduino using a custom serial protocol
Taking advantage of the TCP stack when it's available
Building the Cucumber wire protocol into your device
What to do when you can't modify the app under test
Driving more fully-featured devices such as the BeagleBone or RaspberryPi
By the end of the presentation, you'll have a handle on what the various options are for testing embedded devices, and which tradeoffs will apply to your system.

Published in: Technology, Business
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
1,504
On SlideShare
0
From Embeds
0
Number of Embeds
55
Actions
Shares
0
Downloads
17
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide

Cukeup nyc ian dees on testing embedded systems with cucumber

  1. 1. Testing Embedded Systems With Cucumber Ian Dees • @undees CukeUp! NYC 2013
  2. 2. Plenty of Ruby, but...
  3. 3. There will be C
  4. 4. There will be C++
  5. 5. There will be C#
  6. 6. http://pragprog.com/ titles/dhwcr discount code: CucumberIanDees
  7. 7. The Embedded Continuum
  8. 8. almost a computer chip and some ROM
  9. 9. Simple devices No Ruby, no Cucumber
  10. 10. Gray Code A simple system to test
  11. 11. 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.. | | ... |
  12. 12. Arduino
  13. 13. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); } delay(50); }
  14. 14. Drive code directly
  15. 15. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); } delay(50); }
  16. 16. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); } delay(50); }
  17. 17. static bool isFakeButtonPressed; class Bounce { public: // ... bool risingEdge() { bool result = isFakeButtonPressed; isFakeButtonPressed = false; return result; } };
  18. 18. extern "C" void press() { isFakeButtonPressed = true; }
  19. 19. 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; }
  20. 20. https://github.com/ffi/ffi FFI
  21. 21. 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, [], :void end
  22. 22. When /^I press the button$/ do Arduino.press Arduino.loop end Then /^the LEDs should read "(.*?)"$/ do |leds| expect(Arduino.leds).to eq(leds) end
  23. 23. Cucumber Wire Protocol
  24. 24. Cucumber-CPP https://github.com/cucumber/cucumber-cpp
  25. 25. host: localhost port: 3902 features/step_definitions/cucumber.wire
  26. 26. When /^I press the button$/ do Arduino.press Arduino.loop end Then /^the LEDs should read "(.*?)"$/ do |expected| expect(Arduino.leds).to eq(expected) end
  27. 27. WHEN("^I press the button$") { press(); loop(); } THEN("^the LEDs should read "(.*?)"$") { REGEX_PARAM(string, expected); BOOST_CHECK_EQUAL(leds(), expected); }
  28. 28. Bespoke wire server
  29. 29. http://www.2600.com/code/212/listener.c listen/accept/read/write
  30. 30. while(fgets(buf,sizeof buf,rStream)) { respond_to_cucumber(wStream, buf); }
  31. 31. step_matches invoke Two messages
  32. 32. Then the LEDs should read "..O" ➡ ["step_matches", {"name_to_match": "the LEDs should read"}] ➡ ["success", [{"id":"1", "args":[{"val":"..O", "pos":"22"}]}]]
  33. 33. json spirit http://www.codeproject.com/KB/recipes/ JSON_Spirit.aspx
  34. 34. 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); }
  35. 35. if (type == "step_matches") { string name = step_name(v); if (name == "I press the button") { report_success(stream); return; } else if (...) // ... } }
  36. 36. if (type == "step_matches") { string name = step_name(v); if (name == "I press the button") { report_success(stream); return; } else if (...) // ... } }
  37. 37. 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; } }
  38. 38. Then the LEDs should read "..O" ➡ ["invoke", {"id":"1", "args":["..O"]}] ➡ ["success", []]
  39. 39. if (type == "invoke") { string id = step_id(v); if (id == "0") { press(); loop(); } else if (id == "1") { // ... } } }
  40. 40. if (type == "invoke") { string id = step_id(v); if (id == "0") { press(); loop(); } else if (id == "1") { // ... } } }
  41. 41. 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; } } }
  42. 42. https://github.com/hparra/ruby-serialport Serial
  43. 43. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if (buttonPressed) { counter = (counter + 1) % ENTRIES; updateLeds(); } delay(50); }
  44. 44. void loop() { button.update(); bool buttonPressed = button.risingEdge(); if ( buttonPressed ) { counter = (counter + 1) % ENTRIES; updateLeds(); delay(50); }
  45. 45. 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); }
  46. 46. 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); }
  47. 47. 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); }
  48. 48. 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); }
  49. 49. 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 end end
  50. 50. Ruby, C#, SpecFlow, Cucumber Almost a computer
  51. 51. Feature: Calculator Scenario: Add two numbers When I multiply 2 and 3 Then I should get 6
  52. 52. Run Cucumber directly
  53. 53. rsync -av --delete . remote1:test_path ssh remote1 'cd test_path && cucumber'
  54. 54. Drive the GUI
  55. 55. TestStack White https://github.com/TestStack/White
  56. 56. 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); } // ... } }
  57. 57. 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); } // ... } }
  58. 58. 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); } // ... } }
  59. 59. [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); }
  60. 60. Give your app an API
  61. 61. Mongoose web server
  62. 62. 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; }
  63. 63. 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; }
  64. 64. 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 OKrn Content-Type: text/plainrnrn"); mg_free(multiplier_s); mg_free(multiplicand_s); }
  65. 65. 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 OKrn Content-Type: text/plainrnrn"); mg_free(multiplier_s); mg_free(multiplicand_s); }
  66. 66. 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 OKrn Content-Type: text/plainrnrn"); mg_free(multiplier_s); mg_free(multiplicand_s); }
  67. 67. 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 OKrn Content-Type: text/plainrnrn"); mg_free(multiplier_s); mg_free(multiplicand_s); }
  68. 68. 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 OKrn Content-Type: text/plainrnrn%d", result); }
  69. 69. https://github.com/jnunemaker/httparty HTTParty
  70. 70. When(/^I multiply (d+) and (d+)$/) do |a, b| Calculator.multiply a, b end Then(/^I should get (d+)$/) do |n| expect(Calculator.result).to eq(n.to_i) end
  71. 71. 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 end end
  72. 72. (because tl;dr is passé) In summary:
  73. 73. Have source? Device Technique yes simple Direct yes simple Serial yes medium Cucumber Wire Protocol yes powerful Custom TCP no powerful GUI
  74. 74. Thank you https://github.com/undees/cukeup

×