Home >

Contributing to OSS – Creating a test case & patch

15. December 2008

For some time from now, I have been _trying_ to contribute some opensource projects including NHibernate, Castle(even though i couldn’t contribute the latter yet). People are trying to help the projects by creating issues in the issue trackers. The thing we come face to face usually is that the information provided in the issues are not enough to reproduce, or too time consuming to write a test for(e.g. NHibernate tests usually needs 3 files, 1 for test fixture, 1 for mappings and 1 for the entity). This issue prevents developers from fixing the issue very quickly.

In this post, I will try to explain how to write a test for NHibernate codebase, but the idea is the same for all projects.

There was already a really good article in NHForgegoing over the same steps I am going to follow, but I want people get used to OSS world, hence Source Code Management

Getting the source code

You need to have an SVN client, and during this article I will use Tortoise SVN directly (there are some other clients namely VisualSVNand  AnkhSVN (free)built on top of Tortoise SVN).

  1. In order to get the source code, you should create a folder named nhibernate wherever you want.
  2. Right-click the folder, and press SVN Checkout
  3. Enter the URL of repository(https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/ for nhibernate) and imagepress OK to check out the code.  
  4. Luckily, I was able to get the code in one trip, but this is not always the case. Connection may get broken, or sourceforge may kill you during the checkout. If this is the case, right click the folder and click on Update

    image  
  5. Open a command window in that folder, go to nhibernate subfolder and type nant. This action not only builds the sourcecode, but also generate AssemblyInfo.cs which is required for you to build solution in VS.

    image 
  6. Now you are ready to open solution in visual studio. Go ahead and double-click on .sln file.

    image

    Ups wtf is that? Due to some custom build steps nhibernate is using, visual studio warns us. Uncheck the box below and click on Load project normally. Then press ok.

Creating the test case

The best way to illustrate a bug is a test case. I am probably the last person to talk on how a unit test should be and there are already some good articles on what to do / or better what not to do on the web, but i want to illustrate some points that I think important.

  1. Isolation
    A test case should be isolated from every other non-relevant  component. I see several times that people are creating tests with other projects dll’s. This can be really hard for us to isolate. NHibernate tests shouldn’t have any other dependency other then NHibernate itself.
  2. Short test cases
    It may not be easy to find where the bug is when we have tens of lines in a test. This idea is related to isolation in someway and should be taken into account.
  3. Verify results
    There are times when I see a test code that doesn’t check for the results, but only checks if it doesn’t throw exception. This is plain wrong! A code may not throw an exception, but still not produce the correct result. Please take care with this issue.

I know, I talked a lot, and now it is code time!

  1. First thing you should do is to create a test folder named NHXXXX in NHibernate.Test/NHSpecificTest. Tests that are related to bugs go into NHSpecificTest for tracking purposes.
  2. Second thing is to create entities. (Assuming that you’ll need to demonstrate it using a session and you’ll query the database, otherwise you can skip this step and the following).
    public class MyClass
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
    }
  3. Now it comes to mappings. The thing you must be aware of is that if you give “Mappings.hbm.xml”, NHibernate will automagically catches it.
    <?xml version="1.0" encoding="utf-8"?>
    <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                       assembly="NHibernate.Test"
                       namespace="NHibernate.Test.NHSpecificTest.NH1552">
    
        <class name="MyClass">
            <id name="Id">
                <generator class="native"/>
            </id>
            <property name="Name"/>
        </class>
    </hibernate-mapping>
  4. Now we should create the test case. Our test case should inherit from NHibernate.Test.NHSpecificTest.BugTestCase to make the things easier.

    If our test is specific to a dialect, we should override virtual bool AppliesTo(Dialect dialect) method, and for this blogpost we’ll use issue NH-1552which is specific to MsSql2005Dialect
    protected override bool AppliesTo(NHibernate.Dialect.Dialect dialect)
    {
        return dialect is MsSql2005Dialect;
    }

  5. There are some predefined methods for the test case such as OnSetup() and OnTearDown(). In OnSetUp() method, you initialize the data and on TearDown() you generally only remove the data. For our purpose, since we check if paging works with SqlQuery, we need at least 3 items.
    protected override void OnSetUp()
    {
        using (var session = this.OpenSession())
        {
            using (var tran = session.BeginTransaction())
            {
                MyClass newServ = new MyClass();
                newServ.Name = "tuna";
                MyClass newServ2 = new MyClass();
                newServ2.Name = "sidar";
                MyClass newServ3 = new MyClass();
                newServ3.Name = "berker";
                session.Save(newServ);
                session.Save(newServ2);
                session.Save(newServ3);
                tran.Commit();
            }
        }
    }

  6. Now we write the TearDown() to remove the data. 
    protected override void OnTearDown()
    {
        using (var session = this.OpenSession())
        {
            using (var tran = session.BeginTransaction())
            {
                session.Delete("from MyClass");
                tran.Commit();
            }
        }
    }

  7. Now we need to write 3 test cases for paging, one with FirstResult, one with MaxResult, and one with both.  
     
    I have to admit that _works_as_expected_ doesn’t mean much, but that’s the way i like it.
    [Test]
    public void Paging_with_sql_works_as_expected_with_FirstResult()
    {
        using (var session = this.OpenSession())
        {
            using (var tran = session.BeginTransaction())
            {
                string sql = "select * from MyClass order by Name asc";
                IList<MyClass> list = session.CreateSQLQuery(sql)
                    .AddEntity(typeof(MyClass))
                    .SetFirstResult(1)
                    .List<MyClass>();
                Assert.That(list.Count, Is.EqualTo(2));
                Assert.That(list[0].Name, Is.EqualTo("sidar"));
                Assert.That(list[1].Name, Is.EqualTo("tuna"));
            }
        }
    }
    
    [Test]
    public void Paging_with_sql_works_as_expected_with_MaxResult()
    {
        using (var session = this.OpenSession())
        {
            using (var tran = session.BeginTransaction())
            {
                string sql = "select * from MyClass order by Name asc";
                IList<MyClass> list = session.CreateSQLQuery(sql)
                    .AddEntity(typeof(MyClass))
                    .SetMaxResults(2)
                    .List<MyClass>();
                Assert.That(list.Count, Is.EqualTo(2));
                Assert.That(list[0].Name, Is.EqualTo("berker"));
                Assert.That(list[1].Name, Is.EqualTo("sidar"));
            }
        }
    }
    
    
    [Test]
    public void Paging_with_sql_works_as_expected_with_FirstResultMaxResult()
    {
        using (var session = this.OpenSession())
        {
            using (var tran = session.BeginTransaction())
            {
                string sql = "select * from MyClass";
                IList<MyClass> list = session.CreateSQLQuery(sql)
                    .AddEntity(typeof(MyClass))
                    .SetFirstResult(1)
                    .SetMaxResults(1)
                    .List<MyClass>();
                Assert.That(list.Count, Is.EqualTo(1));
                Assert.That(list[0].Name, Is.EqualTo("sidar"));
            }
        }
    }


  8. Now make sure that the tests are failing(now they all pass after we apply patch from Dana)

    image
    Now it is failing, btw, the image is from Resharper, which is a joy to use but you can also use NUnit
  9. If you can fix the bug, go ahead and fix it, if not it’s still fine. Jump to create a patch section

     

    Creating a Patch

    image 
    Creating a patch produces a diff for modified code pieces. When you click on Create Patch, you will probably see a bunch of files in the list, please make sure you only choose the ones you have added.


    image

    That’s it, take this patch file and send it to JIRA

    kick it on DotNetKicks.com

, , , ,

Comments

12/15/2008 4:29:51 AM #
nice post Smile
hopefully, this will make it a bit easier for us to try to fix issues Smile
12/15/2008 4:59:17 AM #
Thanks Davy.
3/22/2011 7:13:23 PM #
I see there is some very good information in this article. Please continue with the work that you are doing
Comments are closed