Notes
Slide Show
Outline
1
Real Life Cross-Platform Testing
  • Peter Edwards
2
Contents
  • Background aka "Real Life"
  • Cross-Platform
  • Testing


  • Add Windows Testing Under Unix
  • Test::MockObject
  • Test::MockModule
  • Running Unix unit tests under Windows
  • Future Plans For Testing
  • Summary and Links
3
Background aka "Real Life"
  • Content Management System used at BBC to enter XML documents
    that are later transformed to make public websites
  • Client-side
    • GUI using WxPerl (WxWidgets)
    • WYSIWYG editing
    • Talks SOAP over HTTP to server
    • Runs under ActiveState Perl
  • Server-side
    • Handles SOAP requests
    • Stores document blobs in filesystem
    • Stores indexes, metadata in Oracle database
    • Runs under Solaris Perl
  • Usage
    • 100s of users
    • Time critical publishing : failure during release is not an option

4
Cross-Platform
  • CMS code running on Windows and Solaris


  • Solaris perl 5.8.8
    •  $ perl -V
    •  Summary of my perl5 (revision 5 version 8 subversion 8) configuration:
    •    Platform:
    •      osname=solaris, osvers=2.10, archname=sun4-solaris

  • Windows ASPerl 5.8
    • C:\WINNT>perl –V
    • Summary of my perl5 (revision 5 version 8 subversion 6) configuration:
    •   Platform:
    •     osname=MSWin32, osvers=4.0, archname=MSWin32-x86-multi-thread
5
Testing
  • Unit tests for dev
  • Automated overnight smoke testing of unit tests
  • Dev / Staging Test / Live environments
  • Manual release test on staging test area using Windows app
  • Problems
  • Lots of tests for server side code, very few for client side because difficult to run 'use Wx' code on Unix in batch
  • Existing tests run on Unix, fail on Windows
6
Add Windows Testing Under Unix
  • Need to write lots of client-side tests for
  • GUI
    •     WxPerl -> Gtk+ under Solaris
      • ‘Use Wx’ was failing because no X display
      • Problems with font sizing and window alignment
      • Windows-specific components, e.g. ActiveX Altova editor
  • Installation
      • Shortcuts, registry Win32::OLE, unzipping archives to Windows Apps dir etc.
  • Solutions
  • Use Xvfb
      • $ alias runxvfb='Xvfb :10 -dev vfb screen 0 1152x900x8 > /dev/null 2>&1 &'
      • Lets you check code compile and call many routines
      • But how do you test UI rendered properly - interpreting the virtual screen bitmaps is too hard!
  • Sandboxing and mocking
      • Mock required Win32 functions
      • Make them do file I/O to a sandbox area
      • Test::MockObject - Perl extension for emulating troublesome interfaces
      • Test::MockModule - Override subroutines in a module for unit testing

7
Test::MockObject 1
  • Helpers


  •     sub make_mock_obj_in_class {
  •         my $class = shift;
  •         my $obj = Test::MockObject->new;
  •         $obj->fake_module($class);
  •         $obj->fake_new($class);
  •         return $obj;
  •     }


  •     sub dump_mock_calls {
  •         my $mockobj = shift;
  •         my $i = 1;
  •         while ( my $name = $mockobj->call_pos($i) ) {
  •             diag " call $i: $name";
  •             my @args = $mockobj->call_args($i);
  •             for (0 .. $#args) {
  •                 diag '  arg '.($_ +1).': ';
  •                 diag Dumper($args[$_]);
  •             }
  •             $i++;
  •         }
  •     }
8
Test::MockObject 2
  • Mocking


  •     my $wx = make_mock_obj_in_class( 'Wx' );
  •     my $mock_WxPerlSplashProgress = make_mock_obj_in_class( 'Wx::Perl::SplashProgress' );
  •     $mock_WxPerlSplashProgress->set_true(qw( SetLabelColour SetIcon Show SetValue Update Destroy ));
  •     $mock_WxPerlSplashProgress->mock( SetLabel => sub { diag ' SetLabel: '.$_[1] } );


  •     $mock_Win32OLE = make_mock_obj_in_class( 'Win32::OLE' );
  •     $mock_Win32OLE->mock( 'SpecialFolders', sub { shift } );
  •     $mock_Win32OLE->mock( 'AppData', sub { return catdir(qw(data win32), 'Application Data') } );
  •     $mock_Win32OLE->mock( 'StartMenu', sub { catdir(qw(data win32 startmenu)) } );
  •     $mock_Win32OLE->mock( 'Desktop', sub { catdir(qw(data win32 desktop)) } );
  •     $mock_Win32Shortcut = make_mock_obj_in_class( 'Win32::Shortcut' );
  •     $mock_Win32Shortcut->mock( 'Load', sub {
  •         my ($self, $filename) = @_;
  •         $self->{content} = read_file($filename);
  •         return 1;
  •         } );
  •     $mock_Win32Shortcut->mock( 'Path', sub {
  •         my ($self, $path) = @_;
  •         $self->{content} = $path;
  •         } );
  •     $mock_Win32Shortcut->mock( 'Arguments', sub {
  •         my ($self, $args) = @_;
  •         $self->{content} .= ' '.$args . "\r\n";
  •         } );
  •     $mock_Win32Shortcut->mock( 'Save', sub {
  •         my ($self, $filename) = @_;
  •         write_file($filename, $self->{content} . "writetime ". gmtime() . "\r\n");
  •         return 1;
  •         } );
  •     $mock_Win32Shortcut->set_true(qw( ShowCmd Description IconLocation Close ));
  •     { no strict 'refs'; *{'Win32::Shortcut::SW_SHOWMINNOACTIVE'} = sub {}; }


9
Test::MockObject 3
  • Testing
  • $mock_WxPerlSplashProgress->clear();
  • is( $i->_install_loginscript, 1, '$i->_install_loginscript' );
  • dump_mock_calls($mock_IFLDesktopLoginScript);
  • $mock_IFLDesktopLoginScript->called_pos_ok( 3, 'install', 'called IFL::Desktop::LoginScript->install' );
  • dump_mock_calls($mock_WxPerlSplashProgress);
  • $mock_WxPerlSplashProgress->called_pos_ok( 4, 'SetLabel', 'called Wx::Perl::SplashProgress->SetLabel' );
  • $mock_WxPerlSplashProgress->called_args_pos_is( 4, 2, 'Checking login script' );
  • $mock_WxPerlSplashProgress->called_pos_ok( 7, 'SetLabel', 'called Wx::Perl::SplashProgress->SetLabel' );
  • $mock_WxPerlSplashProgress->called_args_pos_is( 7, 2, 'Installing login script...' );
10
Test::MockModule 1
  • Helper


  • sub mock_module {
  •         my ($module,$options,@functions) = @_;
  •         my $no_auto = defined($options->{no_auto}) ? $options->{no_auto} : 1;
  •         my $create_new = defined($options->{create_new}) ? $options->{create_new} : 1;
  •         my $testmockmodule = new Test::MockModule($module, no_auto => $no_auto);
  •         my $object;
  •         if ($create_new) {
  •             $object = bless {}, $module;
  •             $testmockmodule->mock('new',sub { $logger->log($module,'new',@_); return $object });
  •         }
  •         for my $function (@functions) {
  •             $testmockmodule->mock($function,sub { $logger->log($module,$function,@_) });
  •         }
  •         no strict 'refs';
  •         push @{$module . "::ISA"},'Exporter';
  •         my $module_path = $module;
  •         $module_path =~ s{::}{/}xmsg;
  •         $module_path .= '.pm';
  •         $INC{$module_path} = "1 (Inserted by mock_module())";
  •         return $testmockmodule, $object;
  • }
11
Test::MockModule 2
  • Mocking
    • my ($mock_wx_activex_ie, $mock_wx_activex_ie_object)
    •         = mock_module('Wx::ActiveX::IE',{});
    • my ($mock_wx_activex_event, $mock_wx_activex_event_object)
    •         = mock_module('Wx::ActiveX::Event',{},@Wx::Event::EXPORT_OK);
    • my ($mock_wx_panel,$mock_wx_panel_object)
    •         = mock_module('Wx::Panel',{}, qw( SetSizer ));
    • my ($mock_wx_boxsizer,$mock_wx_boxsizer_object)
    •         = mock_module('Wx::BoxSizer',{}, qw( Add ));

  • Tests - use your objects as normal… then check call sequence
    • my @mf_calls = $logger->filter({'FLIPClient::UI::MicroForms' => []});
    • my $call = shift(@mf_calls);
    • is($call->{function},'set_template','position_change (' . $test->{name} . ') calls set_template');
    • ok($call->{args}->[1] =~ $test->{template},'position_change (' . $test->{name} . ') sets template');


    • $call = shift(@mf_calls);
    • is($call->{function},'set_data','position_change (' . $test->{name} . ') calls set_data');
    • is_deeply($call->{args}->[1],$test->{data},'position_change (' . $test->{name} . ') sets data');


12
Running Unix unit tests under Windows 1
  • Some libraries shared between Unix and Windows;
    not being tested properly client-side
  • Perl Portability
    • "perldoc perlport“ http://perldoc.perl.org/5.8.8/perlport.html
      "When the code will run on only two or three operating systems, you may need to consider only the differences of those particular systems. The important thing is to decide where the code will run and to be deliberate in your decision.“
    • Only worrying about Windows and Unix; OpenVMS support is hard
      • binmode and chomp - binmode saves headaches on Windows like EOF ^Z; watch out for CR-LF
      • use File::Spec::Functions rather than Unix paths
        • YES :  my $path = rel2abs( catdir(qw( data local cache file.txt ));
        • NO  :  my $path = './data/local/cache/file.txt';
13
Running Unix unit tests under Windows 2
  • Generic configuration interface with platform-specific subclasses
      • System.pm
      •         |-- System/Win32.pm
      •         |-- System/Unix.pm
      • using File::Spec::Functions for paths
  • Change tests from path strings to regexes using a quote path separator
    •           my $script = $i->startup('remote');
    • NO  : is( $script, 'scripts/FLIP_real.PL', '$i->startup("remote")  script’
    • YES : $ps = ($^O eq 'MSWin32') ? "\\" : '/';
    •           $qps = quotemeta $ps;
    •           like( $script, qr{ scripts [$qps] FLIP_real.pl \z }xms, '$i->startup("remote")  script' );
    • Note PBP style regex
  • Actually run the tests on multiple platforms
14
Future Plans For Testing
  • Automate application release test under Windows
    • Win32::GuiTest (or pay for WinRunner)
15
Summary and Links
  • Summary
    • "perldoc perlport“
    • Write cross-platform tests from the outset; convert old ones
    • Mock platform-specific GUI or system library calls
    • Automate tests (life is short) and get as much coverage as possible
  • Links
    • WxPerl http://wxperl.sourceforge.net/
    • WxWidgets http://docs.wxwidgets.org/trunk/
    • "Perl Testing: A Developer's Notebook" Ian Langworth & chromatic, O'Reilly Media, Inc., 2005 http://preview.tinyurl.com/5k6wnc

  • Thank you. Any Questions?