21 Apr

7 reasons you’re testing your app wrong!

Best Practices, Ideas, Latest Developments, Our Apps

  1. No Test Cases: Your testers don’t have a fixed list of tests they have to execute, they are in the wild with lots of guess work.
  2. Waiting for crisis: Unless there is a major visible failure, no one in your team cares about testing.
  3. Isolation: Your testers start testing on their own and finish on their own when they feel like, and you have no report of which tests were passed, which were failed.
  4. Delayed Releases: Releases are delayed many times because of last minute test failures.
  5. Thinking too late: Not thinking about testing before you write your first line of code.
  6. No track of time: You don’t even know how much time you need to put in testing in first place.
  7. Complexity: Your app’s tests are so complex that you need to teach your testers how to test first, before they can actually start testing. Or even worse, you don’t want to hire testers because you know it’ll be too complex for anyone else.

Any of this sounds familiar? If yes, don’t be shy – we sucked at testing as much as you do (actually maybe more since you took time to read this post), before we decided we had to do something about it.

We’re building something very promising, a test case management system which will effectively work with you in solving all the problems listed above. If it looks worth to you, signup to our newsletter and we’ll inform you once we launch it. Even if you already use one, you’ll definitely find it faster, easier and leaner.

Update: Check out our new test case management app!

26 Jun

The art of keeping in touch with old clients

Best Practices, Business, Ideas

Mouthshut.com is India’s first website where customers can freely post reviews for companies. Founded in 2000, till then it has expanded well. I’ve used this site years back to expose bad service of a web hosting company.

Few days back, I had received an email from CEO of Mouthshut.com, Faisal Farooqui (Here’s the full newsletter). That email stated a latest testimonial of a very satisfied client and left a great impression on me. It’s a nice technique for any business to follow.

Here’s the most impressive part:

First, a story from a MouthShut.com member that shows we are making a positive difference: Pradeep Chopra is an IIT Delhi Graduate who co-founded OmLogic. I am also privileged to count him as a friend. Pradeep shared the following in an email (certain portions underlined for emphasis):

Recently, I had a terrible experience with Apple MacBook and Apple India. I posted a review at your community and only then Apple listened and actually gave me a new laptop. Thanks to your community and its power…Fortunately, the review is being read by around 6000 people and people have been writing to me whether they should be Apple MacBook.”

This was the testimonial included in email. First of all, this testimonial was enough for me to visit their site once again and start using it. It clearly states how successful they are in solving the problems they are meant to – which really impress me, personally.

Secondly, when you have already impressed your customers, you can also announce your new service or special offers. Just like they did!! It has given me an important tip to run my business.

Always keep in touch with all old clients.

- Abhimanyu Grover

15 Apr

Designing for Reusability: Taking advantage of Plugins in CakePHP

Best Practices, CakePHP, Ideas

Do you ever wanted to accomplish a better reusable CMS which helps you do things faster? If yes, you definitely need to know more about CakePHP Plugins. This manual about plugins is not just enough, there are many cool things you can do with them.

In this post, I will explain you, how to create a reusable Backend Plugin, which can easily fit in with any of your project. Although, I've written a tutorial before on how to create an administrator panel, but that is not so RAD approach.

Creating necessary files for plugin:

/app/plugins/backend

Ok, now comes interesting part:

Telling Plugin about our Admin Links

We will need our other controllers to tell Admin plugin about all the admin URLs/function. For this, we will create two new class variables called $admin_links and $admin_menu_order in all our controllers.

PHP:
  1. <?
  2. Class ClientsController extends AppController
  3. {
  4.     Var $name = ‘Clients’;
  5.     var $adminLinks = array('add' => 'Add New Client, 'index' => 'Show all clients);   // add points to admin_add() and ‘Add New Client’ corresponds to the anchor of link.
  6.     var $adminOrder = 1;   // Ordering inside admin panel
  7.  
  8.     // functions etc goes here..
  9. }
  10. ?>

Add these variables in all necessary controller files.

Plugin Connectivity to the rest of system

Since our plugin needs to look each and every controller present in system. I decided to create BackendController::cacheAdminLinks(),this function will get all admin links as specified in controllers, then store it to a temporary cache file. This way, all controllers need not to be loaded in every call.

PHP:
  1. <?php
  2.  
  3. class BackendController extends BackendAppController
  4. {
  5.     var $name = 'Backend';
  6.     var $uses = array();
  7.  
  8.     function cacheAdminLinks()
  9.     {
  10.         $adminLinks = array();
  11.         $ControllersPaths = array()// to store all controller paths
  12.  
  13.         // collect all controller paths to find backend links from
  14.         $allPaths = paths();
  15.         $appControllers = $allPaths['Controllers'];
  16.  
  17.         // find controllers of other plugins
  18.         $folder =& new Folder(APP.'plugins');
  19.         $plugins = $folder->ls();
  20.  
  21.         $ControllersPaths = am($ControllersPaths, $appControllers);   // merge app with all paths
  22.  
  23.         foreach($plugins[0] as $plugin)
  24.         {
  25.             $pluginControllers = $allPaths[Inflector::camelize($plugin)]['Controllers'];
  26.             $ControllersPaths = am($ControllersPaths, $pluginControllers)//add plugin controllers path
  27.         }
  28.        
  29.         $controllers = array()// to store all controllers here
  30.         foreach($ControllersPaths as $path)
  31.         {
  32.             $folder->cd($path);
  33.             $controllerFiles = $folder->find('.+_controller\.php$');
  34.             $controllers = am($controllers, array_map(array(&$this, '__controllerize'), $controllerFiles));
  35.         }
  36.  
  37.         $controllers = array_unique($controllers);
  38.  
  39.         foreach($controllers as $controller)
  40.         {
  41.             App::import('Controller', $controller);
  42.             $className = $controller."Controller";
  43.             $controllerInst = &new $className();
  44.            
  45.             if(isset($controllerInst->adminLinks))
  46.                 $adminLinks[$controllerInst->adminOrder] = array('name' => $controllerInst->name, $controllerInst->adminLinks);
  47.  
  48.         }
  49.  
  50.         // reorder & reform admin links in better form
  51.         $tmp_adminLinks = array();
  52.         foreach($adminLinks as $adminLink)
  53.         {
  54.             $reformedLinks = array();
  55.             $links = $adminLink[0];
  56.             foreach($adminLink[0] as $funcName => $link)
  57.             {
  58.                 $tmp_reformedLink = array('url' => $this->_getAdminUrl($adminLink['name'], $funcName) , 'title' => $link);
  59.                 $reformedLinks[]$tmp_reformedLink;
  60.             }
  61.  
  62.             $tmp_adminLinks[$adminLink['name']] = $reformedLinks;
  63.  
  64.         }
  65.         $adminLinks = $tmp_adminLinks;
  66.  
  67.         if (Configure::read('debug')==3)
  68.             $cacheExpires = '+5 seconds';
  69.         elseif (Configure::read('debug')==1 || Configure::read('debug')==2)
  70.             $cacheExpires = '+60 seconds';
  71.         else
  72.             $cacheExpires = '+24 hours';
  73.  
  74.         cache(LINKS_CACHE_FILE, serialize($adminLinks), $cacheExpires);
  75.         exit;
  76.  
  77.     }
  78.    
  79.     function _getAdminUrl($singular_name, $funcName)
  80.     {
  81.         return '/'.Configure::read('Routing.admin').'/'.low(Inflector::pluralize($singular_name)).'/'.$funcName;
  82.     }
  83.  
  84.     function __controllerize($file)
  85.     {
  86.         return Inflector::camelize(r('_controller.php', '', $file));
  87.     }
  88. }
  89. ?>

Setting up $layout & $admin_links at every admin call

Now, this is a bit tricky part – for every plugin you create in your app, you wouldn't want to add custom hacks anywhere throughout your app for every plugin. This is where you can utilize plugin hooks, as introduced by Felix in this post. I wouldn't go too deep to explain how it works, but it just basically allows you to create a file hooks.php inside your plugin folder. You can attach these hooks with your app, in this case just attach it to AppController::beforeFilter()

PHP:
  1. function beforeFilter()
  2. {
  3.         callHooks('beforeFilter', null, $this);   // you need to have this function defined somewhere – I guess bootstrap.php in best place
  4. }

In our hooks.php, we will create following function:

PHP:
  1. // this function will be called by AppController::beforeFilter() on every HTTP request
  2. function BackendbeforeFilterHook(&$controller)
  3. {
  4.     // if its the administrator/manager - change the layout
  5.     $pos = strpos($_SERVER['REQUEST_URI'], 'admin');
  6.     if($pos == true)
  7.     {
  8.         $controller->plugin='backend';
  9.         $controller->layout='admin';
  10.         $adminLinks = unserialize(cache(LINKS_CACHE_FILE, null));
  11.  
  12.         $controller->set('adminLinks', $adminLinks);
  13.  
  14.     }
  15. }

As you can see, an instance of controller is being passed to this function, and you can modify it as per your needs. So, we just overwritten the layout to admin.ctp inside app/plugins/backend/views/layouts and as also set our admin links to view from cache (saved by BackendController::cacheAdminLinks() )

Create admin.ctp

PHP:
  1. <div id="content">
  2.  
  3.     <div id="menu">
  4.         <? foreach($adminLinks as $title=>$adminLink) { ?>
  5.             <div class="admin_box">
  6.                 <?=$title;?>
  7.                 <ul>
  8.                 <? foreach($adminLink as $link) { ?>
  9.                 <li><a href="<?=$html->url($link['url']);?>"><?=$link['title'];?></a></li>
  10.                 <? } ?>
  11.                 </ul>
  12.             </div>
  13.         <? } ?>
  14.     </div>
  15.     <div id="admin_content">
  16.     <?php
  17.         if ($session->check('Message.flash')):
  18.                 $session->flash();
  19.         endif;
  20.     ?>
  21.     <?php echo $content_for_layout; ?>
  22.     </div>
  23.  
  24. </div>

This plugin gives you a nice looking basic backend panel, by just dropping a folder. [except you have to have callHooks() in your bootstrap.php, however this function is not just for this specific plugin, it will be utilized while working with other plugins as well]

I think the idea of connecting plugins with hooks is amazing, I just realized that it can increase reusability greatly. Just drop folders to your app, and add up a new functionality instantly. There was a project called SpliceIt started in Feb 2006 – which was fully based on plugins, but its not active anymore.

Hope you liked this idea and tutorial. I would like to thank my friend Jacob Mather, he originally inspired me to implement reusability this way.

- Abhimanyu Grover

Hire us

Contact us to get a free quote on your project.