2008.12.16 13:36

Writing unit tests to validate a data access layer (DAL) can be a time consuming (but life saving) task. One of the biggest challanges of DAL unit tests is assuring that you have consistent data to pull from the database. Dedicating a database with static data for unit tests doesn’t always work. As unit tests are added to the project, data may need to be added to the database. This can cause previously created tests to fail, and a lot of time can be lost trying to resync everything.

One technique for getting around the unit test database consistency issue is to write data to the database you expect to find. The steps for doing this would be:

  • Begin a transaction in your unit test.
  • Write the data to the database that your data access layer will need.
  • Test the data access layer functionality against the inserted data.
  • Roll back the transaction.

Using a transaction has a couple of distinct advantages. Since you are running in the scope of an uncommitted transaction, your fellow developers running unit tests will not see your added data (isolation). Also, rolling back the transaction places the database back to the original state in which you found it.

Managing transactions by explicitly attaching them to the connection object doesn’t always work well when testing a data access layer. Since the DAL often contains code to retrieve its own connection, the following sequence often occurs:

  • Begin a transaction in your unit test.
  • Write the data to the database that your data access layer will need.
  • Call the Data Access Layer. The Data Access layer creates its own connection.
  • The DAL attempts to read the new data from the database. It is blocked, though, because it is in a different transaction than the unit test transaction, and will not be able to complete until the DAL commits.

The TransactionScope object helps alleviate this problem. It sits on top of the Distributed Transaction Coordinator, and will assign any code running within its context to the same transaction. In other words, a TransactionScope context within your unit tests will force your data access layer code to run within the same context. Isolation is maintained from other developers, but your DAL can access and manipulate the data as needed. (This does require that you have the Distributed Transaction Coordinator Service running on the box that handles the transaction.)

To demonstrate, let’s assume that I have a DAL method that I want to test that returns all users from a database. This method gets its own connection, retrieves the users, and returns them as a DataSet.

public DataSet GetUsers()
{
DataSet dataSet = new DataSet();
string connectionString = GetConnectionString();

using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
string sql = "SELECT * FROM [User]";
SqlCommand command = new SqlCommand(sql, connection);
SqlDataAdapter adapter = new SqlDataAdapter(command);
adapter.Fill(dataSet);
}

return dataSet;
}

Here is the unit test to test this code. It doesn’t test nearly all of the functionality you would want to check in a real unit test, but it does demonstrate the TransactionScope. Note that the TransactionScope object doesn’t have an explicit RollBack() method. The Rollback occurs if the TransactionScope object is disposed without Complete() being called on it. This occurs at the end of the using block.

[TestMethod()]
public void GetUsersTest()
{
string connectionString = GetConnectionString();

using (TransactionScope ts = new TransactionScope())
{
using (SqlConnection connection =
new SqlConnection(connectionString))
{
connection.Open();
DataLayer dataAccessLayer = new DataLayer();

DataSet dataSet = dataAccessLayer.GetUsers();
AddNewUser("Fred", connection);

dataSet = dataAccessLayer.GetUsers();
DataRow[] dr = dataSet.Tables[0].Select("[UserName] = 'Fred'");
Assert.AreEqual(1, dr.Length);
}
}
}

Hope this is helpful. Good luck and code safe!

MW


[ 출처 : http://dotnetthoughts.wordpress.com/2007/11/10/transactionscope-and-unit-tests/ ]


-----------------------------------------------------


ASP.NET에서 단위 테스트를 진행하던중..

DataSet으로 얻어진 값을 비교하는 방법이 있을지 고민해봤다..

과연.. Assert 클래스로 DataSet의 값을 비교할 수 있을까?

간단하게 테스트해보았지만 역시나 안되었고..

MSDN을 참고해보니 역시 그런건 없어.. Assert 클래스는... 정말 간단한... 젠장..

여기저기 검색을 해보니까 얻어진 DataSet의 길이를 비교하는게 있더군..

역시 얻어진 값을 비교할 수는 없는건가보다..

간접적으로 그 크기(길이)가 같으니 같은 값을 가질것이라고 여기는 방법뿐인가?

물론 값의 일부가 같은지 비교도 가능하지만..

노가다로 전체 값을 다 비교해보던지..후후;

암튼... asp.net에서의 단위테스트의 한계를 점차 느끼고 있다..;;; ㄷㄷ;;


혹시 이 부분에 대해서 조언을 주실분이 계신가요??

Posted by 열라착한앙마

댓글을 달아 주세요

2008.11.07 08:50

▣ ASP.NET Unit Test (in Visual Studio Team System 2008 Development Edition)


[ ASP.NET Unit Test ]

 

* 웹 환경에서의 Unit Test는 극히 제한적으로 이루어질 수 밖에 없음

    + MSDN에서 ASP.NET 단위테스트는

         “.aspx 파일이나 App_Code 폴더 외의 다른 폴더의 코드에서는 테스트를 생성할 수 없습니다.“

       라고 설명하고 있다.

       따라서 이외의 테스트는 실행할 수 없다.

    + VSTS2008 Development Edition에서는 [단위테스트]외에 [웹 테스트],[부하 테스트], [수동 테스트], [제네릭 테스트], [순서가 있는 테스트]는 실행할 수 없다

        - VSTS 2008 Test Edition에서만 가능

 

* 단위 테스트의 작성 방법은 테스트의 초기 소스코드를 만드는 코드 생성 기능을 이용하는 방법과 직접 테스트 코드를 생성하는 방법이 있다.

    + 자동으로 코드를 생성하는 방법

        - 메뉴의 [테스트]-[새 테스트]를 이용하여 생성

        - 테스트를 수행할 메서드에서 Context Menu를 이용해 단위 테스트를 생성

    + 직접 테스트 코드 생성하는 방법

        - 테스트 프로젝트를 생성하거나 미리 생성된 테스트 프로젝트에서 Attribute를 포함한 테스트 코드를 작성하여 생성

 

* 단위 테스트의 개요

    + http://msdn.microsoft.com/ko-kr/library/ms182516.aspx

 

 

 

[ 간단한 예를 통한 ASP.NET에서의 Unit Test 방법 ]

 

1. 단위테스트를 수행하기 위해서는 먼저 Web Application이나 Web Site 프로젝트가 생성되어야 함

    + Web Application으로 생성하는 것을 권장(배포 및 관리적인 측면에서 Web Site보다 좋다고 함)

 

▶[그림 1] 테스트를 수행할 웹 페이지 UI

 

 

▶[그림 2] 테스트 대상 프로젝트

 

 

 

2. 단위 테스트를 위해서 Test 프로젝트 생성

 

▶[그림 3] 단위테스트의 생성

    + 자동으로 Test 프로젝트를 생성해줌

 

 

▶[그림 4] 단위 테스트를 만들 Method 및 환경 설정

    + 원하는 Method를 선택하면 해당 코드의 테스트코드가 생성됨

 

 

 

3. 환경을 설정함

 

▶[그림 5] LocalTestRun.testrunconfig를 실행한 테스트 실행 환경설정 :: IIS 에서 테스트 수행시

    + 메뉴의 [테스트]-[테스트 실행 구성 편집]으로 실행

    + 호스트 형식을 [기본값]으로 사용하면, VS2008에서 지원하는 Virtual IIS에서 테스트가 수행됨

    + IIS에서 테스트를 수행할때는 다음과 같이 설정

 

 

▶[그림 6] 테스트 코드에서의 설정

    + 테스트 코드 생성시 자동으로 생성되는 Attribute를 설정

        - IIS에서 테스트 수행시에는 [AspNetDevelopmentServerHost] 속성을 사용하지 않기 때문에 주석처리함

        - [UrlToTest]에는 실제 웹브라우저에서 확인가능한 페이지를 설정함(페이지명까지 설정하도록 함)

            :: 해당 페이지가 다른 페이지로 Redirect하는 경우 꼭 Redirect된 페이지가 웹브라우저에서 표시가 되는지 확인

            :: 가능하면 Redirect되는 페이지를 사용하지 않을 것을 권장

    + 테스트를 수행하기위한 값으로 초기화

        - 그림에서는 입력값에 각각 1과 출력값으로 3을 설정

 

 

 

4. 테스트를 실행

 

▶[그림 7] 실패하는 테스트 실행 결과(빨간색으로 표시)

 

 

▶[그림 8] 성공하는 테스트 실행 결과(녹색으로 표시)


[참고사항]

 

1. 테스트를 수행할 타겟이 protected나 private와 같은 멤버는 외부에서 접근할 수 없는 것이 옳다.

    하지만, 이로 인해 테스트를 수행하지 못한다면 의미가 없기때문에 VSTS에서는 Accessor를 이용하여 해당 멤버를 사용할 수 있도록 한다.

 

2. Attribute를 가진다.

    + 이 Attribute는 해당 테스트 메서드만을 위한 것이다. 

    + IIS 환경에서 테스트를 수행할때는 [AspNetDevelopmentServerHost]를 사용하지 않는다

    + [UrlToTest]는 웹브라우저에서 실제로 접근이 가능한 웹페이지까지의 Full URL을 설정한다.

        - 여기서 예에서 보이는 Default.aspx가 다른 페이지로 Redirect될 경우 해당 페이지가 웹 브라우저에서 잘 보이는지 꼭 확인해야 함.

테스트

코드의

Attribute

[TestMethod()]
[HostType("ASP.NET")]
[AspNetDevelopmentServerHost("E:\\inetpub\\Temp\\AddTemp\\AddTemp", "/")]
[UrlToTest("http://localhost/Default.aspx")]
[DeploymentItem("AddTemp.dll")]

 

3. 테스트 코드를 검증하기 위해서 [Break Point(F9)]를 사용할 수 없다.

    + 동일한 목적을 위해서는 System.Diagnostics.Debugger.Break(); 를 테스트 코드의 중단 위치에 입력하여 사용한다.

 

[참고자료]

 

1. 단위 테스트 작업 

    + http://msdn.microsoft.com/ko-kr/library/ms182515.aspx

 

2. How to: Debug while Running a Test in an ASP.NET Solution

    + http://msdn.microsoft.com/en-us/library/ms243172.aspx

 

3. Microsoft 고객지원

    + http://support.microsoft.com/

 

4. 개발 OS가 XP를 사용하면 웹 브라우저의 Session개수가 10개로 제한되어 테스트 수행시 오류를 발생할 수 있다.

    + [제어판]-[관리도구]-[성능]에서 [Current Connections] 정보로 확인 가능

 

 


Posted by 열라착한앙마

댓글을 달아 주세요

  1. BlogIcon 열라착한앙마 2008.11.11 16:58 신고  댓글주소  수정/삭제  댓글쓰기

    회사 지식게시판에 작성하고
    귀찮아서 그냥 복사해왔더니만..
    짤리네;;;