Sono nuovo al test delle unità. Iniziato a lavorare sul test delle unità usando PHPUnit. Ma sembra che ci stia impiegando troppo tempo. Se considero che mi prendo 3 ore per scrivere una classe, sta prendendo le mie 7 ore per scrivere un caso di test per questo. Ci sono alcuni fattori dietro di esso.
- Come ho detto, sono nuovo di questa roba, quindi devo fare molta R & D.
- A volte mi viene confuso su cosa testare.
- Prendere in giro alcune funzioni richiede tempo.
- Ci sono molte permutazioni e combinazioni in una grande funzione, quindi diventa difficile e richiede molto tempo per prendere in giro queste funzioni.
Qualche idea su come scrivere casi di test in modo più veloce? Qualche idea per il codice reale in modo che sia più veloce scrivere casi di test?
Quali sono le migliori pratiche che dovrei seguire nel mio codice qui sotto?
<?php namespace Api\Core;
use Api\Exceptions\APICoreException;
use Api\Exceptions\APITransformationException;
use Api\Exceptions\APIValidationException;
use CrmValidation;
use Component;
use DB;
use App\Traits\Api\SaveTrait;
use App\Traits\Api\FileTrait;
use App\Repositories\Contract\MigrationInterface;
use App\Repositories\Contract\ClientFeedbackInterface;
use Mockery\CountValidator\Exception;
use Api\Libraries\ApiResponse;
use App\Repositories\Contract\FileInterface;
use App\Repositories\Contract\MasterInterface;
use App\Traits\Api\ApiDataConversionTrait;
use ClientFeedback;
use MigrationMapping;
use Migration;
use ComponentDetail;
use FavouriteEditorCore;
/**
* Class ClientFeedbackCore
*
* @package Api\Core
*/
class ClientFeedbackCore
{
use SaveTrait, FileTrait, ApiDataConversionTrait;
/**
* @var array
*/
private $request = [];
/**
* @var
*/
private $migrationFlag;
/**
* @var string
*/
private $table = 'client_feedback';
/**
* @var MigrationInterface
*/
public $migrationRepo;
/**
* @var ClientFeedbackInterface
*/
public $clientFeedbackRepo;
/**
* @var MasterInterface
*/
public $masterRepo;
/**
* @var FileInterface
*/
public $fileRepo;
/**
* ClientFeedbackCore constructor.
*
* @param MigrationInterface $migrationInterface
* @param ClientFeedbackInterface $clientFeedbackInterface
* @param MasterInterface $masterInterface
* @param FileInterface $fileInterface
*/
public function __construct(
MigrationInterface $migrationInterface,
ClientFeedbackInterface $clientFeedbackInterface,
MasterInterface $masterInterface,
FileInterface $fileInterface
) {
$this->clientFeedbackRepo = $clientFeedbackInterface;
$this->migrationRepo = $migrationInterface;
$this->masterRepo = $masterInterface;
$this->fileRepo = $fileInterface;
}
/**
* @author pratik.joshi
*/
public function init()
{
$this->migrationFlag = getMigrationStatus($this->table);
}
/**
* @param $request
* @return array
* @author pratik.joshi
* @desc stores passed data into respective entities and then stores into migration tables. If any issue while insert/update exception is thrown.
*/
public function store($request)
{
if ($request == null || empty($request))
{
throw new APIValidationException(trans('messages.exception.validation',['reason'=> 'request param is not provided']));
}
$clientFeedbackId = $migrationClientFeedbackId = $favouriteEditorId = null;
$errorMsgWhileSave = null;
$clientFeedback = [];
$filesSaved = [];
$categoryNamesForFiles = [];
$operation = config('constants.op_type.INSERT');
$this->init();
if(
keyExistsAndissetAndNotEmpty('id',$request)
&& CrmValidation::getRowCount($this->table, 'id', $request['id'])
) {
$operation = config('constants.op_type.UPDATE');
}
//Step 1: set up data based on the operation
$this->request = $this->convertData($request,$operation);
//Step 2: Save data into repo, Not using facade as we cant reuse it, every facade will repeat insert update function
if ($operation == config('constants.op_type.INSERT'))
{
$clientFeedback = $this->insertOrUpdateData($this->request, $this->clientFeedbackRepo);
}
else if($operation == config('constants.op_type.UPDATE'))
{
$clientFeedback = $this->insertOrUpdateData($this->request, $this->clientFeedbackRepo,$this->request['id']);
}
if ( !keyExistsAndissetAndNotEmpty('client_feedback_id',$clientFeedback[ 'data' ]) )
{
throw new APICoreException(trans('messages.exception.data_not_saved'));
}
//If no exception thrown, save id
$clientFeedbackId = $clientFeedback[ 'data' ][ 'client_feedback_id' ];
//Step 3: prepare array for mig repo & save()
if($this->migrationFlag && $operation == config('constants.op_type.INSERT'))
{
$this->saveMigrationDataElseThrowException($this->table, $clientFeedback[ 'data' ][ 'client_feedback_id' ], 'client_feedback', $this->request['name']);
}
//If no exception thrown, save id
$paramsForFileSave = [
'entity_id' => $clientFeedbackId,
'entity_type' => $this->clientFeedbackRepo->getModelName(),
];
//Step 4: Save datainto file, Save job feedback files with params : files array to save, migration data for files
//The method prepareFileData will be called by passing multiple files, and some needed params for file which internally calls prepareData
//$filePreparedData will be in format : $filePreparedData['field_cf_not_acceptable_four'][0] => whole file array(modified)
$filePreparedData = $this->fileRepo->prepareFileData($this->request[ 'files' ], $this->masterRepo, $paramsForFileSave);
$filesSaved = $this->fileRepo->filesInsertOrUpdate($filePreparedData);
//If any file is not saved, it returns false, throw exception here
if($filesSaved == false)
{
throw new APICoreException(trans('messages.exception.data_not_saved'));
}
//Step 5: Save data for file in migra repo.
//For each file type and each file in it, loop, Check for insert data
if(getMigrationStatus('file') && array_key_exists('insert',$filesSaved) && count($filesSaved['insert']))
{
foreach ($filesSaved['insert'] as $singleFileSaved)
{
$fileId = $singleFileSaved['data']['file_id'];
$wbTitle = $filesSaved['extra'][$fileId];
$this->saveMigrationDataElseThrowException('file', $singleFileSaved['data']['file_id'], 'files', $wbTitle);
}
}
//We get created by or last modified by
$createdOrLastModifiedBy = keyExistsAndissetAndNotEmpty('created_by',$this->request) ? $this->request['created_by'] : $this->request['last_modified_by'];
//Calling FavouriteEditorCore as we want to save favorite or un-favorite editor
$favouriteEditor = FavouriteEditorCore::core(
$this->request[ 'component_id' ],
$this->request[ 'rating' ],
$this->request[ 'wb_user_id' ], $createdOrLastModifiedBy,
$this->request[ 'same_editor_worker' ]
);
if ( !issetAndNotEmpty($favouriteEditor[ 'data' ][ 'favourite_editor_id' ]) )
{
throw new APICoreException(trans('messages.exception.data_not_saved'));
}
//If no exception thrown, save id
$favouriteEditorId = $favouriteEditor[ 'data' ][ 'favourite_editor_id' ];
//repare array for mig repo & save()
if(getMigrationStatus('favourite_editor') && $operation == 'insert')
{
$this->saveMigrationDataElseThrowException('favourite_editor', $favouriteEditor[ 'data' ][ 'favourite_editor_id' ], 'favourite_editor', null);
}
// Check if any error while saving
$dataToSave = [
'client_feedback_id' => $clientFeedbackId,
'files' => keyExistsAndissetAndNotEmpty('extra',$filesSaved) ? array_keys($filesSaved['extra']) : null,
'favourite_editor' => $favouriteEditorId
];
//@todo : return standard response
// Return final response to the WB.
return [
'data' => $dataToSave,
'operation' => $operation,
'status' => ApiResponse::HTTP_OK,
'error_message' => isset($errorMsgWhileSave) ? $errorMsgWhileSave : null
];
}
/**
* @param $request
* @param $operation
* @return array
* @author pratik.joshi
*/
public function convertData($request,$operation)
{
if(
($request == null || empty($request)) ||
($operation == null || empty($operation))
)
{
throw new APIValidationException(trans('messages.exception.validation',['reason'=> 'either request or operation param is not provided']));
}
//If blank
echo ' >> request';echo json_encode($request);
echo ' >> operation';echo json_encode($operation);
//Normal data conversion
$return = $this->basicDataConversion($request, $this->table, $operation);
echo ' >> return after basicDC';echo json_encode($return);
//Custom data conversion
$return[ 'client_code' ] = $request[ 'client_code' ];
$return[ 'component_id' ] = $request[ 'component_id' ];
if (isset( $request[ 'rating' ] ) )
{
$return[ 'rating' ] = $request[ 'field_cf_rating_value' ] =$request[ 'rating' ];
}
//Add client feedback process status, in insert default it to unread
if($operation == config('constants.op_type.INSERT'))
{
$return[ 'processing_status' ] = config('constants.processing_status.UNREAD');
}
else if($operation == config('constants.op_type.UPDATE'))
{
//@todo : lumen only picks config() in lumen only, explore on how to take it from laravel
//if its set and its valid
$processing_status_config = array_values(config('app_constants.client_feedback_processing_status')); // Get value from app constant
if (isset( $request[ 'processing_status' ] ) && in_array($request['processing_status'],$processing_status_config))
{
$return[ 'processing_status' ] = $request[ 'field_cf_status_value' ] = $request[ 'processing_status' ] ;
}
}
//@todo : check for NO
if (isset($request[ 'same_editor_worker' ])) {
if($request[ 'same_editor_worker' ] == 'no')
{
$return[ 'wb_user_id' ] = null;
}
else
{
$return[ 'wb_user_id' ] = ComponentDetail::getLastWorkerId($request[ 'component_id' ]);
}
}
//Get job title and prepend with CF
$return[ 'name' ] = 'CF_'.Component::getComponentTitleById($request[ 'component_id' ]);
//@todo check with EOS team for params
$dataFieldValues = setDataValues(config('app_constants.data_fields.client_feedback'), $request);
// unset which field we are storing in column
$return[ 'data' ] = json_encode($dataFieldValues);
echo ' >> return '.__LINE__;echo json_encode($return);
echo ' >> request & return '.__LINE__;echo json_encode(array_merge($request, $return));
return array_merge($request, $return);
}
/**
* @param $crmTable
* @param $crmId
* @param $wbTable
* @param $wbTitle
* @return mixed
* @throws APICoreException
* @author pratik.joshi
*/
public function saveMigrationDataElseThrowException($crmTable, $crmId, $wbTable, $wbTitle)
{
$dataToSave = Migration::prepareData([
'crm_table' => $crmTable,
'crm_id' => $crmId,
'whiteboard_table' => $wbTable,
'whiteboard_title' => $wbTitle
]);
//Save into migration repo
$migrationData = $this->insertOrUpdateData($dataToSave, $this->migrationRepo);
if ( !keyExistsAndissetAndNotEmpty('migration_id',$migrationData[ 'data' ]) )
{
throw new APICoreException(trans('messages.exception.data_not_saved'));
}
return $migrationData[ 'data' ]['migration_id'];
}
}
// E test case
<?php
use Api\Core\ClientFeedbackCore;
use App\Repositories\Contract\MigrationInterface;
use App\Repositories\Contract\ClientFeedbackInterface;
use App\Repositories\Contract\FileInterface;
use App\Repositories\Contract\MasterInterface;
class ClientFeedbackCoreTest extends TestCase
{
public $mockClientFeedbackCore;
public $requestForConvertData;
public $returnBasicDataConversion;
public $operation;
public $convertedData;
public $mockMigrationRepo;
public $mockClientFeedbackRepo;
public $mockMasterRepo;
public $mockFileRepo;
public $clientFeedbackCore;
public $table;
public $saveFailedData;
public function setUp()
{
parent::setUp();
$this->requestForConvertData = [
'client_code' => 'SHBI',
'component_id' => '4556',
'same_editor_worker' => 'yes',
'created_by' => '83767',
'rating' => 'not-acceptable',
'files' =>
[
'field_cf_not_acceptable_four' =>
[
0 =>
[
'created_by' => '83767',
'status' => '1',
'filename' => 'manuscript_0115.docx',
'filepath' => 'sites/all/files/15-01-17/client_feedback/1484497552_manuscript_011512.docx',
'filemime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'filesize' => '116710',
'timestamp' => '1484497552',
],
],
],
];
$this->returnBasicDataConversion = [
'crm_table' => 'client_feedback',
'active' => true,
'last_modified_date' => '2017-03-30 11:21:23',
'created_date' => '2017-03-30 11:21:23',
'created_by' => '83767',
'last_modified_by' => '83767',
];
$this->convertedData = [
'client_code' => 'SHBI',
'component_id' => '4556',
'same_editor_worker' => 'yes',
'created_by' => '83767',
'rating' => 'not-acceptable',
'files' =>
[
'field_cf_not_acceptable_four' =>
[
0 =>
[
'created_by' => '83767',
'status' => '1',
'filename' => 'manuscript_0115.docx',
'filepath' => 'sites/all/files/15-01-17/client_feedback/1484497552_manuscript_011512.docx',
'filemime' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'filesize' => '116710',
'timestamp' => '1484497552',
],
],
],
'field_cf_rating_value' => 'not-acceptable',
'crm_table' => 'client_feedback',
'active' => true,
'last_modified_date' => '2017-03-30 11:21:23',
'created_date' => '2017-03-30 11:21:23',
'last_modified_by' => '83767',
'processing_status' => 'unread',
'wb_user_id' => 1131,
'name' => 'CF_SHBI350',
'data' => '{"field_cf_acceptable_one":null,"field_cf_acceptable_two":null,"field_cf_acceptable_four":null,"field_cf_outstanding_one":null,"field_cf_outstanding_two":null,"field_cf_acceptable_three":null,"field_cf_outstanding_three":null,"field_cf_not_acceptable_one":null,"field_cf_not_acceptable_two":null,"field_cf_not_acceptable_three":null,"field_cf_acceptable_same_editor":null,"field_cf_outstanding_same_editor":null}',
];
$this->table = 'client_feedback';
$this->saveFailedData =
[
'status' => 400,
'data' => null,
'operation' => 'insert',
'error_message' => 'data save failed error'
];
//Mocking start
$this->mockMigrationRepo = Mockery::mock(MigrationInterface::class);
$this->mockClientFeedbackRepo = Mockery::mock(ClientFeedbackInterface::class);
$this->mockMasterRepo = Mockery::mock(MasterInterface::class);
$this->mockFileRepo = Mockery::mock(FileInterface::class);
//Set mock of the Core class
$this->mockClientFeedbackCore = Mockery::mock(ClientFeedbackCore::class,
[$this->mockMigrationRepo,
$this->mockClientFeedbackRepo,
$this->mockMasterRepo,
$this->mockFileRepo])->makePartial();
//Set expectations
$this->mockClientFeedbackRepo
->shouldReceive('getModelName')->andReturn($this->table);
//For insert data
$this->mockClientFeedbackCore->shouldReceive('convertData')
->with($this->requestForConvertData, 'insert')
->andReturn($this->convertedData);
}
public function tearDown()
{
// DO NOT DELETE
Mockery::close();
parent::tearDown();
}
/**
* @test
*/
public function method_exists()
{
$methodsToCheck = [
'init',
'store',
'convertData',
];
foreach ($methodsToCheck as $method) {
$this->checkMethodExist($this->mockClientFeedbackCore, $method);
}
}
/**
* @test
*/
public function validate_convert_data_for_insert()
{
//Mock necessary methods
$this->mockClientFeedbackCore->shouldReceive('basicDataConversion')
->with($this->requestForConvertData, 'client_feedback', 'insert')
->andReturn($this->returnBasicDataConversion);
ComponentDetail::shouldReceive('getLastWorkerId')
->with($this->requestForConvertData[ 'component_id' ])
->andReturn(1131);
Component::shouldReceive('getComponentTitleById')
->with($this->requestForConvertData[ 'component_id' ])
->andReturn('SHBI350');
$actual = $this->mockClientFeedbackCore->convertData($this->requestForConvertData, 'insert');
$this->assertEquals($this->convertedData, $actual);
}
/**
* @test
*/
public function validate_convert_data_without_params()
{
$errorMessage = '';
try{
$this->mockClientFeedbackCore->convertData(null, null);
}
catch (Exception $e){
$errorMessage = $e->getMessage();
}
$this->assertEquals('API Validation Error: Reason: either request or operation param is not provided', $errorMessage);
}
/**
* @test
*/
public function validate_store_without_params()
{
$errorMessage = '';
try{
$this->mockClientFeedbackCore->store(null);
}
catch (Exception $e){
$errorMessage = $e->getMessage();
}
$this->assertEquals('API Validation Error: Reason: request param is not provided', $errorMessage);
}
/**
* @test
*/
public function validate_store_client_feedback_save_fail()
{
$errorMessage = '';
/* $this->mockClientFeedbackCore->shouldReceive('convertData')
->with($this->requestForConvertData, 'insert')
->andReturn($this->convertedData);*/
//For insert, mock separately
//@todo : with() attribute does not work here : ->with($this->convertedData,$this->mockClientFeedbackRepo)
$this->mockClientFeedbackCore->shouldReceive('insertOrUpdateData')
->andReturn($this->saveFailedData);
try {
$this->mockClientFeedbackCore->store($this->convertedData);
} catch
(Exception $e) {
$errorMessage = $e->getMessage();
}
$this->assertEquals('MigrationError: Data not saved',
$errorMessage);
}
public function validate_store_migration_save_fail()
{
//saveMigrationDataElseThrowException
$this->mockClientFeedbackCore->shouldReceive('saveMigrationDataElseThrowException')
->with('crmTable', 123, 'wbTable', 'wbTitle')
->andReturn($this->saveFailedData);
try {
$this->mockClientFeedbackCore->store($this->convertedData);
} catch
(Exception $e) {
$errorMessage = $e->getMessage();
}
$this->assertEquals('MigrationError: Data not saved',
$errorMessage);
}
public function validate_store_file_save_fail()
{
}
public function validate_store_favourite_editor_save_fail()
{
}
public function validate_store_proper_save()
{
}
}
Aiutatemi perché sto superando le scadenze a causa del mancato completamento dei test in tempo.