views:

275

answers:

3

Hey,

I have googled this a little and didn't really find the answer I needed.

I am working on a webpage in C# with SQL Server and LINQ for a customer. I want the users to be able to send messages to each other. So what I do is that I unit test this with data that actually goes into the database.

The problem is that I now depend on having at least 2 users who I know the ID of. Furthermore I have to clean up after my self. This leads to rather large unit tests that test alot in one test.

Lets say I would like to update a user. That would mean that I would have to ceate the user, update it, and then delete it. This a lot of assertions in one unit test and if it fails with updating i have to manually delete it.

If I would do it any other way, without saving the data to DB, I would not for sure be able to know that the data was present in the database after updating etc.

What is the proper way to do this without having a test that tests a lot of functionality in one test?

+3  A: 

Have a fixtures file (in XML, SQL, YAML, whatever) that you can load into the local test database with just a command.

E.g. a fixtures file for two users who need to message each other may look like (this is mine):

Member:

  Member_WibWobble:
    username:       Wibble_Wobble
    email_address:  michael+dev_wibwob@[removed].com
    password:       pw
    is_super_admin: true
    last_login:     "2010-01-06 12:12:57"
    Country:        country_AU
    UploadImage:
      type:         <?php echo UploadImage::TYPE_MEMBER_AVATAR."\n"; ?>
      upload:       "http://localhost/[removed]/images/wibwobble.jpg"

  Member_BunnyHugs:
    username:       BunnyHugs
    email_address:  michael+dev_bunny@[removed].com
    password:       pw
    is_super_admin: true
    last_login:     "2009-12-01 14:11:11"
    Country:        country_UK
    UploadImage:
      type:         <?php echo UploadImage::TYPE_MEMBER_AVATAR."\n"; ?>
      upload:       "http://localhost/[removed]/images/bunnyhugs.jpg"

PrivateMessage:

  PrivateMessage_1:
    subject:          "Yo"
    body:         |
      hi

      <b>escape this html please</b>

      bye
    is_read:          false
    Sender:           Member_WibWobble
    Recipient:        Member_BunnyHugs
Coronatus
That's a good idea, especially if you use SQLite on another memory based database. That will provide you with much better performance, especially if you want to test with large sets of data.
Sandor Drieënhuizen
But wouldnt this mean that I would have to add this data to the DB to test it..?
Oskar Kjellin
@Kurresmack: Yes.
Chris Lively
Yes, but you would have a same-schema testing database in parallel to your production one for this kind of thing.
DaveE
+4  A: 

Do all the test inside of a System.Transactions.TransactionScope block, and simply do not call Scope.Complete() ... All changes will be rolled back when you exit, and other users will not be able to see the temporary data you create in the database, so the test process will only affect the test machine..

You can enter a new user, read the database to verify that it was entered correctly, and whatever else you need to check, within the block of the TransactionScope...

Have the single unit test method impersonate the two different users, all within the one transaction ...

e.g. code

   using (var scop = new System.Transactions.TransactionScope())
   {
       // all your test code and Asserts that access the database, 
       // writes and reads, from any class, ...
       // to commit at the very end of this block,
       // you would call
       // scop.Complete();  // ..... but don't and all will be rolled back
   }
Charles Bretana
Sound like what I need. Would this work like the data was in the DB, but I wouldn't commit? I am accessing the data in some classes and they will have to "think" that the data is in the DB
Oskar Kjellin
Other users *might* be able to see it if they play with the transaction isolation level. That's usually a deliberate action, though, so not really something to worry about.
DaveE
how does this work, is the data stored in the database meanwhile or is it stored in memory?
Oskar Kjellin
It uses the local (on the client) distributed Transaction coordinator, built in to the .Net framework, which uses two-phase commits with participating resource providers MSSql server included) to manage the Commits itself...
Charles Bretana
@Kurresmack, As long as you include the code from the other classes inside the same "block" (see edit to my answer) then yes, they will all "see" the data.
Charles Bretana
Thanks a lot for your answer
Oskar Kjellin
I keep getting " System.Data.SqlClient.SqlException: MSDTC on server xxxx is unavailable" when using TransactionScope. Read somewhere that it is because I have multiple data connections at one place?
Oskar Kjellin
To fix the problem just start the service “Distributed Transaction Coordinator” using Windows Service manager. Here are the detailed steps for starting the service1. Click on Start–>Control Panel->Administrative Tools->Services (or simply type services.msc in the run command box and hit enter); display “Services” manager 2. Scroll through the list and identify the service “Distributed Transaction Coordinator” 3. Right on the service and choose “Start”
Charles Bretana
When SQL Server and the Application Server are on different boxes:The above said solution work well when SQL Server and the Application are on the same box. If they are deployed on different boxes then additionally you should follow these steps to correct the problem1. Click Start–>Control Panel –> Adminstrative Tools –> Component Services –> Computers 2. Right click on My Computer and choose Properties option Switch to MS DTC tab and check allow remote access 3. Repeat these steps on Application Server and Database server
Charles Bretana
Read more: http://www.techdreams.org/microsoft/aspnet/how-to-fix-msdtc-on-server-server-name-is-unavailable-erroraspnetsql-server/311-20080912#ixzz0id1ukvCs
Charles Bretana
+2  A: 

Unit testing databases can be a PITA due to the very nature of them (persisted storage).

To do this right, you should always start with a blank schema. Then have your testing code load fill in the data values that have to be there. This can be as simple as just executing a sql script which contains the schema and default data.

Once that is done, then start running your tests. Tests should encompass all of the crud operations your app will normally do.

If any failure occurs, it's not that big a deal because, again, you should start fresh every time any how.

Also, you want to keep that data around in case of a testing failure so that you can inspect the database state at the time the failure occurred. So, this is both a good and bad thing.

Along these lines, the db tests should be run on it's own test database instance. This way you don't have to worry about transient issues like someone changing a db structure underneath you as the tests are progressing.

Ultimately, database testing is a project in and of itself.

Chris Lively