Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testable Code and global constants

Here I am writing a small app with the sole intent of acquiring better OOP/testable code habits. And loving it, btw!

I am striving to assimilate the methodology behind developping 'testable code', mostly by reading posts from unit testing evangelists such as Sebastien Bergmann, Misko Hevery and Giorgio Sironi.

Among the hardships I assimilated is the misuse of static methods, objects that depend on objects that depend on objects. Currently, I am stuck on global wide constants. At the start of my application I load one single CONSTANT that simply sets the application mode in debug or prod:

/**
 *  APP_MODE values:
 *
 *  PROD   Production, no errors displayed email sent on error, logs to 
 *         logs/app-<date-time>.log.
 *
 *  DEBUG: All warnings and errors displayed, emails disabled and log messages 
 *         sent to console. Whether in-memory modifications to configuration 
 *         data are allowed
 */
 define("APPMODE", "DEBUG");

How can one test app classes for proper error handling depending on the state of this constant?

At first my thought was to simply move the global constant to a class constant instead in my init class and that solves the case for this particular class, but I am not satisfied with this procedure. I mean, should one simply avoid sitewide constants that are not "truly" constants in the strict sense of one possible value always?

I can't imagine testers have to write 2 test suites for every class, ie initClassDebugTest.php and initClassProdTest.php unless phpUnit can somehow reset global state? Should global constants used this way be avoided? I have a weird gut feeling I should not use a constant here at all. I would be very curious to know how test savy coders out there would handle global defines with 2 possible values at runtime.

like image 294
stefgosselin Avatar asked May 12 '11 07:05

stefgosselin


People also ask

What is a testable code?

Testable code is code of high quality. It's cohesive and loosely coupled. It's well encapsulated and in charge of its own state. In short, it's singularly defined in its own proper place so that it's straightforward to maintain and extend.

How do you ensure a code is testable?

One of the keys to writing highly testable code is to ensure that there is a strong separation between the different parts of an application, with clear, simple APIs for interaction between those parts.

Are global variables the same as constants?

Global variables aren't constant (you can change the value of a global variable, but you can only define a constant once). Constants aren't always global (you can declare a constant in a class). Also, global variables can be any type: scalar, array, or object. Constants can only be scalars.

Why do we use global constants?

Using global constants helps you standardize the use of literal values throughout your application. Constants make your code more readable and consistent, ensuring a reliable standard. Many global constants are built into OpenROAD (such as TRUE, FALSE, and CC_RED).


1 Answers

It mainly depends on how you create your objects and how many classes access this APPMODE.

Let's see what APPMODE does:

 *  DEBUG: All warnings and errors displayed, emails disabled and log messages 
 *         sent to console. Whether in-memory modifications to configuration 
 *         data are allowed

Something like this usually gets solved by passing in "DebugLogger" and "DontSendEmailMailer" to the classes that need to send mail.

By doing this you only need a few factories (or whatever you use to create your object graph) that need to know about "production" vs "development".

The classes that do your actual business logic should not know if it's run in production or not. That would mean the developer of each class would have to care about that and every class would need to be changed if you .. say.. have a "staging" environment. It introduces lots of global state that, like you discovered, is hard to test.

If errors should be displayed or not to be decided in your models in your php.ini or in your application bootstrap and should not concern the rest of your application.

I'd start of moving that "debug" functionality out of classes that need your APPMODE setting and move that into to dedicated (logging, mailing, ...) classes. The real thing (that actually sends mail) and the debug thing (that maybe writes mails to disk?). Both of those classes can be tested properly (testing a null logger is pretty easy ;) ) and you need to make that switch only a few times.

if($config->evironment() == "debug") {
    $logger = new DisplayEverythingLogger();
} else {
    $logger = new OnlyLogErrorsToTextfileLogger();
}


$neededModel = new ClassThatDoesActualWork($logger);

$controllerOrSomething = new ControllerOrWhatEveryDoesYourWorkflow($neededModel);
$controllerOrSomething->dispatch();

and so on. You can reduce the amount of global state step by step until you get rid of the define and only have a configuration setting.

When it comes to testing the class that does work you now won ether way because the logging is injectable and you can pass in a mock for testing.


So far for a first idea.. if you think that doesn't work for you maybe provide an example where APPMODE is used

like image 102
edorian Avatar answered Sep 20 '22 10:09

edorian