How do I write unit tests in PHP with a procedural codebase?

久未见 提交于 2019-12-20 08:03:51

问题


I'm mostly convinced of the benefits of unit testing, and I would like to start applying the concept to a large existing codebase written in PHP. Less than 10% of this code is object-oriented.

I've looked at several unit testing frameworks (PHPUnit, SimpleTest, and phpt). However, I haven't found examples for any of these that test procedural code. What's the best framework for my situation and are there any examples of unit testing PHP using non-OOP code?


回答1:


You can unit-test procedural PHP, no problem. And you're definitely not out of luck if your code is mixed in with HTML.

At the application or acceptance test level, your procedural PHP probably depends on the value of the superglobals ($_POST, $_GET, $_COOKIE, etc.) to determine behavior, and ends by including a template file and spitting out the output.

To do application-level testing, you can just set the superglobal values; start an output buffer (to keep a bunch of html from flooding your screen); call the page; assert against stuff inside the buffer; and trash the buffer at the end. So, you could do something like this:

public function setUp()
{
    if (isset($_POST['foo'])) {
        unset($_POST['foo']);
    }
}

public function testSomeKindOfAcceptanceTest()
{
    $_POST['foo'] = 'bar';
    ob_start();
    include('fileToTest.php');
    $output = ob_get_flush();
    $this->assertContains($someExpectedString, $output);
}

Even for enormous "frameworks" with lots of includes, this kind of testing will tell you if you have application-level features working or not. This is going to be really important as you start improving your code, because even if you're convinced that the database connector still works and looks better than before, you'll want to click a button and see that, yes, you can still login and logout through the database.

At lower levels, there are minor variations depending on variable scope and whether functions work by side-effects (returning true or false), or return the result directly.

Are variables passed around explicitly, as parameters or arrays of parameters between functions? Or are variables set in many different places, and passed implicitly as globals? If it's the (good) explicit case, you can unit test a function by (1) including the file holding the function, then (2) feeding the function test values directly, and (3) capturing the output and asserting against it. If you're using globals, you just have to be extra careful (as above, in the $_POST example) to carefully null out all the globals between tests. It's also especially helpful to keep tests very small (5-10 lines, 1-2 asserts) when dealing with a function that pushes and pulls lots of globals.

Another basic issue is whether the functions work by returning the output, or by altering the params passed in, returning true/false instead. In the first case, testing is easier, but again, it's possible in both cases:

// assuming you required the file of interest at the top of the test file
public function testShouldConcatenateTwoStringsAndReturnResult()
{
  $stringOne = 'foo';
  $stringTwo = 'bar';
  $expectedOutput = 'foobar';
  $output = myCustomCatFunction($stringOne, $stringTwo);
  $this->assertEquals($expectedOutput, $output);
}

In the bad case, where your code works by side-effects and returns true or false, you still can test pretty easily:

/* suppose your cat function stupidly 
 * overwrites the first parameter
 * with the result of concatenation, 
 * as an admittedly contrived example 
 */
public function testShouldConcatenateTwoStringsAndReturnTrue()
    {
      $stringOne = 'foo';
      $stringTwo = 'bar';
      $expectedOutput = 'foobar';
      $output = myCustomCatFunction($stringOne, $stringTwo);
      $this->assertTrue($output);
      $this->Equals($expectedOutput, $stringOne);
    }

Hope this helps.




回答2:


What unit tests do well, and what you should use them for, is when you have a piece of code that you give some number of inputs, and you expect to get some number of outputs back out. The idea being, when you add functionality later, you can run your tests and make sure it's still performing the old functionality the same way.

So, if you have a procedural code-base, you can accomplish this calling your functions in the test methods

require 'my-libraries.php';
class SomeTest extends SomeBaseTestFromSomeFramework {
    public function testSetup() {
        $this->assertTrue(true);
    }

    public function testMyFunction() {
        $output = my_function('foo',3);

        $this->assertEquals('expected output',$output);
    }
}

This trick with PHP code bases is, often your library code will interfere with the running of your test framework, as your codebase and the test frameworks will have a lot of code related to setting up an application environment in a web browser (session, shared global variables, etc.). Expect to spend sometime getting to a point where you can include your library code and run a dirt simple test (the testSetup function above).

If your code doesn't have functions, and is just a series of PHP files that output HTML pages, you're kind of out of luck. Your code can't be separated into distinct units, which means unit testing isn't going to be of much use to you. You'd be better off spending your time at the "acceptance testing" level with products like Selenium and Watir. These will let you automated a browser, and then check the pages for content as specific locations/in forms.




回答3:


You could try to include your non-oop code into a test class using

require_once 'your_non_oop_file.php' # Contains fct_to_test()

And with phpUnit you define your test function :

testfct_to_test() {
   assertEquals( result_expected, fct_to_test(), 'Fail with fct_to_test' );
}


来源:https://stackoverflow.com/questions/899390/how-do-i-write-unit-tests-in-php-with-a-procedural-codebase

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!