Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to unit test function with some randomness

I have the function that returns three random element array between 0 to 6. It ensures that all element cannot have the same value (you can have two elements with the same value, but not three). The following is a sample code.

public function getRandom() {
    $array = array(0, 0, 0);
    do {
        $array[0] = rand(0, 6);
        $array[1] = rand(0, 6);
        $array[2] = rand(0, 6);
    } while(($array[0] == $array[1]) && ($array[0] == $array[2]));
    return $array;
}

I am a little bit new to unit test, and the only think that I can think of testing this one is

  1. Testing this function 1,000 times and check if it returns data between 0 and 6, and all three elements cannot have the same value.
  2. Find a way to override function rand() so that it returns what we want:
    • Return value that make all three element have the same value, then, return value that have two elements with the same value. Then, return value that have all element with different value.

I wonder the if there are any approach or which of my approach is better for this case.

like image 510
invisal Avatar asked Mar 22 '23 11:03

invisal


1 Answers

As you have guessed correctly, you cannot really test randomness reliably.

The problem of controlling the randomness shows the weakness in your architecture: You have a class and method that you want to test, but you cannot control the function that is being called (rand()) during the test.

It might sound weird at first, but if you want to test with "controlled" randomness, you need to mock the random function somehow, so you'd need a wrapper around that PHP function that allows intercepting calls and returning defined test values during test time.

Think about it for a moment: If you'd had a class that has a method for randomness, and an instance of that class gets injected to your tested class to provide random values, you could mock that object during test and define the return values. Mission accomplished. :)

Now it sound's weird to instantiate a single object that has a single method rand() which passes all calls to the PHP function of the same name. It's even weirder to need to pass that object into the tested class during runtime to make it work. But you don't have to have that runtime dependency. You can also refactor your tested class to just look if there is a randomness provider injected and use that, and if not, use rand() directly.

If you want even less reworking of your class, there is a trick to override built-in PHP functions: If your class is inside a namespace and calls a native PHP function, that function will first be search inside that namespace. If you declare a function named rand() in your test file in the same namespace as the tested class, that class will call the namespaced function instead of the PHP one. You'd then only have to think of how to pre-define the return values of that mocked function, but you could probably use a global variable or a static property of the testcase class that gets filled with predefined "randomness":

namespace MyNameSpace;
function rand() {
    return array_shift(RandomTest::$randomValues);
}

class RandomTest extends PHPUnit_Framework_TestCase {
    public static $randomValues = array();
    public function testSomeRandomness() {
        self::$randomValues = array(0,0,0,0,1,2);
        // ... test 
    }
}

If I would have that job, I'd opt for the real PHPUnit mock object, a setter to inject it into the class, and coding it as an optional dependency that by default calls rand().

like image 139
Sven Avatar answered Apr 01 '23 10:04

Sven