📜 ⬆️ ⬇️

How to test a legacy without pain and fear

image

Did you receive or come to a project that d + is twenty years old? PHP code was written in between the hunt for mammoths and therefore slightly do not read? You have to at least spoil it, at most - refactor or rewrite?

If after these questions your breathing or pulse has not become frequent, this article is for those who have already been a victim of such bullying or have a premonition of such a turn of fate.

It will be a question of one specific task typical for this situation - covering a unit with legacy code tests before its refactor or change. Namely - the creation of stubs (mocking, simulation, etc) for functions and / or methods on the fly.
')
I want to offer solutions for the following two, as for me - the main problems:

1. Consecutive return for a stub function


public function getSomething($param1, $param2) { $result1 = mysql_query('SELECT * FROM table1'); // ... if ($result1['field'] == $param1) { $result2 = mysql_query('SELECT * FROM table2'); } // ... if ($result2['field'] == $param2) { $result3 = mysql_query('SELECT * FROM table3'); } // ... return isset($result3) ? $result3 : $result2; } 

To cover such a code with a dough, there are several options:


2. Execution of code that does not affect the function under test.


 public function sendSomething(array $data) { $ch = curl_init(); $result = mysql_query('SELECT url FROM info WHERE id = ' . $data['someId']); curl_setopt($ch, CURLOPT_URL, $result['url']); curl_setopt($ch, CURLOPT_POSTFIELDS, implode('&', $data); // ... curl_exec($ch); } public function myMethod() { $data = SomeCLass::getSomeData(); // ... $data = OtherClass::modifyData($data); // ... //  - ,     $data // ... $this->sendSomething($data); // ... return $completelyOtherVariable; } 

Options:


Examples are mostly “far-fetched”, but to some degree of similarity, at least in my practice, such situations occur. Yes, and so clearer.

Most likely, the most painless all this will pass if you choose option # 3 in both cases. You just need to decide what to use, runkit or uopz ? For me, the answer is obvious because writing php code into a string and passing it as a parameter is a perversion.

The main function that we use, but not natively:

 void uopz_function ( string $class , string $function , Closure $handler [, int $modifiers ] ) 

It is extremely simple. We report the data of the function that we are going to override and pass an anonymous function that will be executed instead of the original one. Also there you can "play" with the scope of the function, but this is not about that now.

We could stop at this, because any middle + programmer has already approximately understood what to do next, and junior will hardly be entrusted with this task due to the high probability of suicide.
This article is intended only to slightly speed up the work of the convict and make his code a little more readable and short.

Therefore, I want to offer you 2 things:

  1. Holy war on the topic: "where, how and when to use traits correctly";
  2. Trait wrap for uopz where several convenient methods are implemented

I will not duplicate all the code, just leave a link to github here. And for convenience, briefly list his methods.

 uopzFlags($function, $flags); //   uopzRedefine($constant, $value); //   uopzFunction($function, Closure $closure, $backup = false); //  "" uopz_function   ,   backup-      : 'mysql_query'  ['ClassName', 'methodName'] uopzMuteFunction($function, $backup = false); //    -, ,    ,  -     ,  curl  "" url, etc uopzRestore($function); //    backup- uopzBackup($function); // backup / (    ) uopzFunctionSimpleReturn($function, $return, $backup = false); //    . return   ,  (  )   . uopzFunctionReplace($function, $replace, $backup = false); //    . uopzFunctionConsistentReturn($function, array $return, $backup = false); //    .    ,     . ,     . uopzFunctionConditionReturn($function, array $conditionList, $default = null, $backup = false); //    .          . uopzFunctionHook($function, Closure $closure, &$return, $backup = false); //       . 

Well, and, actually, the solution of those two problems with the help of "this":

1. Sequential return


 $this->uopzFunctionConsistentReturn('mysql_query', [ ['id' => 12, 'data' => 'dummy'], ['id' => 31, 'data' => 'dummy'], ['id' => 45, 'data' => 'dummy'], ]); // ,  ,    (  , ): $this->uopzFunctionConditionReturn('mysql_query', [ ['query', 'SELECT * FROM table1', ['id' => 12, 'data' => 'dummy']], ['query', 'SELECT * FROM table2', ['id' => 31, 'data' => 'dummy']], ['query', 'SELECT * FROM table3', ['id' => 45, 'data' => 'dummy']], ]); 

2. Interception of execution


 $this->uopzFunctionHook( ['ClassName', 'sendSomething'], function() { return $data; }, //     $data //      ,   myMethod   sendSomething  $data ); 

It saved me a huge amount of time, so I decided to share it. I hope someone will find this useful too. And even more I hope that in the world every day there will be less and less such code where it will be useful :)

Thanks for attention.

Source: https://habr.com/ru/post/316140/


All Articles