ORM을 사용하는 앱으로 단위 테스트를 어떻게합니까?
-
21-08-2019 - |
문제
단위 테스트에 대한 다양한 질문을 살펴 보았지만이 질문에 구체적으로 답변하는 질문을 찾을 수 없습니다.
다음과 같은 기능을 포함하는 여러 PHP 클래스가 있습니다.
static function _setSuspended($Suspended, $UserID)
{
try {
$con = Propel::getConnection();
$c1 = new Criteria();
$c1->add(DomainsPeer::USERID,$UserID);
$update = new Criteria();
$update->add(DomainsPeer::SUSPENDED,$Suspended);
BasePeer::doUpdate($c1, $update, $con);
return true;
} catch(PropelException $e) {
return $e->getMessage();
}
}
Propel을 내 ORM으로 사용하고 있습니다. 나는 'mocks'와 'stubs'를 만드는 것에 대해 이야기하는 다양한 단위 테스트 주제를 읽었지만 위와 같은 기능을 테스트하는 방법을 구체적으로 알려주는 것을 찾을 수 없었습니다.
내 생각은 다음과 같습니다. 위의 기능을 테스트해야한다고 부르고 싶습니다. 그러나 내가 그것을 부르면, 그것은 propel을 ORM으로 사용하고 단위 테스트 원칙에 따라 각 기능을 그 자체로 분리해야합니다.
나는 그렇게 할 방법이 보이지 않습니다. 내가 여기서 무엇을 놓치고 있습니까?
해결책
ORM 구성이 테스트되지 않기 때문에 ORM을 조롱하는 것은 나에게 자신감을주지 않는다는 것을 알았습니다. Orms는 또한 거리 효과에서 많은 조치를 취하여 단위 테스트로 잘못된 신뢰를 줄 수 있습니다. 데이터베이스 드라이버를 조롱하거나 대체 인 메모리 데이터베이스를 제공하면 내 코드가 정확하고 ORM을 조롱하는 것만 큼 어려운 확신이 훨씬 높아집니다.
SQLITE는 단위 테스트를위한 훌륭한 메모리 데이터베이스입니다. PDO 지원 데이터베이스 목록에 있습니다. (PDO는 Propel 1.3 데이터베이스 드라이버입니다.) 메모리 인 데이터베이스를 사용하지 않으려면 이미 작성된 PDO 모의를 찾을 수 있습니다.
다른 팁
이것은 Propel에 전혀 익숙하지 않고 PHP에 대해 다소 익숙하다는 일반적인 대답입니다. 기본 답은 종속성 주입을 사용한다는 것입니다. ORM을 직접 참조하는 대신 주변의 래퍼를 만듭니다. 그런 다음 래퍼를 클래스/기능에 주입하여 실제로 사용합니다. 단위 테스트를 수행하려면 ORM과의 인터페이스가 아니라 래퍼에서 메소드 호출에 대한 응답을 구성 할 수있는 모의 또는 가짜 버전의 래퍼를 만듭니다. 이렇게하면 기능을 테스트 할 때 ORM을 고려할 수 있습니다.
나는 구축하는 동안 같은 문제를 해결하려고 노력했다. Symfony 용 PhPunit 플러그인. 나는 그것을 비슷하게 접근하게되었다 장고의 테스트 프레임 워크 - 별도의 데이터베이스/연결을 사용하고 모든 테스트 전에 파괴 및 재 구축하십시오.
나는 또한 테스트 실행에서 첫 번째 테스트 전에 테스트 데이터베이스를 재건 할 수 있다는 것을 알았습니다 (또는 테스트가 명시 적으로 지시하는 경우). 다른 테스트 전에는 모든 데이터를 삭제하여 속도를 약간 빠르게 삭제합니다.
나는 읽고 있었다 Misko Hevery의 테스트에 대한 블로그 최근에 많이. 이 상황을 다룹니다. DI (종속성 주입)를 사용해야합니다.
나는 이것도 조금만 고군분투하고 있으며 Propel도 사용합니다.
우선, "삭제"방법을 피어보다는 "객체"클래스로 이동할 수 있습니다. 어쨌든이 특정 함수의 경우이를 달성하기 위해 정적 메소드를 사용할 필요가 없습니다. API는 다음과 같습니다.
MyObjectPeer::retrieveByPK(1)->suspend();
이것은 정상적인 단위 테스트 방법을 통해 테스트 할 수 있습니다.
실제로 테스트 해야하는 데이터베이스라면 Afaik은 실제로 테스트에 DB를 사용해야합니다. 현재 프로젝트에서 Ltree와 Postgis를 많이 사용하고 있으며 테스트에 포함시키는 것 외에 DB에 의존하는 모델 로직에 대한 단위 테스트를 실행하는 다른 방법을 생각할 수 없습니다.
이것은 단위로 테스트 할 수없는 단단한 의존성을 가진 클래스의 예입니다.
다른 데이터베이스와 연결하여 테스트 할 수 있지만 더 이상 단위 테스트가 아니라 통합 테스트입니다.
내가 생각하는 더 나은 대안은 필요한 모든 다른 방법을 감싸는 쿼리 클래스를 갖는 것입니다. 그러면 그것을 조롱 할 수 있습니다.
먼저 먼저 인터페이스를 만듭니다
interface iQueryFactory
{
function firstFunction($argument);
function secondFunction($argument, $argument2);
}
우리가 필요로하는 모든 ORM 요청이있는 QueryFactory
class QueryFactory implements iQueryFactory
{
function firstFunction($argument)
{
// ORM thing
}
function secondFunction($argument, $argument2)
{
// ORM stuff
}
}
쿼리 팩토리 주입과 함께 비즈니스 로크가 있습니다.
class BusinessLogic
{
protected $queryFactory;
function __construct($queryFactoryInjection)
{
$this->queryFactory= $queryFactoryInjection;
}
function yourFunctionInYourBusinessLogique($argument, $argument2)
{
// business logique
try {
$this->queryFactory->secondFunction($argument, $argument2);
} catch (\Exception $e) {
// log
// return thing
}
// return stuff
}
}
Mock Part, My My My My My EveMple에 Mock Framework를 사용하지 않는다는 점에 유의하십시오 (BTW 응답 세터를 만들 수 있음).
class QueryFactoryMock implements iQueryFactory
{
function firstFunction($argument)
{
if (is_null($argument))
{
throw new \Exception("");
}
else
{
return "succes";
}
}
function firstFunction($argument, $argument2)
{
// sutff
}
}
마지막으로 Mock 구현으로 비즈니스 로직을 테스트하는 단위 테스트
class BusinessLogicTest extends PHPUnit_Framework_TestCase
{
public function setUp()
{
require_once "BusinessLogic.php";
}
public function testFirstFunction_WhenInsertGoodName()
{
$queryMockup = new QueryFactoryMock();
$businessLogicObject = new BusinessLogic($queryMockup);
$response = $businessLogicObject ->firstFunction("fabien");
$this->assertEquals($response, "succes");
}
public function testFirstFunction_WhenInsetNull()
{
$queryMockup = new QueryFactoryMock();
$businessLogicObject = new BusinessLogic($queryMockup);
$response = $businessLogicObject->firstFunction(null);
$this->assertEquals($response, "fail");
}
}