Optimizing unit tests which testing same methods with different parameters - php

I have lots of PHP classes like this:
class Article
{
public function fetchAll($params) {
if ($params["client"] == "mobile") {
return [
"author" => "",
"body" => ""
];
}
return [
"author" => "",
"body" => "",
"publishedOn => """
];
}
}
As you see fetchAll() method returning different fields according to $params["client"].
I need to check existence of these fields. I have 3 options to achieve my goal.
(Note: All codes simplified, dont worry about syntax errors.)
Method 1 : Different test methods for different clients
class ArticleTest extends ...
{
public function testFetchAll() {
$params = [];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
}
public function testFetchAll2() {
$params = ["client" => "mobile"];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
Cons: Using same codes again and again, it's a really bad practice. No need to explain. Refactoring is really difficult. Also this method will cause dozens of test methods.
Method 2 : Using for-loop for different clients
class ArticleTest extends ...
{
public function testFetchAll() {
for ($i=0; $i<2; $i++) {
$params = [];
if ($i == 1) $params["client"] = "mobile";
$data = $article->fetchAll($params);
if ($i == 0)
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
else
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
}
Cons : I'm not sure if using loops on PHPUnit tests is a good idea. Also code readability decreased.
Method 3 : Different test cases / test files for different clients
// articleTest.php
class ArticleTest extends ...
{
public function testFetchAll() {
$params = [];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body","publishedOn"]);
}
}
// articleTest2.php
class ArticleTest2 extends ...
{
public function testFetchAll() {
$params = ["client" => "mobile"];
$data = $article->fetchAll($params);
$this->assertEqual(array_keys($data), ["author","body"]);
}
}
Cons: This method causing dozens of test files, and using same codes again and again. Refactoring is really difficult.
Performance Comparison
Method 1 : Time: 127 ms, Memory: 8.00Mb
Method 2 : Time: 125 ms, Memory: 8.00Mb
Method 3 : Time: 96 ms, Memory: 8.00Mb
I'm trying to understand which method is better with lots of classes like this and waiting for a discussion about optimizing unit tests in this situation.

EDIT: Even if your question focus on optimization, there is actually a broadly accepted best practice for your case so that's what I have shown in my answer.
Just use dataProviders. With those you can execute the same test, passing arguments to the test method. The arguments can be variations on the params or / and the expected result.
So you could do this:
public function fetchAllTestData() {
return [
// case 1
[
[], // params
['author', 'body', 'publishedon'] // expectedKeys
],
// case 2
[
['client' => 'mobile'], // params
['author', 'body'] // expectedKeys
],
];
}
/**
* #dataProvider fetchAllTestData
*/
public function testFetchAll(array $params, array $expectedKeys) {
$data = $article->fetchAll($params);
$this->assertEqual($expectedKeys, array_keys($data));
}
This way the test will be executed independently for each dataProvider row. This means that if the first case fails, the second will be tested so you will know if it also fails or not. If you use loops inside the test case, whenever there is a failure, the rest of the cases won't be tested.
Take a look at dataProviders documentation.
NOTE When writing tests please pay attention to the assert methods parameters ordering. Usually the expected data comes first. If you pass them in reversed order the error messages will be misleading and the arrays and objects comparison will tell that there are missing rows when in fact the rows should not be there.

Related

Mocking data and Calling action from container

I'm having issues making a Unit Testing because I have this code helperController Controller:
public static function applyDiscount(array $params, MoneyParser $moneyParser): Money
{
$discount = Money::BRL(0);
switch ($params['typeOf']) {
case 'above_value':
$amount = $params['value']->subtract($params['value']->multiply(0.85));
$discount = $discount->add($amount);
break;
case 'above_quantity':
$subtractedQuantity = intval(round($params['quantity'] / 3));
$value = $params['value']->multiply($subtractedQuantity);
$discount = $discount->add($value);
break;
case 'same_category':
$lowestVal = 0;
foreach ($params['cart'] as $item) {
if (empty($lowestVal)) {
$lowestVal = $item['unitPrice'];
} elseif ($item['unitPrice'] < $lowestVal) {
$lowestVal = $item['unitPrice'];
}
}
$valueOf = floatval($lowestVal) * 0.40;
$rounded = floor($valueOf * 100) / 100;
$unitPrice = $moneyParser->parse(strval($rounded), 'BRL');
$discount = $discount->add($unitPrice);
break;
case 'for_employees':
$amount = $params['value']->subtract($params['value']->multiply(0.80));
$discount = $discount->add($amount);
break;
case 'for_newones':
$amount = $moneyParser->parse('25', 'BRL');
$discount = $discount->add($amount);
break;
}
return $discount;
}
And I'm trying to mock the data to test it, for example, in the first case I'm trying to do this:
public function testApplyDiscountAboveValue(): void
{
$params = [
'typeOf' => 'above_value',
'value' => Money::BRL('3001'),
];
$money = app(MoneyParser::class);
$currency = app(Currency::class, ['code' => 'BRL']);
$moneyValue = app(Money::class, [
'amount' => 3001,
'currency' => $currency
]);
$discount = app(Money::class, [
'amount' => 450,
'currency' => $currency
]);
$mock = Mockery::mock($moneyValue);
$mock->shouldReceive('multiply')
->with(0.85)
->once()
->andReturn(Money::BRL('2550'));
$mock->shouldReceive('subtract')
->with(Money::BRL('2550'))
->once()
->andReturn(Money::BRL('450'));
$mock->shouldReceive('add')
->with(Money::BRL('450'))
->once()
->andReturn(Money::BRL('450'));
$response = app(HelperDiscountController::class)->applyDiscount($params, $money);
$this->assertEquals($discount, $response);
}
When I execute the phpunit my result is this:
App\Http\Controllers\HelperDiscountControllerTest::testApplyDiscountAboveValue
Mockery\Exception\InvalidCountException: Method multiply(0.85) from Mockery_0_Money_Money_Money_Money should be called
exactly 1 times but called 0 times.
I'm trying to understand what I am doing wrong. Did I have all the necessary adaptations to test my code at least for the first case?
First of all, if you have a static method in a controller, you are doing something wrong, it should be a non-static method, like this:
public function applyDiscount(array $params, MoneyParser $moneyParser): Money
{
// ...
}
Second, if the method applyDiscount is a helper method inside a controller or a parent controller, I would not pass an array $params, but directly a Request $request and use that object, because the array could have missing indexes and you should constantly validate them, with a Request object you can directly do $request->has('param') or do $request->input('param') (and pass a second parameter if you want a default value different from null if param is not present on the request as a parameter).
I do see that, for example, $params['value'] is an object, because you are doing operations on it, so if $params is not a parameter (you will never get an object from a Request as a parameter) then rename the variable to something else that truly repesent what it has, or at least, pass the content as separte method's parameters instead of an array of objects or similar.
Third, now onto the test. $response = app(HelperDiscountController::class)->applyDiscount($params, $money); will never work (get the mock), because you are never passing that mock into the Service Cointaner or into an instance as a dependency, you are creating the mock and not using it anywhere.
The code is a little bit confusing for me right now, but what you should have is something like this:
Controller
public function applyDiscount(Request $request, ): Money
{
// Validate or use a Form Request
// Do anything else you would need to
$result = $this->helperMethod(... needed params);
// Either return the result or process more stuff and return something if needed
return $result;
}
Test
public function testApplyDiscountAboveValue(): void
{
// When you need to mock something, you do it this way
$mock = $this->mock(WhateverClass::class);
$mock->shouldReceive('multiply')
->with(0.85)
->once()
->andReturn(Money::BRL('2550'));
$mock->shouldReceive('subtract')
->with(Money::BRL('2550'))
->once()
->andReturn(Money::BRL('450'));
$mock->shouldReceive('add')
->with(Money::BRL('450'))
->once()
->andReturn(Money::BRL('450'));
$response = $this->post(
'your/url/to/the/controller/endpoint',
[
'param1' => 'data1',
'paramN' => 'dataN',
],
);
$this->assertEquals($discount, $response);
}
See that:
When you use $this->mock(Class), it is internally binding that class resolution (when you do app(class) or resolve(class) or you let Laravel use Dependency Injection), it will return that mock
I am calling an endpoint using $this->post(url, data), you can use any HTTP method, but must match the route (that is how you test routes)
You need to share more info becausue it is very confusing (for me) what you are trying to test
Check my StackOverflow profile, I have some links in my profile about testing, it will help you a lot
Check the documentation as it is already explained in there most of the things you are trying to do
HTTP test
Mocking
Automatic Resolving/Dependency Injection

How to refactor this setTags method?

I want to follows a rule one function do one thing only. So i need to refactor this method. That do many thing to do a task (setTags). Mostly inside foreach the code will re-use to other method (setGenres, setLanguage, setCharacter, setCountries) with same style code.
This raw original method. in bottom my refactor method.
public function setTags($tags)
{
foreach ($tags as $name)
{
$tag = Tag::firstOrCreate([
'name' => ucwords($name)
]);
if (empty($this->media->tags()->where('tag_id', $tag->id)->exists())) {
$this->media->tags()->attach($tag->id);
}
}
}
Refactor method
// is it right to name it createModelValue?
public function createModelValue($model, $value) {
$collection = $model::firstOrCreate([
'name' => ucwords($value)
]);
if (!empty($collection)) {
return $collection;
}else{ return NULL; }
}
// new problem how to pass $model (tag) as function relationship to replace tags function
public function setModel($model) {
if (empty($this->media->tags()->where("{$model}_id", $model->id)->exists())) {
$this->media->tags()->attach($model->id);
}
}
Laravel has a sync() method that will come in handy here, instead of attaching each tag individually, you can just say attach all tags with following id and remove everything else.
$tagsCreated = collect($tags)->map(function (string $tag) {
return Tag::firstOrCreate([
'name' => ucwords($tag)
]);
});
$this->media->tags()->sync($tagsCreated);
In all honesty, being explicit in code is sometimes better than being clever. Your first version of your method is very specific and clearly indicates what it does. Your refactored clever version, i was like "uh whats going on". So choosing between these two, i would like the one above. Also your method only does one, it set tags from an array of strings. Could that be optimized yes, but very minor. The principle is more applicable if you have an email service that sends emails, you should not start to create pdf's at the same time.
I have done similar logic as the refactored but consider how big the project is and how many times you repeated the logic before you start that.
A generic refactor could be as simple as this, if you are feeling adventurous you could implement tags as a trait and/or an interface.
function setTags($model, $tags) {
$tagsCreated = collect($tags)->map(function (string $tag) {
return Tag::firstOrCreate([
'name' => ucwords($tag)
]);
});
$model->tags()->sync($tagsCreated);
}

How to achieve reverse localization (user input) on Laravel 5.x?

Laravel's localization features provide a convenient way to retrieve strings in various languages, allowing to easily support multiple languages within the application.
At this point I'm using the function trans() and related that ships with Laravel, to translate (localize) the user interface (output) of my application, as one would usually do with a default setup. As how this strategy works, we are translating english keys into another language. It is clear that keys need not to be in any particular language, and they don't even need to be readable text, that we just keep them readable for sake of software maintainability and comply a standard, since the programmatic keywords are in english.
My question is if there's a well known way to accurately translate user input in a selected language back to the base language (let's say english).
An example of this would be letting the user to write certain keywords or expressions in their language as input, and get them into a base language.
Currently, I would do something like this:
// Dummy Sample Code I
$foreignInput = 'lunes';
$translatedToBase = trans("input.days.$foreignInput");
dd($translatedToBase); // "monday"
// lang/es/input.php
[
// ...
'days' => [
'lunes' => 'monday',
// ...
],
// ...
]
I would also do something like this:
// Dummy Sample Code II
$foreignInput = 'lun';
$translatedToBase = trans("input.days.$foreignInput");
dd($translatedToBase); // "mon"
// lang/es/input.php
[
// ...
'days' => [
'lun' => 'mon',
// ...
],
// ...
]
This would perfectly work for the purpose, however, it feels hacky (unsafe, little reliable, hard to maintain and counter-intuitive) and I wonder if there's a package of better way to achieve this. I could even require not using trans(), that's fine for me.
I understand there may be lots of options, so suggestions are welcome as long as I can keep some constrains:
Also please note that in this example I used a weekday name, but I'd not limit the solution to a datetime context.
Plus, I'm seeking to rely on the translation to be strict, this means that I'm expecting it to be deterministic, reason I'm putting aside a live translation external service.
Thanks for reading!
First of all, I wouldn't structure the language files the way you suggested. It's a subversion of Laravel's organisation to have shifting keys that have the same value throughout translations, and they would have to be specifically populated for reverse translation because they'd be too hard to use for normal translation. I'd much rather stick to the normal Laravel structure:
// lang/en/input.php
[
// ...
'days' => [
'monday' => 'Monday',
// ...
],
// ...
];
// lang/es/input.php
[
// ...
'days' => [
'monday' => 'lunes',
// ...
],
// ...
]
// lang/de/input.php
[
// ...
'days' => [
'monday' => 'Montag',
// ...
],
// ...
]
Here's an object capable of processing the reverse translation:
<?php
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use RecursiveArrayIterator;
use RecursiveIteratorIterator;
class ReverseTranslator
{
protected $languageGroups = [];
protected function loadGroups($locale)
{
// Iterate through all available groups and store
$languagePath = resource_path("lang/{$locale}/");
$languageGroups = array_map(function ($file) use ($languagePath) {
return str_replace([$languagePath, '.php'], '', $file);
}, File::glob($languagePath.'*.php'));
$this->languageGroups[$locale] = $languageGroups;
return $languageGroups;
}
protected function lowercase($value)
{
return is_array($value) ? array_map([$this, 'lowercase'], $value) : Str::lower($value);
}
protected function arraySearchRecursive($search, array $array, $mode = 'value', $return = false) {
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach ($iterator as $key => $value) {
if ($search == $value) {
$keys = [];
for ($i = $iterator->getDepth()-1; $i >= 0; $i--) {
$keys[] = $iterator->getSubIterator($i)->key();
}
$keys[] = $key;
return implode('.', $keys);
}
}
return false;
}
protected function search($foreign, $group, $locale = 'en')
{
// Load all strings for group in current language
$groupStrings = trans($group);
// Recursive and case-insensitive search
return $this->arraySearchRecursive($this->lowercase($foreign), $this->lowercase($groupStrings));
}
public function get($foreign, $group = null, $locale = 'en')
{
if (!$group) {
if (!isset($this->languageGroups[$locale])) {
$this->loadGroups($locale);
}
foreach ($this->languageGroups[$locale] as $group) {
$key = $this->search($foreign, $group, $locale);
if ($key !== false) {
return trans("{$group}.{$key}", [], null, $locale);
}
}
// Failed to match -- return original string
return $foreign;
}
$key = $this->search($foreign, $group, $locale);
if ($key !== false) {
return trans("{$group}.{$key}", [], null, $locale);
}
// Failed to match -- return original string
return $foreign;
}
}
Put this on your app/Providers/AppServiceProvider.php inside the register method:
$this->app->bind('reverse-translator', function () {
return new ReverseTranslator();
});
Then you could build a Facade to access your object more literally.
class ReverseTranslator extends \Illuminate\Support\Facades\Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'reverse-translator';
}
}
Now you can write your helper like so (using the Facade, not the object itself):
function rtrans($foreign, $group = null, $locale = 'en') {
return ReverseTranslator::get($foreign, $group, $locale);
}
Then usage would simple:
rtrans('lunes'); // "Monday"
rtrans('lunes', 'input'); // "Monday", quicker
rtrans('lunes', 'input.days'); // "Monday", quickest
rtrans('lunes', 'other'); // "lunes", because of wrong group
rtrans('lunes', null, 'de'); // "Montag"
Note:
I've made the search case-insensitive to be a little easier to use, and because case is relevant and should be considered in translation arrays (see "Monday" on lang/en/input.php).
Obviously it's much more efficient to know which file you want to load and search, so if you know your translating weekdays, you'd pass 'days' as your group. But if you don't know, then you'd have to iterate through all available language files, which could become costly depending on how often you do it and how many files you have.
Maybe the ideal solution would be to extend the default Laravel translator to give it "reverse translation" capabilities, instead of using a stand-alone object like above, but for the sake of clarity it has been isolated.
I hope this is a good starting point. Buena suerte!

best practice for initializing class members in php

I have lots of code like this in my constructors:-
function __construct($params) {
$this->property = isset($params['property']) ? $params['property'] : default_val;
}
Some default values are taken from other properties, which was why I was doing this in the constructor. But I guess it could be done in a setter instead.
What are the pros and cons of this method and is there a better one?
Edit: I have some dependencies where if a property is not supplied in the $params array then the value is taken from another property, however that other property may be optional and have a default value, so the order in which properties are initialized matters.
This means that if I used getters and setters then it is not obvious which order to call them in because the dependencies are abstracted away in the getter instead of being in the constructer...
I would suggest you, to write proper getter/setter functions, which assert you the correct data-type and validations (and contain your mentioned default-value logic). Those should be used inside your constructor.
When setting multiple fields, which depend on each other, it seems to be nice to have a separate setter for this complex data. In which kind of way are they depending anyway?
e.g.:
// META-Config
protected $static_default_values = array(
"price" => 0.0,
"title" => "foobar"
// and so on
);
protected $fallback_getter = array(
"price" => "getfallback_price"
);
// Class Logic
public function __construct($params){
$this->set_properties($params);
}
public set_properties($properties){
// determines the sequence of the setter-calls
$high_prio_fields = array("price", "title", "unimportant_field");
foreach($high_prio_fields as $field){
$this->generic_set($field, $properties[$field]);
// important: unset fields in properties-param to avoid multiple calls
unset($properties[$field]);
}
foreach($properties as $field => $value){
$this->generic_set($field, $value);
}
}
// this could also be defined within the magic-setter,
// but be aware, that magic-functions can't be resolved by your IDE completely
// for code-completion!
private function generic_set($field, $value){
// check if setter exists for given field-key
$setter_func = "set_".$v;
if(method_exists($this, $setter_func){
call_user_func_array(array($this, $setter_func), array($v));
}
// else => just discard :)
}
// same comment as generic-set
private function generic_get($field){
// check if value is present in properties array
if(isset($this->properties[$field]){
return $this->properties[$field];
}
// check if fallback_getter is present
if(isset($this->fallback_getter[$field]){
return call_user_func_array(array($this, $this->fallback_getter[$field]));
}
// check for default-value in meta-config
if(isset($this->static_default_values[$field]){
return $this->static_default_values[$field];
}
// else => fail (throw exception or return NULL)
return null;
}
public function get_price(){
// custom getter, which ovverrides generic get (if you want to)
// custom code...
return $this->generic_get("price");
}
private function getfallback_price(){
return $this->properties["other_value"] * $this->properties["and_another_value"];
}
public function set_price($price){
$price = (float) $price; // convert to correct data-type
if($price >= 0.0){
$this->properties["price"] = $price;
}
// else discard setting-func, because given parameter seems to be invalid
// optional: throw exception or return FALSE on fail (so you can handle this on your own later)
}
Update to your edit:
the modified source-code should solve all your demands (order of setter-funcs, different resolvings of get-value).
Create "globally available" function array_get.
public static function array_get($array, $property, $default_value = null) {
return isset($array[$property]) ? $array[$property] : $default_value;
}
When having a lot of default options and you need to be able to overwrite them - as you have maybe seen in jQuery using .extend() before - I like to use this simple and quick method:
class Foo {
private $options;
public function __construct($override = array()) {
$defaults = array(
'param1' => 'foo',
'param2' => ...,
'paramN' => 'someOtherDefaultValue');
$this->options= array_replace_recursive($defaults, $override);
}
}
Especially for getting classes started this is a very easy and flexible way, but as already has been mentioned if that code is going to be heavily used then it probably not a bad idea to introduce some more control over those options with getters and setters, especially if you need to take actions when some of those options are get or set, like in your case dependencies if I understood your problem correctly.
Also note that you don't have to implement getters and setters yourself, in PHP you can use the __get and __set magic methods.
It follows some useless code that hopefully gives some ideas:
[...inside Foo...]
public function __set($key, $value){
switch(true){
//option exists in this class
case isset($this->options[$key]):
//below check if $value is callable
//and use those functions as "setter" handlers
//they could resolve dependencies for example
$this->options[$key] = is_callable($value) ? $value($key) : $value;
break;
//Adds a virtual setter to Foo. This so called 'magic' __set method is also called if the property doesn't exist in the class, so you can add arbitrary things.
case $key === 'someVirtualSetterProp': Xyzzy::Noop($value); break;
default:
try{ parent::__set($key, $value); } catch(Exception $e){ /* Oops, fix it! */ }
}
}
Note that in the above examples I squeezed in different approaches and it usually doesn't make sense to mix them like that. I did this only to illustrate some ideas and hopefully you will be able to decide better what suits your needs.

PHP Object Validation

I'm currently working on an OO PHP application. I have a class called validation which I would like to use to check all of the data submitted is valid, however I obviously need somewhere to define the rules for each property to be checked. At the moment, I'm using arrays during the construction of a new object. eg:
$this->name = array(
'maxlength' => 10,
'minlength' => 2,
'required' => true,
'value' => $namefromparameter
)
One array for each property.
I would then call a static method from the validation class which would carry out various checks depending on the values defined in each array.
Is there a more efficient way of doing this?
Any advice appreciated.
Thanks.
I know the associative array is used commonly to configure things in PHP (it's called magic container pattern and is considered bad practice, btw), but why don't you create multiple validator classes instead, each of which able to handle one rule? Something like this:
interface IValidator {
public function validate($value);
}
$validators[] = new StringLengthValidator(2, 10);
$validators[] = new NotNollValidator();
$validators[] = new UsernameDoesNotExistValidator();
This has multiple advantages over the implementation using arrays:
You can document them (very important), phpdoc cannot parse comments for array keys.
Your code becomes typo-safe (array('reqiured' => true))
It is fully OO and does not introduce new concepts
It is more readable (although much more verbose)
The implementation of each constraint can be found intuitively (it's not in a 400-line function, but in the proper class)
EDIT: Here is a link to an answer I gave to a different question, but that is mostly applicable to this one as well.
Since using OO it would be cleaner if you used classes for validating properties. E.g.
class StringProperty
{
public $maxLength;
public $minlength;
public $required;
public $value;
function __construct($value,$maxLength,$minLength,$required)
{
$this->value = $value;
$this-> maxLength = $maxLength;
$this-> minLength = $minLength;
$this-> required = $required;
}
function isValidat()
{
// Check if it is valid
}
function getValidationErrorMessage()
{
}
}
$this->name = new StringProperty($namefromparameter,10,2,true);
if(!$this->name->isValid())
{
$validationMessage = $this->name-getValidationErrorMessage();
}
Using a class has the advantage of encapsulating logic inside of it that the array (basically a structure) does not have.
Maybe get inspired by Zend-Framework Validation.
So define a master:
class BaseValidator {
protected $msgs = array();
protected $params = array();
abstract function isValid($value);
public function __CONSTRUCT($_params) {
$this->params = $_params;
}
public function getMessages() {
// returns errors-messages
return $this->msgs;
}
}
And then build your custom validators:
class EmailValidator extends BaseValidator {
public function isValid($val=null) {
// if no value set use the params['value']
if ($val==null) {
$val = $this->params['value'];
}
// validate the value
if (strlen($val) < $this->params['maxlength']) {
$this->msgs[] = 'Length too short';
}
return count($this->msgs) > 0 ? false : true;
}
}
Finally your inital array could become something like:
$this->name = new EmailValidator(
array(
'maxlength' => 10,
'minlength' => 2,
'required' => true,
'value' => $namefromparameter,
),
),
);
validation could then be done like this:
if ($this->name->isValid()) {
echo 'everything fine';
} else {
echo 'Error: '.implode('<br/>', $this->name->getMessages());
}

Categories