3

I've been trying to wrap my head around unit testing and I'm trying to deal with unit testing a function whose return value depends on a bunch of parameters. There's a lot of information however and it's a bit overwhelming..

Consider the following:

I have a class Article, which has a collection of prices. It has a method GetCurrentPrice which determines the current price based on a few rules:

public class Article
{
    public string Id { get; set; }
    public string Description { get; set; }
    public List<Price> Prices { get; set; }

    public Article()
    {
        Prices = new List<Price>();
    }

    public Price GetCurrentPrice()
    {
        if (Prices == null)
            return null;

        return (
            from
            price in Prices

            where

            price.Active &&
            DateTime.Now >= price.Start &&
            DateTime.Now <= price.End

            select price)
            .OrderByDescending(p => p.Type)
            .FirstOrDefault();
    }
}

The PriceType enum and Price class:

public enum PriceType
{
    Normal = 0,
    Action = 1
}

public class Price
{
    public string Id { get; set; }
    public string Description { get; set; }
    public decimal Amount { get; set; }
    public PriceType Type { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public bool Active { get; set; }
}

I want to create a unit test for the GetCurrentPrice method. Basically I want to test all combinations of rules that could possibly occur, so I would have to create multiple articles to contain various combinations of prices to get full coverage.

I'm thinking of a unit test such as this (pseudo):

[TestMethod()]
public void GetCurrentPriceTest()
{
    var articles = getTestArticles();
    foreach (var article in articles)
    {
        var price = article.GetCurrentPrice();
        // somehow compare the gotten price to a predefined value
    } 
}
  • I've read that 'multiple asserts are evil', but don't I need them to test all conditions here? Or would I need a separate unit test per condition?

  • How would I go about providing the unit test with a set of test data? Should I mock a repository? And should that data also include the expected values?

4 Answers 4

4

You are not using a repository in this example so there's no need to mock anything. What you could do is to create multiple unit tests for the different possible inputs:

[TestMethod]
public void Foo()
{
    // arrange
    var article = new Article();
    // TODO: go ahead and populate the Prices collection with dummy data


    // act
    var actual = article.GetCurrentPrice();

    // assert
    // TODO: assert on the actual price returned by the method
    // depending on what you put in the arrange phase you know
}

and so on you could add other unit tests where you would only change the arrange and assert phases for each possible input.

Sign up to request clarification or add additional context in comments.

6 Comments

What is the unit in this case? I just want to know if the GetCurrentPrice works properly. Is every different different input a unit and does the correctness of the method depend on all these unit tests passing? Or is the method itself the unit?
@diggingforfire, there is no unit. But that's because your code is designed in a such way that there is no unit. Your Article class does 2 things: it has properties to hold some data and it contains a method to manipulate this data. So you cannot unit test those 2 in isolation. If you wanted to separate those responsibilities you could have defined the GetCurrentPrice in a separate repository class which would take Article instance as argument.
Would that not lead to an anemic domain model? I'd prefer the Article itself to contain that logic. It seems to make unit testing a bit harder though..
@diggingforfire, yes it could lead to an anemic domain model. You are using a DDD approach which is good. But in order to unit test this article class in this case you should prepare different instances of it in each unit test to cover the different possible outputs of the method you are trying to test.
that makes sense (like Ivan says as well). How would I go about providing the data for each test? Would I just hardcore it into the unit test in this case?
|
2

You do not need multiple asserts. You need multiple tests with only a single assert each.

4 Comments

I would disagree here. I think you can combine test data as long as you are expecting the same results. There is no reason for me to write 100 unit tests when I can achieve the same thing in a single test within an iterative pattern. This is assuming the test results for each should be the same. An example here would be a dictionary of key value pairs that were used as test data for each key passed into the method - the method return value should be the value of the key value pair. I see no reason why this isn't effective.
At the end of the day - the same data evaluation is performed - and if it does fail - I have to figure out why anyway - so fire up the debugger and investigate the test. Same results - much easier to maintain.....
@tsells: I start by creating a single unit test, then a second unit test, then I refactor. I might very well reach the level of an iterative test, though that has the disadvantage that failure of one test will result in the remaining tests not being executed.
John - Keep in mind my first statement - you are expecting the same type of results - so most likely when an error is introduced into the area - it's going to affect multiple tests. Also note - I don't use any type of BDD / TDD to drive my code. I test my code as I write it. I cannot for the life of me create a failing test and then write code to make it work. It's inefficient to me - but that's another conversation all together.
2

new test for each startup condition and single assert,f.e.

[Test]
public void GetCurrentPrice_PricesCollection1_ShouldReturnNormalPrice(){...}

[Test]
public void GetCurrentPrice_PricesCollection2_ShouldReturnActionPrice(){...}

and also test for boundaries

for unit tests i use pattern

MethodName_UsedData_ExpectedResult()

5 Comments

So you would have a test for every possibly input and it's respective output? That sounds logical. It seems that it would take a lot of manual coding though to set it all up. Are there any ways of easily providing the tests with such data?
we can say that what iam interested in is not to test everything(its impossible almost). you should stay focused especialy on boundaries (f.e. prices with same dates but different pricetyp). Common things are not worth to test a lot. I heavily use factories for example to create a valid PriceCollection but you have to code some things more but you are covered with unit tests witch you appreciate when you begin to refactor your solution.
Resharper screams at you for that pattern of unit testing. Just curious - why do you need the method name in there - can't you see what method is being called inside the test?
And yes I know the recommendations, read the books,etc - I just haven't sipped the "purist" juice as of yet - I am more of a realist. :)
why not to add it there? its not a mistake for me. what makes more sense when you look at test results, have there only usedData_returnFalse() or CreateMethod_u sedData_returnFalse()? I know but about resharper but that are conventions not errors or bugs:-) i used to do it like that way, i know that this is not the most sophisticated way but for me it works
2

I think you need datadriven testing. In vsts there is an attribute called Datasource, using it you can send a test method multiple test cases. Make sure you don't use multiple asserts. Here is one MSDN link http://msdn.microsoft.com/en-us/library/ms182527.aspx

Hope this will help you.

2 Comments

Why data driven? This isn't testing the database.
We use datadriven testing when we need to test a method with multiple inputs. It can be any method.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.