42 Comments

  • This is awesome Roy! Great use of ES, very insightful idea. I am a big fan of ES and we (IDesign) recommend its use to many of our customers. Now I have a great way to show them how it can help their TDD as well (with all due credit to the originator of the idea).



    Scha-weeet.

  • Excellent. Alexei and I were troubled by the TransactionManager class in the book and we thought there must be a better way. Thanks for pointing this out and for the great article.

  • Nice article, Roy! I'm one of those that think Enterprise Services doesn't get enough attention, and hopefully this helps bring it a little more to the forefront.

  • Thanks folks. I'm flattered that you like it.

    I'm still trying to find holes in my theory and "bugs" in it.

    Please let me know if you find any of those. I'll update the article accordingly.



    Thanks,

    Roy

  • Simple and effective



    This is a great solution Roy, I wonder if that can also be used against LDAP, i'll check it up...

  • Very Nice Roy !!

    (I was just wondering about the subject the other day...)

    A few questions:

    1. Can i use the same method for testing my BL / WF layers ?

    or is there a better test method for those layers...

    2. Does it matter if my DAL is ES or not ?

    3. you mentioned that "every assembly that it references must have a strong name as well", does it means my DAL needs a strong name ?

    (if its ES then of course it has, but what if its not ?...)

    Thanks,

    Ido.

  • Very nice indeed. I think that it's a fantastic way to do DB testing.



    One small aside, though - in your categoryManager code, your building sql strings dynamically which opens up the possibility of sql injection attacks. You should be using parameterized sql - either in sprocs or in your own code.



    But still, EXCELLENT article.

  • Hi, Roi.



    Great article, very insightful.



    It definitely gives a good solution to most cases, however, there are a few exceptions:



    1. If your ADO.NET provider does not support distributed transactions (most popular RDBMSs support this but not all of them) - for example MSACCESS or an early version of IBM DB2.



    2. If the component you are testing is using Enterprise services, but with inappropriate Transaction attribute ("Disabled", "Not Supported" or "Requires New").



    3. If the component is performing an action that cannot participate in a distributed transaction (such as a DDL command, that commits automatically for most RDBMSs).



    For these exceptions, I don't see any other way but to resort to other methods. There's no quick fix for using Enterprise Services (unless you're willing to change your tested component).



    Another thought: How about using MS DTS? You can easily create a package that generates your initial database state on a new schema, and than run the package before the beginning of the test.



    Best Regards,

    Amir.

  • Ido:

    1. yes

    2.No, unless the DAL has explicit "Don't use transactions" attributes.

    3.yes



    Udi:

    It was for the same of example. I wanted to show there is no behind the scenes goo going on. Obviously that measn not production ready code.



    Amir: Thanks for the insightful comments.

    1. agreed.

    2. agreed.

    you could solve that by doing #ifdefs at the component level for DEBUG and RELEASE modes I would guess.



    3.Didn't consider that, good point.



    The main thing is that this method solves the pain problem for *most* cases out there. to me, solving 80% of the cases is much better than nothing. Obviously this isn't a silver bullett. Nothing ever is.

  • Perfect timing as I'm just beginning my foray into ES :-) Two birds with one stone, good stuff Roy!

  • It's not exactly true that the state of the database is the same if you rollback the transaction. If you have IDENTITY fields, then, even if you did not commit the transaction, the values you got are gone, and the next time you'll get different values.



    So, you need to be very careful with foreign keys that map to autonumbered primary keys, as you cannot rely in the values you get. If you need a CategoryId in a Customer table, then you need to first insert a Category, get the identity value, and use it in the Customer table.



    When the database is complex (e.g., to insert a record in 'Customer' you first need to insert a record in 15 tables), this can make your unit tests very complex.



    Creating the database from scratch is a better solution. In that case you can assume that the first CustomerId you'll get is 1, and hard code that '1' in your tests. Is less elegant but easier.



  • Andres: You have a point, btu I'd still rather do those inserts rather than recreate the DB from scratch before every test, which I think would big performance consequesnces.

  • Yes, that's also true.

  • Pretty. Simple.

    Thanks!





  • Roy -- interesting article.



    We were doing this 8 months ago on our own team (though, now I wished we had written about it then! ;) ).



    We found issues, though, when testing with Server Application components. Also, we found issues with the various Oracle providers (both from Microsoft and Oracle) not playing well with NUnit started and ended transactions. With Server Applications, in particular, if you need the client to call Dispose explicitly, and in this case, the client is NUnit, which is also not derived from ServicedComponent.



    I will write more about these issues and problems to watch out for on my blog later on today.

  • Robert - Excellent!

  • Great article Roy. I just managed to set up something akin to James's approach in my app, and now I'll be changing it again...



    As an aside, you replied to the question about the DAL saying it would have to be signed. Am I missing something here, or could you avoid this by putting the Serviced Component test base class in a strong-named assembly, and referencing it from your main test assembly/ies? It doesn't appear to me that your ServicedComponent should have to know about your DAL. Then again, I may be all wet here!



    Dave

  • Dave: no, that would not work (even if it sounds like it).

    Because your derived text fixutres inherit from a ServicedComponent, they themselves are servicedcomponents and thus the assembly they reside in should also have a strong name. Which leads down the same path outlined in the article.

    But thanks for asking. I actually tried before answering because I wan't sure myself.

  • Thanks Roy. It sounded plausible, but I had a feeling it would be too easy...

  • Roy -- I ran a test today with some simple code against an Oracle database using your method. I tested against a Server Application component. Follow the link above to see my results.

  • "Am I missing something here, or could you avoid this by putting the Serviced Component test base class in a strong-named assembly, and referencing it from your main test assembly/ies?"



    Unless I'm incorrectly interpreting your question, the application of the AllowPartiallyTrustedCallersAttribute class would more than handle a situation like that.

  • Hi Roy,



    It realy looks like a simple & effective approach that can help for 80% of the cases.

    I thought on some problem that maybe related not just to this article but to the whole TDD approach (I admit that i'm not very familiar with this approach):



    The tests results are based on the ability to verify automatically the results of the actions done during the tests (you used the "VerifyRowExists" method in your example). I support the need for automatic verification of the tests and in your case there is no other way because the transaction is automatically rolled back at the end of the test, but when you are writting code to verify test results some bugs in the verification process are eventually inevitable.



    In your example the verification process is quite simple and you can write a generic code that do the "VerifyRowExists" logic and test it once seperately and by that you are solving most of the problem, but sometimes verification code is much more complex (e.g. when the dal operation is not running a simple CRUD function but rather handles multiple records by running a stored procedure or by triggers invoked automatically by the database). For instance, you might design your system in a way that when you delete a category that contains some items, all of the items should not be deleted, but rather shipped to a default category.



    The big problem might occur when the bug in the verification does not fail the test but rather approves a test that has a bug.



    Regards,

    Omer.

  • Roy,

    Good article. I experimented with ES and NUnit a while back, but not in the way you describe here. The pain I ran into was with DAL classes that had parameterized constructors, which ES doesn't like. This approach is definitely easy and solid for most code that writes to a DB that supports distrbuted transactions though I would mention somewhere that the network config has to support it. In firewalled scenarios, the dev machine's DTC may not be able to talk to the DTC on the DB server.



    Cheers,

    Christian

  • Thanks for all the comments.

    There's definitely going to be an "appendix" post for this article with all the newfound edge cases and special cases and problems.

    That's what community is all about.

    I haven't had a lot of time to continue learning this issue (crazy week!) but I'll get on it as soo as I can.

    Keep that info coming.



    Roy

  • Roy,

    Have you tested this with NUnit 2.2? As soon as I inherit frmo Serviced Component, NUnit says I have an invalid fixture!



  • No, I tested with Nunit 2.1. Thanks for the info.

  • A quick fix:

    In Nunit-Gui - open General options and uncheck the "Reload before each test run" checkbox.

    this should fix most if not all the "Object disposed" errors you see.

    BTW: it works great on NUnit 2,2 (same option)

  • Roy,



    Have you thought about using services without components instead? It makes the tests a little lighter weight, which is important when you have a lot of tests to run.

  • Udi:

    Yes. I'm planning an article on this issue real soon. :)

  • My initial testing indicates that the base class simply becomes:



    [TestFixture]

    public class TransactionalFixture

    {

    [SetUp]

    public void Setup()

    {

    ServiceConfig config = new ServiceConfig();

    config.Transaction = TransactionOption.Required;



    ServiceDomain.Enter(config);

    }



    [TearDown]

    public void TearDown()

    {

    ServiceDomain.Leave();

    }

    }



    Seems ok to me, without any extended testing. Remember you'll need XP SP2...

  • Don't think I was thinking too clearly this afternoon! Forgive me...



    Doesn't work... code should read...





    [TearDown]

    public void TearDown()

    {

    ContextUtil.SetAbort();

    ServiceDomain.Leave();

    }



    ... I think. But I get an error. I'll investigate further...

  • Mint. Got it working a treat. My subclass had a Setup routine which was causing the TransactionalFixture.Setup to not be called. A quick call to base.Setup(); did the trick.



    Works great.



    Which is good, since I keep getting Object Disposed errors with the original technique, even when I clear the checkbox in options. Mmmm.

  • I'm having problems with both NUnit gui and Nunit Add-in when test class is extended from ServicedComponent. They run ok first time but silently fail afterwards until next build. However Johnny's solution with



    ServiceConfig config = new ServiceConfig();

    config.Transaction = TransactionOption.Required;



    works perfectly so far.

  • Does anybody know of a solution similar to Johnny's that works on Windows 2000?



    The problem with the original solution is (as far as I can tell) that when SetAbort() is called the COM+ object (and there by also the .NET object) is destroyed. This will give the 'object disposed' error when running test number 2 in the same class.

  • I keep getting the following error when loading the assembly into the NUnit:

    Error: System.Runtime.InteropServices.COMException(0x80040154): Class not registered|| at System.Runtime.InteropServices.Marshal. ThrowExceptionForHR(Int32 errorCode,IntPtr errorInfo)|| at System.EnterpriseServices.Thunk.Proxy.CoCreateObject(Type serverT...



    Do you have any idea as to what could be wrong?



    Thanks

    Ilene

  • I get this error Unhandled exception at 0x7c4a218a in nunit-gui.exe: 0xC0000005: Access violation writing location 0x7c4c4a68. When running the test in NUnit 2.1.



  • Sorry about the redundancy, but after a tough fight with nunit-gui, I feel like to say - It works great using service without components!!!



    Thanks, Rui



  • in my code i have transactions, my test code gives me error saying: "OracleConnection does not support parallel transactions."

  • if i have local transactions in the code, the test gives me error: System.InvalidOperationException: OracleConnection does not support parallel transactions

  • I like your solution. I have a knowlege hole though, when you said "you ARE using a test database...".

    I don't see how, in the .net environment, to get all the connections that exist in all the forms changed back & forth from the test to the production database.

    Is there a book or website that describes this architecture and process?

    Thanks

  • Hi,
    I have a problem in using com+ for transaction.

    I used to use vb.net 2003. My program's architecture is 5 layers.

    The Business objects and dataobjects are com+ objects.I use tcp channel for remoting and the atributes for com+ that I use are :









    and for all of my functions I use try/catch .at the end of try I use ContextUtil.SetComplete and In catch I use Contextutil.SetAbort. also I Inherit a class from Exception for my own exception.

    but In one function I call 3 other functions.when the error of remoting has raised or when I throw my own exception, SetAbort does not work.

    I will be glad If someone help.

    Thanks

  • Dear Roy,

    Could you pleased upload the source code sample of this article?

    I couldn't make test with this. The error :
    TestFixture failed: Invalid ServicedComponent-derived classes were found in the assembly.

    Thanks. Best regards.

Comments have been disabled for this content.