단위 테스트가 “완전히 훌륭하다”, “정말 멋지다”, “모든 좋은 점”이라고 들었지만 70 % 이상의 파일에 데이터베이스 액세스 (일부 읽기 및 일부 쓰기)가 포함되어 있지만 어떻게해야할지 모르겠습니다. 이러한 파일에 대한 단위 테스트를 작성합니다.
PHP와 Python을 사용하고 있지만 데이터베이스 액세스를 사용하는 대부분 / 모든 언어에 적용되는 질문이라고 생각합니다.
답변
데이터베이스에 대한 호출을 조롱하는 것이 좋습니다. 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 연결을 모의하는 것이 좋지만 실제 데이터베이스와 상호 작용하기 위해 수행 할 수있는 작업에 대한 제안은이 책을 확인하십시오.
답변
당신이 가진 옵션 :
- 단위 테스트를 시작하기 전에 데이터베이스를 지우고 미리 정의 된 데이터 세트로 db를 채우고 테스트를 실행하는 스크립트를 작성하십시오. 매 테스트 전에이를 수행 할 수도 있습니다. 속도는 느리지 만 오류가 발생하기 쉽습니다.
-
데이터베이스를 주입하십시오. (의사 Java의 예이지만 모든 OO 언어에 적용됨)
클래스 데이터베이스 { 공개 결과 쿼리 (문자열 쿼리) {... real db here ...} }
MockDatabase 클래스는 데이터베이스 { 공개 결과 쿼리 (문자열 쿼리) { "모의 결과"를 반환; } }
ObjectThatUsesDB 클래스 { 공용 ObjectThatUsesDB (데이터베이스 db) { this.database = db; } }
이제 프로덕션 환경에서는 일반 데이터베이스를 사용하고 모든 테스트에는 임시로 만들 수있는 모의 데이터베이스를 삽입하기 만하면됩니다.
- 대부분의 코드에서 DB를 전혀 사용하지 마십시오 (어쨌든 나쁜 습관입니다). 결과로 리턴하는 대신 일반 오브젝트를 리턴
User
하는 (즉 , 튜플 대신 리턴하는) “데이터베이스”오브젝트를 작성하십시오.{name: "marcin", password: "blah"}
임시로 생성 된 실제 오브젝트로 모든 테스트를 작성하고이 변환을 확인하는 데이터베이스에 따라 하나의 큰 테스트를 작성하십시오. 작동합니다.
물론 이러한 접근 방식은 상호 배타적이지 않으며 필요에 따라 혼합하여 사용할 수 있습니다.