답변:
데이터베이스에 대한 호출을 조롱하는 것이 좋습니다. Mocks는 기본적으로 호출자가 사용할 수있는 동일한 속성, 메소드 등이 있다는 의미에서 메소드를 호출하려는 오브젝트와 유사한 오브젝트입니다. 그러나 특정 메소드가 호출 될 때 프로그래밍 된 작업을 수행하는 대신 모두 건너 뛰고 결과를 반환합니다. 이 결과는 일반적으로 미리 정의됩니다.
조롱 할 객체를 설정하려면 다음 의사 코드와 같이 일종의 반전 제어 / 종속성 주입 패턴을 사용해야합니다.
class Bar
{
private FooDataProvider _dataProvider;
public instantiate(FooDataProvider dataProvider) {
_dataProvider = dataProvider;
}
public getAllFoos() {
// instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
return _dataProvider.GetAllFoos();
}
}
class FooDataProvider
{
public Foo[] GetAllFoos() {
return Foo.GetAll();
}
}
이제 단위 테스트에서 FooDataProvider의 모형을 작성하면 실제로 데이터베이스에 도달하지 않고도 GetAllFoos 메소드를 호출 할 수 있습니다.
class BarTests
{
public TestGetAllFoos() {
// here we set up our mock FooDataProvider
mockRepository = MockingFramework.new()
mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);
// create a new array of Foo objects
testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}
// the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
// instead of calling to the database and returning whatever is in there
// ExpectCallTo and Returns are methods provided by our imaginary mocking framework
ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)
// now begins our actual unit test
testBar = new Bar(mockFooDataProvider)
baz = testBar.GetAllFoos()
// baz should now equal the testFooArray object we created earlier
Assert.AreEqual(3, baz.length)
}
}
간단히 말해서 일반적인 조롱 시나리오. 물론 실제 데이터베이스 호출도 단위 테스트하고 싶을 것입니다.
이상적으로는 객체가 영구적으로 무지해야합니다. 예를 들어, 요청을하고 개체를 반환하는 "데이터 액세스 계층"이 있어야합니다. 이렇게하면 해당 부분을 단위 테스트에서 제외하거나 별도로 테스트 할 수 있습니다.
객체가 데이터 계층에 단단히 연결되어 있으면 적절한 단위 테스트를 수행하기가 어렵습니다. 단위 테스트의 첫 번째 부분은 "단위"입니다. 모든 장치는 독립적으로 테스트 할 수 있어야합니다.
내 C # 프로젝트에서 NHibernate를 완전히 별도의 데이터 레이어와 함께 사용합니다. 내 개체는 핵심 도메인 모델에 있으며 내 응용 프로그램 계층에서 액세스합니다. 응용 프로그램 계층은 데이터 계층과 도메인 모델 계층 모두와 통신합니다.
응용 프로그램 계층을 "비즈니스 계층"이라고도합니다.
PHP를 사용하는 경우 데이터 액세스 전용 으로 특정 클래스 세트를 작성하십시오 . 객체가 어떻게 유지되는지 전혀 모르고 응용 프로그램 클래스에서 두 객체를 연결하십시오.
또 다른 옵션은 조롱 / 스텁을 사용하는 것입니다.
데이터베이스 액세스로 오브젝트를 단위 테스트하는 가장 쉬운 방법은 트랜잭션 범위를 사용하는 것입니다.
예를 들면 다음과 같습니다.
[Test]
[ExpectedException(typeof(NotFoundException))]
public void DeleteAttendee() {
using(TransactionScope scope = new TransactionScope()) {
Attendee anAttendee = Attendee.Get(3);
anAttendee.Delete();
anAttendee.Save();
//Try reloading. Instance should have been deleted.
Attendee deletedAttendee = Attendee.Get(3);
}
}
이렇게하면 트랜잭션 롤백과 같은 데이터베이스 상태가 되돌려 져 부작용없이 원하는만큼 테스트를 실행할 수 있습니다. 우리는이 방법을 대규모 프로젝트에서 성공적으로 사용했습니다. 우리 빌드는 약간의 시간이 걸리지 만 (15 분) 1800 단위 테스트를하는 것은 끔찍한 일이 아닙니다. 또한 빌드 시간이 중요한 경우 빌드 프로세스를 변경하여 여러 빌드를 만들 수 있습니다. 하나는 src 빌드를위한 것이고 다른 하나는 단위 테스트, 코드 분석, 패키징 등을 처리하는 나중에 발생합니다.
수많은 "비즈니스 로직"SQL 작업이 포함 된 중간 계층 프로세스의 단위 테스트를 시작했을 때 경험을 맛볼 수 있습니다.
먼저 합리적인 데이터베이스 연결을 "슬롯 인"할 수있는 추상화 계층을 만들었습니다 (이 경우 단일 ODBC 유형 연결 만 지원했습니다).
이것이 완료되면 코드에서 다음과 같은 작업을 수행 할 수있었습니다 (C ++로 작업하지만 아이디어를 얻었을 것입니다).
GetDatabase (). ExecuteSQL ( "INSERT INTO foo (blah, blah)")
정상적인 런타임에 GetDatabase ()는 ODBC를 통해 데이터베이스에 직접 모든 SQL (쿼리 포함)을 공급 한 객체를 반환합니다.
그런 다음 메모리 내 데이터베이스를 살펴보기 시작했습니다. 장기적으로는 SQLite 인 것 같습니다. ( http://www.sqlite.org/index.html ). 설정하고 사용하는 것은 매우 간단하며 서브 클래스와 GetDatabase ()를 재정 의하여 수행 된 모든 테스트에 대해 생성 및 파괴 된 메모리 내 데이터베이스로 SQL을 전달할 수있었습니다.
우리는 여전히 초기 단계에 있지만 지금까지는 좋아 보이지만 필요한 테이블을 작성하고 테스트 데이터로 채우도록해야합니다. 우리를 위해이 모든 것을 할 수있는 일반적인 도우미 함수 집합입니다.
전반적으로 TDD 프로세스에 큰 도움이되었습니다. 특정 버그를 수정하기 위해 매우 무해한 변경 사항을 만드는 것은 SQL / 데이터베이스의 특성으로 인해 시스템의 다른 (감지하기 어려운) 영역에 매우 이상한 영향을 미칠 수 있기 때문입니다.
분명히, 우리의 경험은 C ++ 개발 환경을 중심으로 이루어졌지만 PHP / Python에서 비슷한 작업을 할 수 있다고 확신합니다.
도움이 되었기를 바랍니다.
xUnit Test Patterns 책 은 데이터베이스에 충돌하는 단위 테스트 코드를 처리하는 몇 가지 방법을 설명합니다. 나는 당신이 느리기 때문에 이것을하고 싶지 않다고 말하는 다른 사람들과 동의하지만 언젠가는해야합니다 .IMO. 더 높은 수준의 내용을 테스트하기 위해 db 연결을 모의하는 것이 좋지만 실제 데이터베이스와 상호 작용하기 위해 수행 할 수있는 작업에 대한 제안은이 책을 확인하십시오.
당신이 가진 옵션 :
데이터베이스를 주입하십시오. (의사 Java의 예이지만 모든 OO 언어에 적용됨)
클래스 데이터베이스 { 공개 결과 쿼리 (문자열 쿼리) {... real db here ...} }이제 프로덕션 환경에서는 일반 데이터베이스를 사용하고 모든 테스트에는 임시로 만들 수있는 모의 데이터베이스를 삽입하기 만하면됩니다.MockDatabase 클래스는 데이터베이스 { 공개 결과 쿼리 (문자열 쿼리) { "모의 결과"를 반환; } }
ObjectThatUsesDB 클래스 { 공용 ObjectThatUsesDB (데이터베이스 db) { this.database = db; } }
User
하는 (즉 , 튜플 대신 리턴하는) "데이터베이스"오브젝트를 작성하십시오. {name: "marcin", password: "blah"}
임시로 생성 된 실제 오브젝트로 모든 테스트를 작성하고이 변환을 확인하는 데이터베이스에 따라 하나의 큰 테스트를 작성하십시오. 작동합니다.물론 이러한 접근 방식은 상호 배타적이지 않으며 필요에 따라 혼합하여 사용할 수 있습니다.
프로젝트의 응집력이 높고 커플 링이 느슨하면 데이터베이스 액세스 단위 테스트가 쉬워집니다. 이 방법으로 모든 특정 클래스가 한 번에 모든 것을 테스트하지 않고도 수행하는 것만 테스트 할 수 있습니다.
예를 들어, 사용자 인터페이스 클래스를 단위 테스트하는 경우 작성하는 테스트는 해당 함수의 비즈니스 논리 나 데이터베이스 조치가 아니라 UI 내부의 논리가 예상대로 작동하는지 확인해야합니다.
실제 데이터베이스 액세스를 단위 테스트하려면 실제로 네트워크 스택과 데이터베이스 서버에 의존하기 때문에 더 많은 통합 테스트가 필요하지만 SQL 코드가 요청한 내용을 수행하는지 확인할 수 있습니다. 하다.
개인적으로 나를 위해 단위 테스트의 숨겨진 힘은 응용 프로그램이없는 것보다 훨씬 더 나은 방식으로 응용 프로그램을 설계하도록 강요했다는 것입니다. 이것은 "이 기능이 모든 것을 수행해야한다"라는 사고 방식에서 벗어나는 데 실제로 도움이 되었기 때문입니다.
죄송합니다. PHP / Python에 대한 특정 코드 예제가 없지만 .NET 예제를 보려면 이 동일한 테스트를 수행하는 데 사용한 기술을 설명하는 게시물 이 있습니다.
나는 일반적으로 객체 테스트 (그리고 ORM이있는 경우)와 db 테스트 사이에 테스트를 중단하려고합니다. 나는 데이터 액세스 호출을 조롱하여 사물의 측면을 테스트하는 반면, 내 경험으로는 일반적으로 상당히 제한된 db와의 객체 상호 작용을 테스트하여 사물의 db 측면을 테스트합니다.
나는 데이터 액세스 부분을 조롱하기 시작할 때까지 단위 테스트 작성에 좌절 했으므로 테스트 데이터베이스를 만들거나 테스트 데이터를 즉시 생성 할 필요가 없었습니다. 데이터를 조롱하면 런타임에 모든 데이터를 생성 할 수 있으며 객체가 알려진 입력에서 올바르게 작동하는지 확인할 수 있습니다.
PHP 에서이 작업을 한 적이 없으며 Python을 사용한 적이 없지만 데이터베이스에 대한 호출을 조롱하는 것이 좋습니다. 이를 위해 타사 툴이든 직접 관리하든 IoC 를 구현할 수 있으며 가짜 호출의 결과를 제어 할 수있는 모의 버전의 데이터베이스 호출자를 구현할 수 있습니다.
인터페이스에 코딩하는 것만으로 간단한 형태의 IoC를 수행 할 수 있습니다. 이것은 코드에서 어떤 종류의 객체 방향이 필요하므로 수행중인 작업에 적용되지 않을 수 있습니다 (필자는 PHP와 Python에 대한 언급이므로)
검색 할 용어가없는 경우 도움이 되길 바랍니다.
첫 번째 게시물에 동의합니다-데이터베이스 액세스는 인터페이스를 구현하는 DAO 계층으로 제거해야합니다. 그런 다음 DAO 계층의 스텁 구현에 대해 논리를 테스트 할 수 있습니다.