Saturday, February 11, 2012

On javascript, and its testing

Lately I run into requirements that need an intensive javascript logic, including Ajax, jQueryUI and dynamic DOM manipulation logic. It makes me ponder over issues I have had with this whole thing. 

I hate it. 

Why? 

Javascript itself is a nice little language, completely object oriented, on prototyping paradigm. But when it comes to Ajax and DOM manipulation, well, there is no other term more appropriate than shit hits the fan. 

jQueryUI is a great tool. You select the DOM elements with the CSS3 selector, you attach things to it. Job's done. I can add a calendar widget, a date picker, and a rich text editor to my site in no more than a line of javascript code each. The hard part, though, begins when I have to begin handling the events these widgets fire. 

When I want to handle a hover event on a calendar entry, which element am I talking to? There is 'this' variable which is the reference to the object firing the event. But I want the event to affect other elements of the same kind. Dig through the 5000 lines of library code (which is not the cleanest code in the world, and no test to refer to either) is not an easy task. How is jQueryUI object defined? How do I access the methods inside the object? It is either my lack of understanding of the prototyping OO, or jQuery was not designed with the OO in mind. I suspect it's the former. 

Stuff like this reminds me how valuable it is to stick to Test-Driven-Development. The test lets you make sure that new code you add in will not break original functionality. The new test lets you make sure you are coding to the requirement, nothing more. It assures you that any refactoring you do maintains the same functionality and purpose, and nothing more. 

So how do we go about writing tests for these Javascript code? 

Well, not easy. The one word you need to keep reminding yourself: discipline. A strict one.

First let me explain on the technical side how javascript testing is done. There are two ways you can test javascript code. Standalone javascript unit testing, and high level functional testing.

Standalone javascript unit testing is a fine grain level of testing, focusing on the javascript code itself, and nothing else. The tool available is Jasmine. Jasmine runs anywhere javascript code can run. V8, Rubyracer, Rhino, Firefox, Chrome, IE, Opera, etc. All you need to do is to make the Jasmine standalone package accessible in the scope of the code, and you can harness all the testing facility it provides. To test the DOM manipulation logic, you need HTML fixture. The fixture needs to have the DOM you want to manipulate, and all the condition you expect from the 'real' page. When the test is run, the assertion should be able to assert that the DOM with the particular CSS matching condition should change in the way you have expected. 

You should see a problem by now. Do you? 

The problem is, we create the 'real' page with the server side code, with dynamic content depending on the states of objects related. Unless you are on node.js code, you cannot effectively invoke those 'real' pages with the plain Jasmine standalone. The fixture, then, has to significantly match the 'real' page. Once the 'real' page changes, chances are you forget to update your fixture. The tests pass, but somehow there are bugs in the system. Also, as soon as you fall in the pit of 'we can just have the server side code generate the javascript for us,' the ability to test the javascript loses its coverage. If that dynamic javascript includes crucial part of the logic, such as initialization variables, then the whole suite breaks down. As soon as you have Ajax code in the path of the testing logic, the suite breaks down. A particular problem related to running the javascript unit testing on check in time is automation. If you want to run it as a check in script, you need some javascript engine which is separated from the browser's engine. You have the same problem as the fixture problem. They are not the same engine, and they may be different in functionality. Rhino, for example, does not update the DOM element width and height properties without having to manually calling style update functions. You can manually fire up the browser and run the suite there. But if I get $20 each time we forget, I won't be running around offering my service to feed my wife and child.

There are merits to javascript unit testing, no doubt. The limitation, however, is significant. You need to be careful to make sure that the DOM manipulation logic is safe, static, and as isolated as possible.

On the other hand, we have the high level functional testing. 

In this approach, we have the browser adapter that takes macros to run automatically, then after the macro is performed, assertions are made on the resulting page. Selenium is the prime example of this kind of test. You start out with a Selenium adapter opening up a browser, most of the time Firefox. Tell it to visit a URL, may be enter some text in the login field, and password field, click the login button. Then the test suite asserts that the current URL of the window is the home page, with a particular DOM element saying "Welcome to my site, #{username}", etc. 

This approach, of course, overcome all the limitation we have in the other approach. We can rely on anything that results in the page of interest. There is no fixture to worry about. There are adapters to allow high level functionality on all kinds of programming language. The test suites themselves are written in any language of choice. 

Problem within this approach is all the steps you need to achieve just to be able to start testing. You need a server up and running, with everything setup according to the fixture specification, a user in the database, the date and the time for time-sensitive data, etc. At the end of every single testcase we have to reset the whole thing. The server process is separated from the test suite, as the suite runs on the client side, but contradicts itself and go ahead and resets the state of the app on the server side as well. Transactional fixture cannot be performed, and data cannot be rolled back instead of deleted. Performance is a major concern. The more we cover, the polynomially longer the suite takes to perform. If the test is badly designed, the degree of increasing complexity becomes exponential. Without enough coverage, there is no use in investing in functional suite. With high enough coverage, and a big enough codebase, the time to run the test is actually longer than it to be of any use. There's a balance we need to strike. 

We can run the functional suite at check in time, but you have to think about how many times you are checking in a day, and how much time is spent waiting. We can run it as a CI suite, but how much do we really care about it? A mysterious bug that ends up being a wrong testcase, or a change in a tiny DOM element id, or class, will remind you of a shepherd boy tale. Soon enough, you will lose interest in maintaining it. At that point it ceases to be useful. 

Now, back to our one word, discipline.

Problems we have with both approaches can be mitigated to a certain degree if the whole team accepts some discipline terms. When it comes to Javascript, it must be considered as a separated set of logic from the server side code. Do not rely on the server side to provide anything. It's irony that Javascript asynchronous behavior, which the client-server HTTP paradigm lacks, requires us to think about Javascript logic very differently from how we think about the web application client-server logic. Javascript logic fits much more to the .NET analogy than we anti-M$ monkeys dare admit. With that understanding in mind, we have a better clue of how the code executes, and how the logic are supposed to be tested. We need to design our Javascript logic so that the Ajax facility, the jQueryUI facility, and any facilities that is hard or impossible to unit test are contained, isolated, and tested by the high level functional suite. The rest can then be tested with the unit test for acceptable performance reasons. 

This discipline is not achievable overnight. Especially when you have existing code base that has javascript logic splattered all over the place, intermixing Ajax, DOM manipulation, and server side code. The amount of time and effort it takes to fix this, and the business value it brings make it really hard to justify the investment from the stakeholders. 

Either you do it right from the very start, or you need one hell of influence. 

Start with discipline, good things will then follow. 

Configuration Management with Rails

DevOps is apparently the hot new thing, as it should be. The ideal world of sysadmin having the ability to (test-driven-) develop the configuration management of all the machines in their responsibility is here, now. Combining that with the power of cloud computing, the world is moving toward the next age of computing. The true Cloud Age.

I am a Rails guy. It will stay that way at least for a few years to come. I have been deploying different Rails application on different projects. They are all automated, all in different mindset, goal, and methodologies. Recently I have a chance to have a complete control on deploying a new code to a new server for a startup company. The experience is priceless, and I have a giant leap of understanding of why DevOps is such an important step we have to take to achieve continuous delivery.

Let's see our case study. We have requirements that our application is to be deployed on an Ubuntu 11.04 64 bit server. There are, as are a few other projects, other apps hosted on it as well. There is only one server in this startup company, and we are not about to go spend the little money we have on another box. The server box already have Apache2, MySQL, and whatever else a PHP site needs to have a running PHP site. What I need done is to configure it so that Apache2 proxies a subdomain requests to Rails, with Unicorn as the Rails server.

There are a few tools available. There's Puppet. There's Chef. There's our plain old shell script. My past experience with Puppet is not bad, but could have been better. Remembering all that DSL is a little unnecessary when I already know Ruby and Chef uses Ruby as its DSL. So Chef is a reasonable tool.

Learning Chef is a pain. At a glance, the documentation is really detailed about how things are organized, what goes where. Dig a little further, you'll see that Opscode people have a whole lot of assumption that what they know in their heads are also in yours. A simple question like where/how attributes are supposed to be defined in the node definition JSON file is not easy to answer. Even a simpler question like how do I start is also quite a hard question. Especially when you are decidedly not paying for a hosted Chef solution.

Enough rants, back to the point. Since I don't have too large an infrastructure to take care of, and I cannot afford to have a separate server running, I choose to deploy the whole thing with chef-solo. I package the whole chef repository, ship it to the target machine, and run it standalone.

With Chef, you can abstract a server box as a node. You have a whole lot of recipes to choose from. You add these recipes to the node's run list. When run, Chef will look at all the attributes you specify, for example, what user and group unicorn should run as, what the name of your Rails application is, what the virtualhost name of the Rails application is, etc. Then it reads all the recipes in the Chef repository, defining services, and other facilities along the way. Then it looks at the node's run list. It figures out what recipe to run. The recipes themselves are the code that installs package, put the right files in the right places with the right owner, group, and permission. It can dynamically create the file with erb, too. Upon completion, you have a server with all the packages installed, and ready to go. No matter how many times you run the script on any number of different boxes, the end result is the same. People with little experience on configuration management will not believe how hard it is to achieve this idempotent property by hand.

When enough nodes are defined, you will begin to see a pattern. The run list can be aggregated into groups and if you need a certain feature, the whole group of recipes need to be included. Chef allows you to define this group of run list, and other attributes, as roles. So what you can do is define the roles such as database_master, database_slave, httpd, rails, etc. So any time you want to define a new node, you can pick out the roles you need without having to specify every single recipe you need all the time. Don't-Repeat-Yourself.

Of course, nobody can achieve everything in one go without mistake. We need a way to test this whole thing. We don't want to test it on our only server with another running application. VirtualBox saves the day for me. I setup a virtual machine, take a pristine snapshot before I run anything on it. Then I start deploying chef to it. Things breaks many times, as I am no expert on configuration management. All I need to do is revert the virtual machine to the pristine state, and I have a fresh fixture to test things again. There is also chef-spec that is supposed to let you test your Chef recipes as well. Unfortunately I cannot learn Chef in concurrent with any other thing at one time. So it goes to my lab list.

All is fine and dandy, I got two nodes defined, one is the staging VM which requires everything, Apache2, MySQL, Bundler, Bluepill, Rails, and Unicorn. The second is for the production server, with only Bundler, Rails, Bluepills, and Unicorn. Ok, great. Now I can deploy the code and start having a server to show people. ... therein lies a problem.

Traditionally, Rails has its own deployment automation tool, Capistrano. It does the second half of what Chef is supposed to do. It puts Rails code in the right place, with the right symlink, etc. It also installs the bundle, runs migrations, and fires up the (monitoring process, which in turn fires up the) server. It also allows you to define other deployment tasks to be done on any number of remote machines involved with this Rails code.

The conflict begins when you have a part of Chef recipe that needs Rails deployed, but you cannot deploy Rails before Chef completes, such as the monitoring tool configuration, logging facility, and so on. A bad solution is to let Chef finish half the way, and have Rails finish the other half. This works, but I don't like the fact that my configuration is now fragmented into two places. Rails itself should not have been involved in configuration management in the first place anyway.

My plan of action is then to have Chef take care of everything, including deploying Rails itself. I will have a new rails-deployment cookbook that will: check out Rails code, with the right git tag, to /tmp, and run bundle install and cap deploy from there. Ideally, I will want to have most of the configuration files, such as  deploy.rb, database.yml, etc, in erb templates and managed by Chef as well. The end result should allow me to just change the release tag in the rails role, run chef, and the new code should be deployed and ready to serve client requests.

When that is done, I will come back and tell you how it goes.

Thursday, October 7, 2010

Fun with ssh

I was trying all morning to ssh into a server with the public key authentication. There's a page about troubleshooting the public key authentication. I suspected the server is so old it uses ssh 1.5, but no. I can't think of something else beside googling on the error message:

Agent admitted failure to sign using the key.

And there it is: a bug on ubuntu.

Thursday, April 8, 2010

Indentation, indentation...

So, lately I see many developers prefer what I call ASCII Art Indentation style such as:


Class Foo
def bar(params = {})
Barf.find(params.merge :key => "value",
:key2 => "value2", ... and so on )
end
end


with the claim that it reads better, and you know exactly where to look for things.

I say it's completely opposite. It is no where near intuitive.

  • Say you have to change params to parameter_name_that_makes_more_sense ? you have to adjust your whole code base to reflect your tiny change, and apparently no one does.

  • For something like:

    Sundae.new(Icecream.with_fruit.no_sugar.all(:include => [:nutrition_facts, :production_plan]), :whipped_cream => WhippedCream.no_fat.first(:conditions => ["make is not :make and calorie < :calorie", {:make => "Booga", :calorie => 120}]))

    How do you systematically define the ASCII Art Indentation scheme that every one will follow and give exactly the same indentation? Where do you look for the make of the whipped cream? Not easy.



And for some reason, people loath my style of indentation: 1 more soft tab when you open a paren, brace, or bracket followed by a new line. and new line and one fewer soft tab before the close of the matching paren, brace, or bracket. It is said it's hard to see where things are, and it increases the number of lines. But behold:

Sundae.new(
Icecream.with_fruit.no_sugar.all(
:include => [:nutrition_facts, :production_plan]
),
:whipped_cream => WhippedCream.no_fat.first(
:conditions => [
"make is not :make and calorie < :calorie",
{:make => "Booga", :calorie => 120}
]
)
)


  • You can choose not to indent if your list is small enough, less than, say, 40 characters, half the 80x24 line.

  • You can write your code easily to indent stuff for you, consistently.

  • You can instruct people easily to indent the exact same way and give the exact same result.

  • You know exactly where you miss your close paren.

  • You know exactly where you can find your make of the whipped cream.

  • Your code rarely falls off the right edge while leaving an airstrip on the left side.



Prove me wrong, someone.

.not is EVIL!

What's a .not?

.not is a decorator method that decorates the object, so that the boolean method call that immediately follows the not call is negated. For example, if self.is_italic? returns true, self.not.is_italic? returns false.

Why would people use .not?

The only reason I have heard so far is because it sounds more like English...Pffft, sounds more like English!

Person.find_by_name("Paul Frank").not.is_popular?

How is that anyway similar to English: Is Paul Frank not popular?

What's wrong with logical negation '!' ? The only reason I have heard so far is that it's at the head of the statement, and with long condition, it falls off the screen... Pffft, falls off the screen!

First, I don't believe in reinventing the wheel that is worse than the original one.
Second, instead of using a CPU to flip a bit, we malloc the heap, create an object, extend some other object with this object, which alias the method to use a CPU to flip the flipping bit!
Oh, did I mention it's stupid?

In addition to that, .not is broken. In any given object, you cannot call .not on the boolean variable.

Class Person
@humorous = false;

def foo
@humorous.not #=> is not going to do anything!
end
end

More over, what prevents someone from doing foo = bar.not, and a thousand lines later, some other call foo.is_real? without knowing that it is being 'notted'?

So you cannot get rid of ! anyway. The best you can do is use the not operator to avoid !, then you have precedence problem to think about.

People then say oh who cares, how many CPU time do you use over that? I'd rather have more style than looking like C.

Well, you see, you don't usually run around a building complex just to go from your desk to the break room 25 feet away, even though it doesn't affect one bit the history of mankind. You don't do that because it's stupid. Oh, and did I mention .not is stupid in exactly the same way?

! has been around since the dawn of computer programming. Whoever thinks ! is not readable should quit programming and do some research on herbal hair gel. If your condition is way too long for you to see ! on a 24" 1080p widescreen, it's a code smell that you should rethink your logic, or model design in general. Whoever thinks ! is ugly and remind them of C should appreciate their own breadth of knowledge, and that without C there is no .not, and the fact that languages are means, not the goal.

Remind yourselves to always do the simplest thing that makes sense. .not is not simple, and not making much of a sense either.
\No new line at the end of file.

Thursday, January 22, 2009

Uploading a file.... from browser to a webapp, to another webapp ???

It's been six and a half months since I last do anything interesting, but here it is.

Problem: We have a RESTful web service, and a web application that talks to each other. We want to allow the user to upload a file, particularly binary, to the web app, and put that in the database in the REST server.
- We can't make a multipart request from the app to the REST server, it will come out as a string literal "".
- We can't call read on the UploadedStringIO and put that into the request from the app to the REST server. It will come out as gibberish string.
- We can't put the specific header that we want to do a multi-part request. I didn't even dare track that down.
- I was going to try make a raw HTTP request, saving the file in a temporary file on the app somewhere, but before I did that, I realized something...

IT'S ALL XML!

When we make requests to REST server using ActiveResource, the request body is marshaled into xml format. That means all your binary is going to mess up the xml document, and/or recognized as the string literal above.

What do I do?

A multi-part stream has a few important attributes: original_filename, content_type, and the file data we can get by calling read on the IO object. So I created a hash with these attribute names as keys (call the content whatever you like, make sure your REST server knows what it is and fetch it accordingly), and the value from the user's request as the value. I encode the binary content with the Base64.encode64. Now this hash can be marshaled easily. It's all ASCII text. So, go ahead and put that in your request parameters and fire away.

On the server side, handle the request as you would normally, except when you write the file to your storage, remind yourself that it is encoded by Base64.

Now comes the other way around, showing what we just uploaded. Again, it's all xml up until it reaches the app. So, fetch what you stored, make sure it goes out the REST server with the binary content encoded with Base64, and decode it at the app when you're ready to present it on screen.

This works pretty well with ActiveResource. A nice feature request to ActiveResource people would be to automate all this. :)

Tuesday, June 3, 2008

Asymmetrical database structures between production and non-production environments

So long time ago, before I was born as a consultant, there was some specific needs for the database tables. What we need was a table to mock external databases that we don't have access to in the development environment but the production does. What we did was creating a table right in the test cases and deal with it there.

Problems followed.

For one, MySQL and Rails doesn't have support for nested transaction.
For two, All tests written are in transaction that is rolled back every time the method ends.
For three, we need to ensure that the implementation employs the transaction mechanism somehow.

Whenever we create a new table, the transaction stops working. Whatever we did to the database, it was showing in the later test methods. Therefore there is no way we can ensure that when the transaction was supposed to be rolled back, it was indeed rolled back.

Solution:

The best we could do at that time was clearing out the tables ourselves. Andy Kotlinski wrote an assertion called assert_in_transaction to work around the problem. Talk to him if you would like more information on it.

Several months passed. The project was shelved for whatever reason.
Yet other months passed. The client decided to dig the zombie out of the grave.

All hell broke lose.

Somehow, Rails 2.0 doesn't allow us to work around it the way we did any more. The reason was something I couldn't remember any more because I forgot to blog about it at the time of digging that grave. What do we do?

Solution:

Instead of creating the tables in the test methods, I thought of a different way.

I want to have the transaction working.
I hate putting Data Description Language into the tests.
I like the "Elegance: Simple and Powerful", quoting Dr. David Matuszek from the University of Pennsylvania.

It turns out that we completely overlooked the power of Ruby. Database migrations are Ruby files. It means we can do pretty much anything a Ruby file can do. So we employ a simple condition to the migration

if RAILS_ENV == 'test'
create_your_fancy_test_tables
else
do_some_funny_stuff
end

Not bad, but still, not good. Why? Because we also want it in the development database too. And what happens when you have 8 different environments and you have to hand pick them?

Take two:

unless ['production'].include? RAILS_ENV do
create_your_fancy_test_tables
else
do_some_funny_stuff
end

Better. But still, what happens when you have 20 different migrations ahead?

Take three:

Define a module like so:

module SkipEnv
def skip_env
unless ['production'].include? RAILS_ENV do
yield
end
end
end

Put the module in the lib directory. Then, in the migration:

class << self do; include SkipEnv; end

def self.up
skip_env do
create_your_fancy_test_tables
end
end

def self.down
skip_env do
drop_your_fancy_test_tables
end
end

Hmm.. something's missing: the do_your_fancy_stuff. It turns out, our do_your_fancy_stuff does absolutely nothing, so we just cut it out.

It has to be a class method because the migration up and down methods are class methods.

Alright, looks good. We can create test tables anywhere except production, or any other environments you put into the array, and we can choose to skip environments in anything having access to the lib folder of the rails app, not only migrations. Awesome.

Further refinement would be to put it as a plugin, and install it in any Rails app you happen to have the need for such mechanism.

We didn't do that, because the zombie was buried back in the ground... again. This time, the reason is that to fix it and make it works for Rails 2 and have ActiveResource support, we would need to take a month and work on it full time.