I use Git and I love Git! I knew about Git hooks but never used in real life. So today I decided to get my hands dirty with Git hooks. I had a feeling it’d be awesome if I could use the “pre-commit” hook to do some routine processing before the changes are committed. I had two things in mind: 1) Unit Testing with PHPUnit or 2) Generating documentation using DocBlox. I chose Unit testing favouring simplicity.
Summary: I have a git repo for my PHP project. I have written some unit tests for the project and I want to run an automated unit test before changes are committed. If the unit tests succeed, the changes will be committed, if one of the tests fails, the commit will not proceed. This will ensure that I am always committing *working* codes to my Git repo.
Understanding Git Hooks
Git hooks are nothing but executable scripts which are run by Git on special events. If the hook script returns an exit code of 0, Git continues with whatever it was doing. For any non zero exit code, Git halts the operation. It’s that simple!
Git hooks are stored in the “.git/hooks” directory of a git repo. There are some samples already generated for us to check out! The script should be named with the hook, that is for the “pre-commit” hook the file name should also be “pre-commit”.
My Lame Unit Test
To begin with, we need at least one test case. So I wrote this really lame unit test and saved it as “Mytest.php”
|
<?php class Mytest extends PHPUnit_Framework_TestCase { public function testSayHello() { $this->assertEquals("hello","hello"); //lame :D } } |
Try running the script with PHPUnit like this:
Output:
|
PHPUnit 3.6.10 by Sebastian Bergmann. . Time: 0 seconds, Memory: 4.50Mb |
Okay, so we have a valid unit test that passes. Now let’s add a Git pre-commit hook to run this unit test before commit is made.
Setting Up Git Hook
Assuming that your project is already under Git DVCS, navigate to .git/hooks and create a file named “pre-commit” with the following contents:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
#!/usr/bin/php <?php // Hook configuration $project = 'My Test'; $testSuiteFile = '/home/masnun/test/Mytest.php'; // Tell the commiter what the hook is doing echo PHP_EOL; echo '+ Starting unit tests'.PHP_EOL; // Execute project unit tests exec('phpunit '.$testSuiteFile, $output, $returnCode); // if the build failed, output a summary and fail if ($returnCode !== 0) { // find the line with the summary; this might not be the last while (($minimalTestSummary = array_pop($output)) !== null) { if (strpos($minimalTestSummary, 'Tests:') !== false) { break; } } // output the status and abort the commit echo '+ Test suite for '.$project.' failed:'.PHP_EOL; echo $minimalTestSummary; echo chr(27).'[0m'.PHP_EOL; // disable colors and add a line break echo PHP_EOL; exit(1); } echo '+ All tests for '.$project.' passed.'.PHP_EOL; echo PHP_EOL; exit(0); |
PS: The pre-commit script is a modified version of this nice Gist: https://gist.github.com/975252
Make the “pre-commit” file executable by issuing this command:
NB: This is important that you make the script executable otherwise it will not function as a hook.
Making a Commit
We shall create some file or make some changes and try to commit. In my case, I just added the “Mytest.php” file and tried to commit. Here’s the output:
|
masnun@ubuntu:~/test$ git commit -am "added changes" + Starting unit tests + All tests for My Test passed. [master 3c473de] added changes 1 files changed, 1 insertions(+), 1 deletions(-) masnun@ubuntu:~/test$ |
Aha! All tests passed and the commit was made. Now let’s force the unit test to fail. Change the unit test to look like this:
|
<?php class Mytest extends PHPUnit_Framework_TestCase { public function testSayHello() { $this->assertEquals("hellox","hello"); //let's fail - hellox is never equal to hello } } |
And let’s commit now:
|
masnun@ubuntu:~/test$ git commit -am "this commit should fail" + Starting unit tests + Test suite for My Test failed: Tests: 1, Assertions: 1, Failures: 1. masnun@ubuntu:~/test$ |
Compare the output with previous output. There is nothing similar to:
|
[master 3c473de] added changes 1 files changed, 1 insertions(+), 1 deletions(-) |
The commit was not made because the test failed. So, it worked! 😀
I am really loving what it is going to offer me! I shall be writing tests for most of my projects so I never unintentionally break something in a commit. Unit testing is just one type of validation, I believe we could use lots of others (eg. PHP Code Sniffer) 🙂