问题
I'm developing web application by using Laravel and this web app has to connect to internal service. I choose to do TDD in this project and now I'm facing with problem about too much dependencies to mock.
In RegisterController
, we allow user to login and upgrade their account type, see code below:
public function loginForUpgradeAccount() {
$data = Input::only('email', 'hashedPassword');
if (!empty($data['email']) && !empty($data['hashedPassword'])) {
$email = $data['email'];
$hashedPassword = $data['hashedPassword'];
$login = $this->createLoginServiceConnector();
$token = $login->withEmailAndHashedPassword($email, $hashedPassword);
if ($token) {
$profile = $this->createProfileServiceConnector($token);
if ($profile->getData()['secret_code'] != Session::get('ThirdPartyData')['secret_code']) {
return $this->buildErrorJsonResponse('UPGD-002', 'SecretCode not match with Third party', 400);
}
else {
if ($profile->getData()['profile_type'] == 'consumer')
return $this->buildErrorJsonResponse('UPGD-003', 'Profile type is consumer', 400);
else //has to call internal upgrade api
}
}
}
return $this->buildErrorJsonResponse('UPGD-001', 'Wrong email or password', 400);
}
public function createLoginServiceConnector() {
return new Login(ApiInvokerFactory::createApiJsonInvoker());
}
public function createProfileServiceConnector($token) {
return new Profile(ApiInvokerFactory::createApiJsonInvoker(), $token);
}
And example of test code as below:
public function test_loginForUpgrade_must_return_error_code_UPGD_002_when_secret_code_not_match_with_third_party_data() {
$data = array('email' => 'correct@example.com', 'hashedPassword' => '123333');
$profileData = array(
'secret_code' => 'abcdefg',
);
Session::put('ThirdPartyData', array('secret_code' => 'hijklmnop'));
Input::shouldReceive('only')
->with('email', 'hashedPassword')
->andReturn($data);
$login = Mockery::mock('Login');
/** @var $login RegisterController|\Mockery\MockInterface */
$ctrl = Mockery::mock('RegisterController');
$ctrl->shouldDeferMissing();
/** @var Profile||MockInterface $profile */
$profile = Mockery::mock('Profile');
$ctrl->shouldReceive('createLoginServiceConnector')
->andReturn($login);
$login->shouldReceive('withEmailAndHashedPassword')
->with($data['email'], $data['hashedPassword'])
->andReturn('correcttokenlogin');
$ctrl->shouldReceive('createProfileServiceConnector')
->with('correct_tokenlogin')
->andReturn($profile);
$profile->shouldReceive('getData')
->andReturn($profileData);
$result = $ctrl->loginForUpgrade();
$response = json_decode($result->getContent(), true);
$this->assertEquals('UPGD-002', $response['errorCode']);
}
This is only one example test case and I have very same code for other scenario in one controller action like wrong email or password and profile type is consumer and much more.
I think it hard to maintain messy test code like this and if someday our flow for upgrade is changed or require more condition to upgrade this would broke all of my test and I have to delete and rewrite all again.
Am I do unit testing and TDD correctly?
Please suggest me the right way.
回答1:
Am I do unit testing and TDD correctly?
Doing TDD wrong is hard. TDD is a process. You write test then you write code to fulfill test's requirements. Simple.
On the other hand, doing software design wrong is very easy. Sticking to SOLID principles, keeping your code loosely coupled, avoiding common anti-patterns and anticipating upcoming changes (software and requirements change all the time) is very, very hard. It requires constant, non-trivial and non-obvious decision making.
Unit tests, being first consumers of your code will be the first to highlight those design issues. Namely, they will be complex, hard to write and fragile. All those symptoms lead to single cause -- incorrect design decisions.
How can your code be improved to mitigate those problems? Take a closer look at:
loginForUpgradeAccount
-- is logging in specific to account upgrade? Or is it more general process? Can your users log in and not do an account upgrade? This may be first extraction point (ie.upgradeAccount(login)
orapplicationGateway->logIn(user)
)$this->buildErrorJsonResponse
-- is upgrading account really responsible for json response generation? This would be best fitted into parameterized factory of some sort (errorResponseFactory->createForAccountUpgrade(profileData)
)
Those are the kind of decisions you'll have to make. Unit test will help you spotting wrong decisions early.
来源:https://stackoverflow.com/questions/24447226/am-i-mock-too-much-or-doing-tdd-and-unit-testing-the-right-way