The unit test of the database, look carefully :)

zhaozj2021-02-16  110

Simplified Database Unit Testing Using Enterprise Services

By Roy Osherove

Summary:

In this article I'll show what problems we encounter today when we perform unit tests against database related component, and the various ways used to solve these problems. I'll also show a new method to solve these problems, which is far easier and simpler than any of the others. At the end of this article you will have the tools necessary to do simple and easy unit tests against database related components without the need to write messy cleanup code after your tests.

Introduction

Test Driven Development makes life easier. Unit tests are the lifeblood of a good Test Driven Development approach (and thus, are also the lifeblood of most of the agile methodologies out there such as XP, Scrum and others). Therefore it stands to reason that one should strive to create unit tests with the least amount of work and maintenance, which will in turn allow the developer to focus on the business problem at hand and the various design and algorithms needed to create a working product / component.

For the past couple of years I've been struggling with Unit tests, That is, I've used them in major production projects and have seen the good and the bad that can come from a fully test Driven approach. I'll let the "good" part be taken care of by the numerous other articles out there that talk about the benefits you get from Unit Tests and TDD, and will focus on one of the most problematic parts of Unit tests in a real world project - testing against the Database.

Testing Data Access Layer Components (Dals for Short) Is Problement In a Number of Ways. The Number One Reason F IS THIS:

Testing the various CRUD (Create, Retrieve, Update, Delete) operations on a single class can result in a massive amount of "garbage" data residing on the test database (You are using a test database, are not you?). This Creates Several Problems: 1. Problem: GARBAGE DATA

Your Database Will Eventually Be Full of Junk Data Which You'll Need To Get Rid of Sooner or Later

2. Problem: Affecting Other Tests

Worst, you may break one of the golden rules of unit testing:. No test should be dependent on another test to perform correctly That is, you should be able to run your tests in any order you choose or maybe just one of them at a That, And The Should All Still Work. That Means That Every Test Needs to Start With A Known State On Which It Will Act. For Dals This Usually Means A Known State of Records in The Database.

But guess what? When you test CRUD operations you're actually changing the state of the database, so the next test that will be run that might depend on a specific record being there might fail because you just deleted that record in your current test. Anything can happen.

3. Problem: Starting Tests from a known stat

This is the other side of the previous problem: My test needs to have something in the database in order to perform correctly For example - it needs a specific "category" entity in the Category tables in order to test inserting a child "Product". INTO The Products Table. I Need To make Sure I Insert The Values ​​Into The Database Before I Perform The Test, But I Also Want To Make Sure That Data Is No Longer The After i finish my test.

DEALING WITH THE Problems

So how do you deal with all this garbage data that's making your database and tests unstable Obviously, you need to make sure that before and after each test you leave the database in the same state that you got it in That is -?. You need To "undo" your crred operations after testing them. Here Aread of the MOST Prevalent Methods Used to Achieve this: Remove It Manually

This is the most obvious and in simple cases one of the simplest to achieve. To do this, after each test (at the end of it) you execute the opposite actions than the ones you took in the test. For example, if you inserted A Record in the test - you delete it at the end.

Some Problems and Questions That Arise from this approach:

o If I inserted the record into the Database using my objects' Insert () method which I was testing - should I remove it from the database using the object's Delete () method (which might have not been tested yet or may not even exist yet !) OR SHOULD I Do IT AGAINST THE DATABASE USING DIRECT CALLING OF Stored Procedures Or Direct ADO.NET CLASSES?

Oiff i choos remove the records i INSERTED DIRECTLY From - this Involves Some Serious Amounts of extra Code, Residing in My Test! and That Code Can Have Bugs as Well.

oness ia choose to use my Object to delete and INSERT AS Well - What do I Test First? (chicken and the egg kind of question ..)

O I Can Also Choose To Remove All The Garbage Data At The end of all the testeardown method. But Then I'd NEED LOTS of Data And Custom Code and .... It's just completed.

o I can ignore all my garbage data and restore my database from backup before running all the unit tests. This might solve the problem partially but may still leave a few unit tests that mess with other test's data. A big no-no.Use the Transaction Object in ADO.NET

In his very interesting and insightful book, "Test Driven Development with Microsoft.Net", James Newkirk addresses this same problem with a seemingly simply solution: Execute all your actions inside a transaction and rollback that transaction when you're done with each test. This Is A Great Solution and Can Definitely Be Implement, But Also Has ITS Problems:

You need to worry about components that manage their own inner transaction but still will need to use outside transactions initiated by the unit tests. To workaround this problem James came up with an implementation of the Command pattern inside the unit tests (the book has a good Example). HOWEVER-Even IT ACCOMPLISHES The Task Nicely, After Using this Pattern We are Left with more complicated unit Tests Test We stay Have, And All this Just to Be Aable To Support Rollback Functionality.

A Simpler Way

The previous two approaches are the most used, and if you've used either of them you'd know that they are still hard to achieve with massive amount of custom work just to make the database data more reliable.

When I Was Reading Jame's Book, In The Chapter That Discusses Implementing Transactions, i Came Across this Little Sentence (abbreviated):

"We Want to Achieve The Same FunctionAlity As" RequireSTransaction "for Each Tested Component, Just Like In COM "

This sentence triggered an interesting thought: why work so hard Why not let COM (Or, "Enterprise Services" in .Net) do the work of coordinating the transactions for us Thus - I came up with another way of implementing database rollback functionality?? : Using Enterprise Services

What we want is to be able to achieve the transaction functionality without all the overhead of writing our own custom Transaction manager class as prescribed in James' book. How do we do that? With COM Enterprise services. Here is the technical gist of the method :

Our Simple Efforts Start With this Simple Base Class

Using system;

Using nunit.framework;

Using system.enterprises;

Namespace TransactionTesting

{

[TestFixTure]

[Transaction (TransactionOption.Required)]

Public Class DatabaseFixTure: ServicesDComponent

{

[Teardown]

Public void Transactionteardown ()

{

ContextUtil.isintransaction

{

Contextutil.Setabort ();

}

}

}

}

Here Are Some Important Pointers About this class:

.................................. ..

For That End:

It inherits from ServicedComponent It Has The Following Attributes on It:

[TestFixTure] [Transaction (TransactionOption.Required)]

...................................... ...Claizers.

IT HAS A Simple Teardown Method Which Will Call ContextUtil.Setabort (). THIS Causes The Following Flow To Occur:

1. Before Each Test A Transaction Context Is Opened (or Enlisted Into An EXISTING TRANSAACTION) 2. The Test Is Performed

3. Teardown Is Called and The Transaction Is Rolled Back THUS

We get The Following Benefits from this approach:

1. IT is dead simple and easy to import

2. You NEED No Code Whatsoever To Roll Back Any Database Changes Your Components Have Made THROUGHOUT The Test. They Will Be Rolled Back Automatic.

3. You do not need................

A few more settings and we're done

We're STILL NOT FINISHED But're Awfully Close To The Finish Line.

All Servic Components Need To Have Strong Names. This One is no exception.

Use the sn.exe Tool with the -k option to create a key file. In your test project open assemblyinfo.cs and set the foll item attribute (add it is is not there):

[assembly: assemblykeyKeyfile (@ "../../../ Test.snk"]]

Notice that I've put here a relative file path. This is the file I generated from sn.exe and it is located in the same directory as my .sln file. That way I can put this same attribute on all the other projects that NEED IT AS Well.

PUT A Static Version Attribute In your test project:

Look for this in the asseminfo.cs file: [assembly: assembly "] and change it inTo [Assembly: assemblyversion (" 1.0.0 ")] or wherever version you like.

This step is important because by default when the attribute is at its default state Visual Stuido.Net will automatically increment the assembly version with each build. That's a bad practice to have on any project, but when you're dealing with a COM component ( A ServicedComponent is just that) you need to realize that for every separate version of the test assembly that you build, you will have created a different COM component in the COM catalogue of the testing machine SO -. after you've run the tests 50 times a day your COM catalog might turn out to look pretty scary and we do not want that. Setting the version to a fixed version saves us this little headache.Since your test assembly now has a strong name, every assembly that it references must Have a strong name as well. this Is Easily Done: Put this Same Attribute in Each of The Reference Projects Set in your test project.

That's Basically It. You're done.

Here is a simple example of a test fixture that derives from this class to achieve a perfectly transparent data rollback after each test This class tests a simple class that does category insert and delete against the Northwind database.:

Using system;

Using nunit.framework;

Using system.data;

Using system.data.sqlclient;

Using Microsoft.ApplicationBlocks.data;

USING Northwinddal;

Namespace TransactionTesting

{

Public Class Categorytests: DatabaseFixTure

{

String conn = @ "server = localhost; database = northwind; trusted_connection = true";

[TEST]

Public void insert ()

{

CategoryManager mgr = new categoryManager ();

INT newid = mgr.insert ("test category");

VerifyRowexists (CONN, NewID, TRUE);

}

[TEST]

Public void delete ()

{

CategoryManager Mgr = new categoryManager (); int newid = mgr.insert ("test category");

Bool result = mgr.delete (newid);

AskERT.ISTRUE (RESULT);

VerifyRowexists (CONN, NewID, False);

}

Private void verifyroWISTS (String Connectionstring, Int EXIXID, BOOL ShouldExist)

{

String select = "select * from categories where categoryid =" existingrowid.tostring ();

SqlDataReader Dr = SQLHELPER.EXECUTEREADER (Connectionstring, CommandType.Text, SELECT);

Assert.Areequal (Shouldexist, Dr.hasRows);

Dr.close ();

}

}

}

As you can see there is no clean up code anywhere. That's How Simple This Technique Realy IS.

Just so you can see there no tricks up my sleep - here is the categoryManager test class (in vb.net - just for the heck of it):

Public Class CategoryManager

Private conn as string = "server = localhost; database = northwind; trusted_connection = true"

Public Function Insert (Byval CategoryName As String) AS INTEGER

DIM SQL AS STRING = "INSERT INTO CATEGORIES (CategoryName) VALUES ('" categoryName "); select scope_identity ()"

Dim Result as integer = northwinddal.data.sqlhelper.executescalar (conn, commandtype.text, sql)

Return RESULT

END FUNCTION

Public Function Delete (Byval ID As Integer) AS Boolean

DIM SQL AS STRING = "Delete from categories where categoryid =" id.tostring ()

Dim Result as integer = _

Northwinddal.data.sqlhelper.executenonQuery (conn, commandtype.text, sql)

Return (Result = 1)

END FUNCTION

END CLASS

No tricks, no hidden cards. The Database Actually Gets Updated, ONLY The Updates Are Rolled Back.

Conclusion

转载请注明原文地址:https://www.9cbs.com/read-10664.html

New Post(0)