Test-drive the development of your command-line applications


Summarized using AI

Test-drive the development of your command-line applications

David Copeland • September 29, 2011 • New Orleans, Louisiana • Talk

In the talk titled "Test-drive the development of your command-line applications," David Copeland discusses the integration of test-driven development (TDD) into the creation of command-line applications, emphasizing the importance of writing robust and maintainable code.

Copeland begins by sharing his background as a principal engineer at Opower, where he was compelled to shift from quickly hacked bash scripts to well-structured command-line applications. He notes that without tests, maintaining these scripts becomes an arduous task. This leads him to focus on how to effectively test command-line applications, particularly in Ruby.

The key points covered in the talk include:

  • Understanding TDD: He explains the TDD process using a simple cycle of writing a failing test, implementing code to pass the test, and then refactoring the code while ensuring all tests pass.
  • Challenges of Testing CLI Apps: Copeland highlights the specific challenges posed by command-line applications, such as their interaction with the filesystem, which could lead to unwanted changes on the local machine during testing.
  • Utilizing Ruby Tools: He introduces tools like Cucumber and Aruba, which help manage testing for command-line applications. Cucumber allows for writing tests in a natural language format, making it easier to create scenarios around the expected behavior of the application.
  • Building an Example Application: Through a practical demonstration, Copeland shows how to write a simple application for managing dotfiles using TDD principles. He discusses setting up the directory structure and using tools to bootstrap the testing environment.
  • Implementing Tests: The talk covers defining features in a feature file, writing step definitions, and how to leverage mock environments to avoid affecting the actual filesystem during testing.
  • Refactoring for Maintainability: Copeland emphasizes the need for code readability and organization, explaining how to refactor code to improve it without breaking functionality.
  • Error Handling and UI Improvement: He discusses creating a user-friendly interface and handling errors gracefully in command-line applications, demonstrating the importance of feedback in user experience.

In conclusion, the main takeaways from Copeland's talk encourage developers to approach command-line application development with the same rigor as web development. He advises creating templates for command-line applications, starting from the user interface tests, and ensuring a thorough understanding of error management for a robust application. Incorporating Ruby tools, like methadone and Aruba, can significantly streamline the testing process, making it easier to adopt TDD practices across different types of programming ventures.

Overall, Copeland’s talk serves as a guide for Ruby developers looking to integrate TDD into their workflow for command-line applications, ensuring cleaner code and better long-term maintainability.

Test-drive the development of your command-line applications
David Copeland • New Orleans, Louisiana • Talk

Date: September 29, 2011
Published: December 12, 2011
Announced: unknown

Rubyists love testing, and test-driven-development is becoming THE way to write code. But, do we do this with our command-line tools? How DO you write a test that your awesome application cleans up its temp files? How does one make a failing test for a missing command-line option? What's the easiest way to check our app's exit codes? This talk will answer these questions with some real-world examples. We'll talk briefly about the challenges particular to testing command-line apps, and then dive into some code where we'll show off techniques for organizing code for testability, tools for interacting with the filesystem, and how to create full-blown acceptance tests for your command-line app. In no time, you'll be able to write your command-line apps the same way you write your other code: test-first.

RubyConf 2011

00:00:17.000 I'm going to talk about um writing command line applications using test driven development so I love writing command line applications I love using
00:00:23.359 the command line I love everything about it right it's so simple and I don't have to take my hands off the keyboard and
00:00:28.560 it's just really clean and uh uh you know and I like test driven development too so uh we're going to talk about how
00:00:34.680 those things work together um a little bit of background about me just so you kind of know where I'm coming from to set this up a little um so I'm Dave
00:00:40.800 copen or Dave Tron 5000 and I am a principal engineer at a company called
00:00:46.399 op power and why the so op power is a software of services company in Washington DC and uh why that's
00:00:53.039 important is when I started I was employee number 23 probably the sixth developer that they hired so any of you
00:00:58.879 that have worked like at a startup or on a small company or something like that you know that when you have to build
00:01:05.040 your awesome product there's other things that have to happen too and you don't have a team of Specialists you
00:01:10.360 don't have a DBA and assisted man and all this stuff to help you you have to stop what you're doing and go fix it and
00:01:16.159 make things work you know automate things that are tedious or glue things together that need to be glued together and if you do it the way I used to do it
00:01:22.960 what you would do is you would hack together the crappiest bash script of all time as quickly as possible and run
00:01:28.600 it once and if it works you're good to go and you go back to what you're doing so uh that sucks right because 6 months
00:01:34.600 later it's still running and you still got to fix it and it's this horrible mess and you have no test so you have no
00:01:39.799 easy way to fix it or make it better or even figure out what it's supposed to do so when I started at op power uh you
00:01:46.240 know like I said small company at the time and I had to do these things and I decided I'm not going to do that I'm
00:01:51.640 going to write really good command line applications I'm going to take some time and take some care and and and leave
00:01:57.119 myself little bits of documentation help and all kind of things to make a nice application I loved it so much that I uh
00:02:04.159 started writing a book about it um and and I thought I had you know really thinking about all this command line
00:02:09.399 stuff I'm going to collect it all into one you know one one book and and and and get it together and I'm writing and
00:02:14.599 writing writing so here's the the proposed cover of the book um everyone in here is is is running a command line
00:02:21.239 app that's why they're so cool um I don't know if the publisher is going to go for it but
00:02:26.360 maybe so anyway as I'm writing this book I get I I realize I've got all these
00:02:32.440 great ideas but I haven't talked about testing how how the heck do I test my command line app right because
00:02:40.080 um yeah okay so so we're going to talk about why that's tricky but uh before we get into that let's do a quick review of
00:02:47.120 tddd anybody here not know what TD is I'll do it very fast just so you know
00:02:52.560 what I'm talking about so I write a test that uh that that asserts my my my app does something that it doesn't do and
00:02:59.519 then I run the test and watch it fail so I know that my test is doing something uh then I fix it as quickly simply as
00:03:05.400 possible I don't think about The Grand Design of the system I just just fix that one test get that done and then I
00:03:11.360 refactor then I clean up my little mess so I separate the the step of making work versus making nice then I repeat
00:03:18.879 this over and over and over again so um why is this hard for command line apps so we're getting back to as I started
00:03:24.920 thinking how do you you know test a command line app because what do they tend to do right they tend to copy file
00:03:30.000 to other servers or fuss with the system or delete files or mess around in my home directory so I can't be running a
00:03:35.319 test of a command line app that's going to change my system or screw up my home directory every time I run the test that's kind of a problem and uh so
00:03:42.720 that's why I sort of resisted for a while but H it's actually not that hard especially if you're using Ruby um so
00:03:48.360 what this talk is going to be about is how to deal with that and what it all comes down to is that we're writing our command line apps on production right
00:03:55.159 we're not writing them on the production server but we're writing them on the machine on which intended to be run
00:04:01.280 which means that a test that runs the command line app has a potential to screw up the very machine where it's you
00:04:07.319 know designed to run and uh avoiding this uh so so right what would you do in
00:04:13.280 a in in a regular uh web application right you would You' have a development server you'd have a QA server you'd have
00:04:18.600 a staging server and you would do all that and that's how you would keep things from from messing up um and I guess if you're whole business is
00:04:24.360 writing command line apps maybe you should do that but that's kind of crazy for writing like a backup script so um
00:04:31.960 using Ruby it's actually pretty easy to make sure that you uh don't screw things up and still can get all the benefits of
00:04:38.880 test driven development so that's what we're going to talk about um and we're going to go through uh writing a simple
00:04:44.639 application using tdd and see how this how this happens and as it turns out there's a few tools and a few ways of
00:04:51.080 thinking and a few different techniques that are probably going to seem obvious but maybe you hadn't thought of them or or maybe I'm just dumb and I hadn't
00:04:57.360 thought of them before but they should uh they should make get pretty clear how you can handle some of this stuff so our
00:05:02.639 app uh that we're going to write is going to manage our DOT files like our bash RC and and all that stuff so I
00:05:07.759 don't know about you guys but I keep mine um in a git repo you up on GitHub and so when I've got to set up a new
00:05:13.240 machine or a new account or something um I'll clone that down and then I'll Sim link all those files into my home
00:05:19.039 directory and that works okay it's kind of a pain it's a bit tedious and it's the perfect thing for a command line app
00:05:24.639 to to do for me so uh we'll we'll we'll make one right so let's let's get to it right um
00:05:32.400 so how do we start well we'll make a directory I'm going to call it full stop right that's that's British as I understand it for period um so we'll
00:05:39.280 just make a directory called Full Stop and and go right um well that sucks because now I got to piece together a bunch of rake files and Gem files and
00:05:45.240 crap like that that um maybe I don't know what to do and it's going to be like you know too long so I'll just
00:05:50.560 bundle gem full stop that's way better right because then I get a rake file and Gem spec and all that unfortunately I
00:05:55.600 can't I still can't really run tests right because what I really want is you know kind of like the Step Zero before
00:06:01.840 we do our test fix refactor thing I want to bootstrap everything when you write a rails app you type rails fo CD Fu rake
00:06:09.520 test and you get a test one test gets run that's pretty awesome right so that's what we want right because uh it
00:06:16.520 reduces the friction between us and starting to write tests and and being able to write tests as soon as we have an idea of what we want to do um without
00:06:24.440 something like this uh we rely only on our professionalism and it's kind of
00:06:29.639 hard to rely just on that if uh you're under the gun or you're under a lot of pressure so having something that enables and encourages you to start
00:06:36.280 writing tests as soon as you have an idea that's a good thing so you can um
00:06:41.440 you know make a get repo with a template project or some tarball that has what whatever you need um so I wrote a uh I wrote an app
00:06:48.520 called methadone that does all this for me according to the way I would like to uh you know write a right and test a
00:06:54.360 command line app um so when I run it it just it just basically does a bundle Gem
00:07:00.160 and then a bunch of other stuff setting up things that I need and we'll see kind of what those things are when we run a
00:07:05.280 bundle install and we have this big giant list of uh gems that get installed so what these are are not important what
00:07:11.199 what's going on here is that we're setting up cucumber um and we're setting up a thing called Aruba so Ruba is a
00:07:17.080 bunch of cucumber steps if you don't know what cucumber is we'll see it cucumber is a way to write tests in English and each each each line of your
00:07:24.759 English is a step that does your test we'll see that later anyway Aruba is a bunch of steps for test testing command
00:07:30.039 line applications so it will run your app capture the output capture the standard error let you do some asserts
00:07:36.720 um you know on like the exit status or check that the error contains something so all these handy things that we might want our app to do so so methodone is
00:07:44.120 going to set all that up for us so let's check that out here okay right so it's a basic you know
00:07:52.879 kind of standard looking stuff you might want in your project um we've got
00:07:58.440 a little app that uh does really nothing you can see there's haran code just
00:08:03.639 enough to get us going um we can run a test right so if we feel like writing
00:08:09.319 unit test we're good to go there and we can run a cucumber feature and right so that's good to go
00:08:17.199 wonderful so let's add a feature and so this is cucumber right uh feature everything from feature to scenario is
00:08:23.479 just English you can write whatever you want it's designed to get you thinking about the problem you're solving and to
00:08:28.520 write out what what's what's the reason for what you're doing but everything under scenario all this that's code that's going to execute and actually run
00:08:34.719 our test so the given and and we're writing it just in in magical English
00:08:40.039 that is what we want our app to do so given I've got my DOT files and some git repo when I run the app giving it that
00:08:47.920 that repo uh then the dot files should get checked out in in a in a directory called dot files in my home directory
00:08:53.880 and then they're going to get SIM links so that's exactly what I described before of what I wanted the app to do and this uh we we can execute all this
00:09:01.480 stuff so let's uh let's go ahead and see what happens since we've bootstrapped everything we just need to make that file see it
00:09:08.760 here right so it's the same thing without syntax highlighting so we'll run that okay well
00:09:15.560 we're looking for a failing test right we're not quite getting there so we have all this gold and all that means is the Cucumber doesn't know how to execute
00:09:21.519 those steps the one in blue cucumber does know how to execute that step and that's because this step is provided
00:09:28.360 what the h something going on there okay this step is provided by Aruba so cucumber knows
00:09:35.320 how to execute that but it can't yet because it doesn't know how to do the other ones so we're 25% of the way there
00:09:41.279 we just need to implement the rest of the steps and so here's the second part of how cucumber works so you see this all that right
00:09:48.480 there right that's the English it's matching it's just a basic reg x match and it's extracting things out of there so all we're doing in this given the
00:09:54.680 given is when we set up the conditions under which our test is going to get run so we uh kill whatever directory we've
00:10:00.680 been given uh recreate it fresh and then for all the files we're just going to make some empty files in there and then
00:10:06.399 down here we just make a little git repo right really simple but now we have a fully functional git repository and a
00:10:11.880 nice clean directory that we can rely on when we when we run our app so now to do
00:10:17.079 these assertions right to check that the dot files are there um we basically just
00:10:22.720 go into uh the directory where we expected to have cloned everything and then we call this Aruba step then a file
00:10:29.720 name whatever should exist so Ruba gives us that and a step call another step so uh it's pretty easy we don't have to
00:10:35.480 really do anything uh the second thing we need to check is that the Sim links worked so again um we're going to do the
00:10:41.760 same sort of thing right we're going to go into our home directory and for all the files that we know we're in our git repository we're going to assert that
00:10:48.639 they're a Sim link and uh so this is this is some rspec stuff here and rspc does not provide a b assim
00:10:55.680 link matcher um I provided that and we'll talk about why um but this sort of demonstrates like
00:11:01.839 why it's important to watch your tests fail so when I was developing this right I I use this code instead because this
00:11:08.120 works fine for arpec right I make a file from the file name I call elat and the
00:11:13.600 object that's returned by elat has a method called Sim link so when I say should be Sim link arpc is smart enough
00:11:19.680 to call the Sim link method and check that it returns true great so you know problem solved
00:11:26.519 unfortunately this is the message that you get right expected siming to return true got false symboling of what I have
00:11:33.000 no idea and I'm running in a loop so if this were ever to actually break I would have to go into the code and add a bunch
00:11:38.320 of puts to figure out what actually went wrong so we can write a custom matcher which is very simple uh like
00:11:46.440 this so uh we put that same test inside the match block there which RPC will call to figure out if the test failed or
00:11:52.720 not and then we get a chance to write a nice lovely message that explains what the actual problem is cool
00:11:59.920 so now okay we've done all that we're hoping to find a failing test somewhere
00:12:07.279 all right so let's see if we can do that yes okay so we see there's some
00:12:14.399 green right because we did set up our our test get repo that worked out uh when we ran the app it exited non zero
00:12:21.519 so that's green and then um we've got a big red splotch that tells us exactly
00:12:26.920 what we need to do um and so this is the actual problem that's causing the test to fail the file's not there and that's
00:12:33.760 not surprising right we haven't written any code yet so why would it be there um but we're we're pretty confident that
00:12:39.639 we're testing for that so now we're going to fix that but it's important to know we're only fixing this problem
00:12:45.600 right the way cucumber wants you to work is one step at a time so we're not going to use this test failure as some excuse
00:12:51.320 to write the entire application we're just going to fix the problem at hand take it one step at a time and see how that goes so here's the the code that
00:12:59.600 methadone gave us right it's just has option parsers set up and that's about it um so what we want to do is uh change
00:13:07.399 to our home directory and run to get clone okay so that's all we do and uh
00:13:13.720 you know rv0 that's the repository we're getting on the command line so that should hopefully uh hopefully work
00:13:21.160 there's a slight problem though right now we're getting into the weirdness of testing command line apps and home
00:13:26.920 that's my home directory so what is going to happen is this is going to get run and what God knows what that's going to do to my home directory I've got
00:13:33.040 these temp files in my home directory now what if I had my real dot files there now they're gone who knows that's
00:13:38.959 not good but I still want it to work in reality to check into my home directory
00:13:44.480 so what do we do about that well um you've noticed that I haven't used Tilda
00:13:49.760 or an explicit path anywhere in the test or in the code I've always used this home environment variable and that's I
00:13:55.160 can expect that to be set to my home directory the system guarantees it think it works on windows so um that means
00:14:03.440 that I have a way to change it so I can change that environment because when Aruba runs our app it our app will
00:14:09.959 inherit that environment so I can mess around with it and do whatever I want but still be confident that we're using
00:14:16.160 the standard home environment variable to get the home directory right because we're developing on production so uh cucumber lets us uh
00:14:23.920 write code that runs before and after every single uh scenario that we execute so what we're going to do is is create
00:14:30.440 an empty directory in temp um and set that to be our home directory by setting the home environment variable no problem
00:14:36.680 and afterwards we you know set it back just to just to be nice um so now all right crisis averted
00:14:44.720 we hope let's see how that goes right so there's no there's no dot files
00:14:50.399 directory in my home directory right now
00:14:56.160 so okay we still got some red but we got a step further right this guy right there was failing before now he's
00:15:01.519 passing our checkout is working um and still nothing was messed up in my
00:15:07.279 home directory wonderful all right so the next problem The Next Step that we need to do is fix the problem of the Sim
00:15:13.959 links not being there so here's where we left off and basically all we want to do is is all
00:15:19.399 the files in this dot files D we want to assimilate those to the current directory which is our home directory yeah and so we use uh the square bracket
00:15:27.519 thing Ur and this awful looking expression but it gives us all the files and then uh we check make sure we're not
00:15:33.040 sim linking Dot and dot dot so who knows what that's going to do and then Sim link everything
00:15:38.319 else now hopefully that will give us a nice bunch of
00:15:46.319 green all right look at that it does it all kinds of wonderful green um so right it works right so it
00:15:54.800 does exactly what we said we executed it you know we wrote down what we what we wanted it to do we executed that um but
00:16:00.800 right we're still only on step two so you should never tell anyone at this point that you're done because this the most important part is to do the
00:16:06.959 refactoring now we've only written like a few lines of code so like why refactor well why not refactor we have a test and
00:16:12.279 we can do it um so here are some things I don't like about the code that I just wrote right there's no variables so I don't know um what ARG v0 is supposed to
00:16:20.480 represent you know there's all these magic strings there's that awful reg XY thing I it's so ugly I can't stand it
00:16:25.759 even though it's useful I I want that out of there and I want to know what it means but I don't want to have to look at it I'm repeating file base name all
00:16:32.680 over the place you know that's no fun um and it's organized backwards right when I open up this file I just see a bunch
00:16:37.800 of option pars and crap I don't see the the main logic of my code which is what I want to see so let's fix that by
00:16:43.240 refactoring so we'll put a main method is the very first thing um we'll extract our magic. file string out of there um
00:16:50.160 and now since main is taking some parameters namely the repo and where we're linking to we get variable names
00:16:56.519 that explain what things are and then we replace that durt expression with a nice method called dot files in and now our
00:17:03.199 main method reads exactly like what it does dot files in then can swallow up
00:17:08.360 that awful thing which is a little bit cleaner actually now that we have uh some variables there and then we apply
00:17:14.160 some Ruby functional list comprehension magic to uh clean that up and uh we should be good to go followed by option
00:17:20.880 parsing and then we call Main so that's all good and it certainly satisfies my
00:17:26.319 need to have the code look nice but did I break anything and that is why we have a
00:17:34.679 test and no I didn't break anything meaning everything the app did before I
00:17:40.360 started messing around it still does and I can prove it beyond a shadow of a doubt that's what the test gives us and
00:17:45.760 this is going to be really handy in six months when I look at this thing after all kinds of development and it's so ugly and I have to fix it before I can
00:17:51.360 add new features got all these tests to let me do that okay so we could keep doing this
00:17:56.919 and keep adding more features um but there's a few other problems that we might want to think about that I think are going to be a little more
00:18:02.080 interesting so the UI is currently pretty terrible we're lucky to get Das D
00:18:07.240 help right because we have option parser but um you know we we document here that we take options of course we don't take
00:18:13.280 any options we document that we don't accept any command line arguments of course we require a command line argument um and there's no indication of
00:18:20.159 what this thing does you know I'm not going to remember my clever Name six months from now and this thing's pretty destructive so I would like to know what
00:18:26.000 it's supposed to do uh when I get help from it um secondly uh there's no error handling
00:18:32.240 I don't use any if statements in that whole thing and all of these system commands I mean especially with a command line app all this interaction
00:18:37.320 with the system is going to cause some problem somewhere at some time and we need to know what our app is going to do
00:18:42.400 and how it's going to handle it okay so let's deal with the sucky UI first because that's still pretty easy so here
00:18:47.720 is a feature that describes a non sucky UI you know I I get help for the app it should exit nonzero because you exit non
00:18:54.880 zero when you do what you're asked there should be a banner that bannner is that usage thing so we should have a banner should not document that it takes any
00:19:01.559 options it should document that we take an option called repo which is required and there should be a on line summary of
00:19:06.840 what the app does wonderful so fortunately for us um all these steps are provided by Aruba and methadone
00:19:13.559 already so we don't have to write them but if you don't feel like using methodone you could write them very easily like this so getting help for the
00:19:19.440 app we uh save off the app name uh use Arubas when I run and then to check the
00:19:25.559 output we then use another thing Aruba gives us the output should should match you can give it a regx and it will see
00:19:31.360 that that Reg X applies so that's why we're saving the app name right because we want the app name to show up in the
00:19:36.480 banner all right so let's see how sucky our UI is according to our new feature
00:19:42.280 file feates okay so yeah we we exited zero
00:19:49.600 that's good and and the banner was there because option pars gives it to us but then it falls down on this options thing
00:19:55.280 okay so let's fix that so rather than step through all of those painful detail we'll kind of fast forward a little bit
00:20:01.120 and we'll fix it all in you know two lines of code um so that's essentially what we want if you haven't used option parser before Banner is exactly that it
00:20:08.880 sets sets the banner
00:20:14.080 okay and now we're all green so we have a nice simple but effective and useful
00:20:21.400 help system that is not sucky anymore okay that was pretty easy and and the cool thing about this is now whenever you want to add new options you have a
00:20:28.080 way to do it via driving it with your tests and that's a good thing I don't think we need to refactor this it was
00:20:33.720 just a couple lines of code um so the unhappy path this is where it gets tricky so here's our main method again
00:20:41.760 here's all the things that could go wrong I mean the link dur might not be there git could fail uh we could you
00:20:47.880 know Sim link a file that's already there and who knows what the app is even going to do so let's find out so we'll
00:20:54.600 run it and again we're setting the home environment variable to our fake home which is still there from the last last test our fake repo still there from the
00:21:00.520 last test um so we're just this is just exploring like what would our app do so then uh we run it it runs successfully
00:21:07.000 then we remove the git repo so that what will happen the next time we run it is it will check everything out again but
00:21:12.760 then the Sim link will fail because the files are still there so we do that and sure enough that is exactly what
00:21:18.760 happens uh Ellen doesn't like that file exists horrible back Trace you know this is this is pretty pretty terrible you
00:21:26.120 know we don't we should never show back Trace right that is that is amate hour the user should never have to deal with that they should just see a message
00:21:31.440 about what went wrong so let's fix that so we write another scenario my DOT files are there and there's a file in my
00:21:38.279 home directory already then when I uh run it I should exit with nonzero
00:21:43.320 because we didn't do what we were asked to do and the standard error should include our error message and the output
00:21:48.520 should not contain a back Trace simple enough and uh here's how we set that up
00:21:53.880 so we just touch the file to you know as part of our given and then to assert we um basically just defer to uh Aruba to
00:22:00.039 say the output should not contain anle bracket main I don't know a better way to check for a back Trace in the output I mean this is kind of cheesy but it'll
00:22:06.760 work you know it'll work well enough for us all right so what
00:22:13.880 happens okay sure enough somewhere in all of this it says that the output did contain
00:22:22.000 a back Trace so it's showing us all the output that we got and then it shows us a nice little diff here um and we can
00:22:27.559 see that yeah it's it's certainly right about that all right so let's fix that here's what we're calling main we're
00:22:33.080 just going to do the simplest possible thing ever which is surrounded begin rescue which I learned yesterday is not
00:22:39.279 the greatest way to do this but whatever uh this this will this will work well
00:22:44.640 enough let's
00:22:51.760 see okay it works great that is awesome so now the user doesn't get this horrible back trace and our app is a
00:22:58.600 little bit more robust and handles errors a little bit better but that's just one case like there's all kinds of
00:23:03.840 ways that this thing could fail and they get harder and harder to simulate because how are we testing right we're setting up a a special set of files and
00:23:10.880 directories and then we're running and seeing what happens and so setting up a special bunch of files can be really
00:23:16.520 hard if we want our app to fail in certain very specific ways um you know it's lots of setup and it makes it
00:23:22.279 potentially fragile something that might happen to work on my laptop might happen to not work on your laptop so what we
00:23:27.760 need to do is use tests unit test will allow us to um really isolate exact behaviors that we want to test for and
00:23:34.000 see how that goes um they also run fast right we don't have to set up anything we don't have to actually run the app
00:23:40.600 and you know testable units are good right we want units in our code we want little bits of code that do small simple
00:23:46.039 things so having a unit test means we have to have units and uh that'll be a good thing so since all of our tests are
00:23:53.240 passing we can refactor we can refactor anytime the tests are passing so let's do that to give ourselves the ability to test what we've done with the unit test
00:24:01.080 here is the code again um magically formatted so that when we extract it from the executable and we wrap it in a
00:24:07.400 class and put it in our lib directory uh we can now use this without having to run the application meaning we can test
00:24:13.960 it I'm also going to change that percent X to call system these will have a
00:24:19.039 similar effect except that system has the uh ability of me being able to mock it whereas I don't know how to mock
00:24:24.720 percent X um so right and then our utable uh all we need to do is just you
00:24:30.840 know call it from the CLI class instead of instead of be and everything's good we hope so before we do anything we got
00:24:38.000 to make sure we didn't screw anything up and we did not everything's still
00:24:43.640 good the refactor was good the app still works as expected okay so what's our approach
00:24:50.480 going to be um so we're going to test the main method and we're going to say that there's any problem anywhere in that method we're just going to raise an exception because the uh the executable
00:24:59.159 already handles getting an exception and printing that message to the user and exiting nonzero so we've already got
00:25:04.480 that taken care of so we can just let main throw an exception so we'll need a new test um now the file utils methods
00:25:11.159 they do throw exceptions already so let's not test them first I mean we might want to eventually but now they we
00:25:16.520 know that that's going to kind of get handled okay system on the other hand doesn't it returns true or false
00:25:21.720 depending on if the application that it calls exited zero or non zero so what we'll do is we'll mock system to return
00:25:28.320 turn false every time see how that goes okay so here we're going to set it up and I'm using test unit because I like
00:25:33.840 test unit you know if you don't too bad um we're going to stub CLI to just do
00:25:39.600 nothing unchanged dur for system it's going to return false and then for Ln it's going to raise an exception because
00:25:44.919 we shouldn't get that far the expected behavior is that when system returns false we raise an exception that there
00:25:49.960 is a problem and we do not proceed with the Ln and then to test that we'll assert that calling main raises a
00:25:56.480 runtime error and we also Al assert that the message of that runtime error is something that explains the actual
00:26:02.640 problem so we want to be clear about where in the code the exception came from and exactly what went wrong so we
00:26:10.240 got that see how that goes so now we're in test land so we
00:26:15.799 test okay unfortunately it's not in red I'm sorry I wish it were but we see here runtime error expected but nothing was
00:26:22.120 raised all right that's uh that's not surprising so we'll fix that so here is
00:26:28.520 the here's the call that we have now um and essentially what we're going to do is say unless system so we're going to actually check the return code then we
00:26:34.600 raise an error and we include the command in the string again we're fixing this the simplest quickest possible
00:26:45.080 way and sure enough it works and just to make sure we didn't mess anything else up everything else that the app does it
00:26:52.760 still does great so refactor right this is awful um
00:26:58.399 there's first of all there's control statement in there which I don't want to see second I'm repeating the command twice so that's no good um so let's yank
00:27:05.919 that out into a method called uh sh bang because it phrases an exception so we'll put a bang in there and doing that
00:27:12.559 requires uh you know gets us this thing in a command so we're not repeating the command twice and now our codee's still nice and kind of readable still looks
00:27:18.799 like a little you know shell scripty thing there so that's uh I like that so
00:27:24.760 now test okay that looks
00:27:30.440 good features okay that looks good so now we
00:27:36.039 have handle that great so you know we could continue doing that and and handle all the different cases and the cool
00:27:41.519 thing about unit test and about using mocking is that you can set up the exact conditions of failure that you want to
00:27:47.320 simulate um so I'm way ahead of time that's okay um so this is you know
00:27:52.960 mostly what I wanted to share with you hopefully this this got your kind of juices flowing I mean none of this is too terribly compc but bring it all
00:27:59.519 together it took me took me a little bit to figure out how to do it so basically what you should take away is starting with a template right rails is nice
00:28:06.320 because you get a template so you should have that for your command line apps too and if you you think you're going to be writing more than one of them set up a
00:28:11.559 template project for yourself use methodone use your own thing doesn't matter um start from the outside in so I
00:28:17.240 think it's handy to start with cucumber and start testing the user interface because that's how the user you are
00:28:22.600 going to be interacting with this thing so it makes it very easy to think about how it's supposed to work and what effect it's going to have of course as
00:28:28.320 soon as things start getting complicated right you want to get to your unit tests so that you can simulate all the little
00:28:33.640 esoteric side cases and weird things that could happen um and finally designing for testability is okay right
00:28:39.720 I replace that percent x with system specifically for testing and it's okay to design your app to make it easy to
00:28:45.799 test because then your app is easy to test um so that's all I had to share with you for today I appreciate it and
00:28:52.120 we ended it early so I have you know some time for questions if anybody has any
00:28:59.559 hey oh Jesus uh I had to I had to rewrite it so uh I'm almost done they're
00:29:04.880 supposed to announce it um uh next month oh
00:29:12.039 hey what's that yeah you know I just discovered it the other day one thing in this looking
00:29:18.320 at command line stuff the searchability of these tools are terrible and so there's like 10 things that do the same stuff so I had written this whole
00:29:24.039 methodone thing and it's pretty basic and then I saw the main jam and I'm like oh that's that's kind of like what I what I was doing so I don't know how
00:29:30.760 much I'll continue down it um yeah but s just searching for that outside of GitHub is very difficult because main is
00:29:57.039 everywhere so are are you wondering about um
00:30:02.279 leaving droppings around after the
00:30:09.159 tests oh yeah yeah yeah so um if you if you package it all up with ruby gems it actually pretty much takes care of it
00:30:15.760 and so if your sisadmin are cool with using ruby gems that makes it you know easy if they're not um you can turn a
00:30:21.200 gem into like an RPM and you can install it that way uh you know it works um but
00:30:27.120 I guess if you're really TR you can just jam it all into one file if you really wanted
00:30:44.720 to
00:30:50.360 yeah um I have not and I I dug a little bit into the Aruba internals like you
00:30:55.519 know researching this and and figuring this out and it's still very early um there's there's some wackiness in there
00:31:01.440 like it saves the output of every app it's ever run in the test and things like that so I'm not really not really
00:31:07.480 sure but that's that's an interesting thought to just stub out system and try to do stuff that way make everything run
00:31:12.519 faster I
00:31:18.960 guess all right well uh thank you guys and uh enjoy the early
Explore all talks recorded at RubyConf 2011
+55