About this: "I would like to know if DataObjects.NET has best practices concern TDD". (from DataObjects+Mock framework )

What is the best way to test BL (repositories, services etc), which uses some data? Example: goods/orders. I need to test calculation of discount sum. So I need some goods, the order with collection of goods.

Should I write class to provide fake data (but how can I use fetching in such case?) or use test database with fake data, and run all tests in one transaction with rollback at the end? Should I use mocks (but I can't use mocks for DO Entities)?

I know, that this question isn't about DO supporting, but I think your experience will help all developers to better understand how to do unit testing with DO :)

Or maybe you provide some useful links :)

Thank in advance.

asked Nov 11 '10 at 12:07

Ness's gravatar image

Ness
155232328


One Answer:

We use the following approach to test DO itself:

All our testing is based on NUnit. Nearly all our test fixtures inherit from AutoBuildTest. This type ensures its Domain property is provided for each test (use [SetUp] or [TestFixtureSetUp] to implement this; we use the second option, but do nothing, if Domain property is already set), and allows to change its configuration by overriding BuildConfiguration member (normally we register a specific namespace with specific model there).

By default Domain is built in Recreate mode, i.e. an empty one; you can override Setup method to fill the database with data specific to a particular test. Likely, in your case you should invoke one of external data population services (classes) from this method to fill the initial data - I'd separate this logic from test because many tests tend to share the same initial data, also, data population code is normally written as in tree-like fashion (CreateCustomers, CreateOrders, ...), so it's a good idea to implement each scenario in separate class.

In addition, this class uses environment variables to modify the provided configuration in accordance with a set of rules, since we must run all the test on build server on each backend (i.e. database) and in a set of different mapping scenarios (we use IModule feature to change inheritance mapping and few other options). But likely, you won't need this.

Any test does the following:

  • Creates a Session
  • Runs the test itself
  • Rolls back all the modifications - usually, by rolling back the transaction.

If there is a sequence of transactions, we cleanup all the changes by one of the following ways:

  • Manually, if this is easy
  • Automatically - by invoking a special method that rebuilds the Domain (if changes are complex)
  • In some cases this isn't necessary at all - i.e. if changes shouldn't affect on other tests.

To speedup testing, you can run some tests on memory provider. But remember that currently it can't rollback any changes and can't run the transactions concurrently. Also, it's a bad idea to use it in tests with complex queries on large volumes of data: our query optimizer is pretty simple, so such tests may run faster on SQL Server.

Since some tests are RDBMS-specific, we use a special Require class to skip execution of test on some backends and configurations - e.g. Require.ProviderIs(StorageProvider.Sql) call ensures test will run only on SQL databases. Such methods throw IgnoreException (for NUnit) in case the condition is violated. Again, probably you won't need this, but that's the idea you can use as well.

And few things I'd recommend you to avoid:

  1. Using a real database (e.g. backup) instead of data population routines - people used to dealing other ORM tools (or with plain SQL) frequently make this mistake. In this case you'll need to care about upgrade on each model change instead of thinking about this only closer to release. Refactoring in VS.NET (esp. with ReSharper) helps to change data population routine in accordance with model changes with nearly zero efforts, but data upgrades can be much more complex. Also, you should track versions of such backups, distribute among developers and build servers, ensure changes made by tests aren't left there, and likely, even store them in repository. Obviously, that's a hell. On the other hand, none of such problems appear, if you deal just with C# code.

  2. Developing tests dependent on the order of their execution. I know, that's a normal practice, but sometimes people ignore it. The problem appears when you're using different test runners - e.g. TeamCity, ReSharper and NUnit GUI execute tests in different order.

Here is an example code of pretty typical test:

[TestFixture]
public class SingleTableTest : AutoBuildTest
{
  protected override DomainConfiguration BuildConfiguration()
  {
    var config = base.BuildConfiguration();
    config.Types.Register(typeof (Base).Assembly, typeof (Base).Namespace);
    return config;
  }

  [Test]
  public void CombinedTest()
  {
    using (var session = Session.Open(Domain))
    using (var tx = Transaction.Open()) {
      new Base();
      new Derived() {Name = "Derived"};
      new Leaf() {Name = "Leaf", Description = "Green"};
      new DerivedNode() {Description = "Node"};

      var primaryIndex = Domain.Model.Types[typeof (Base)].Indexes.PrimaryIndex;
      var rs = primaryIndex.ToRecordSet();
      var result = rs.ToEntities(0).ToList();
      // tx.Complete(); // Rollback
    }
  }
}

Hopefully, this will help.

answered Nov 11 '10 at 16:26

Alex%20Yakunin's gravatar image

Alex Yakunin
29714412

edited Nov 11 '10 at 16:34

Just updated the answer.

(Nov 11 '10 at 16:35) Alex Yakunin Alex%20Yakunin's gravatar image

Yes, thanks.

We have used your method already.

And we use mocks, but not to replace DO entities, because there is no need to do it in most cases.

Maybe this is a bit clumsy example, but it illustrates using of mocks without placing them instead of DO Entities.


///order - DO Entity
var mockCalculator = new Mock<idiscountcalculator>();
            mockCalculator.Setup(m =>m.CalculateSumForOrder(order)).Returns(5);

order.RecalculateSum(mockCalculator.Object);
Assert.That(order.Sum, Is.EqualTo(10));
Assert.That(order.SumWithDiscount, Is.EqualTo(5));

(Nov 16 '10 at 05:13) Ness Ness's gravatar image

Clear - i.e. you use mocks for BLL. It seems a reasonable option.

(Nov 17 '10 at 08:15) Alex Yakunin Alex%20Yakunin's gravatar image
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!
toggle preview

Subscription:

Once you sign in you will be able to subscribe for any updates here

Tags:

×574
×2
×2
×1

Asked: Nov 11 '10 at 12:07

Seen: 3,543 times

Last updated: Nov 17 '10 at 08:15

powered by OSQA