0

I'm not really sure what/if there is a gold-standard on how much a unit test should be broken down. Another thing is sometimes I question whether I am wasting my time on a specific test. I am new to unit testing/TDD. So for example, say I have some simple code like this:

public class IntegerStats
{
    public int MaxValue { get; private set; }
    public int MinValue { get; private set; }
    public int NumberOfElements { get; private set; }
    public double AverageValue { get; private set; }

    public IntegerStats(int[] inputArray)
    {
        MaxValue = inputArray.Max();
        MinValue = inputArray.Min();
        AverageValue = inputArray.Average();
        NumberOfElements = inputArray.Length;
    }
}

My first two unit tests look like this:

[Test]
public void ProvideArrayReturnsIntStatsObject()
{

    int[] testArray = {1, 5, 254783, 98, 4793, 67};
    IntegerStats intStats = new IntegerStats(testArray);

    Assert.IsTrue(intStats.GetType().ToString().Contains("IntegerStats"));

}

[Test]
public void Length5ArrayLengthIs5()
{
    var result = new IntegerStats(new int[]{5,4,8,9,4});

    Assert.AreEqual(result.NumberOfElements,5);
}

However, should I be passing multiple arrays in to this one test or should I make several array length tests of different lengths? How do I know if the method is adequately tested?

My next plans were to continue testing the other properties... But then I was questioning whether I should even be testing these methods since they are using built-in provided standard library algorithms rather than my own custom algorithms in the first place.

Also, I had first started out with a test checking whether all of the stats were accurate in one big test but then I realized that's not really a unit test since it could/should have been broken down more.

Any advice/resources on this would be super helpful. The thing is, I've done plenty of reading about "What unit testing is" but putting it into practice for me has been quite different.

2
  • 5
    Your first test isn't particularly interesting. All it does is test whether your programming language's new keyword works. Oct 21, 2016 at 23:12
  • 5
    I was questioning whether I should even be testing these methods since they are using built-in provided standard library algorithms rather than my own custom algorithms in the first place. -- As well you should. You should also be questioning whether such a class is even useful (other than as an intellectual exercise or example), since all you're really doing is replicating functionality that already exists as methods of the array object. Oct 21, 2016 at 23:15

3 Answers 3

2

Don't try to get from unit testing more than you can. In order to make sure a function is correct, you should test it with all possible inputs (most often, that's really expensive, if not impossible). Thus, when writing tests, keep in mind that a function that does pass the tests is not correct, but rather it has a lower likelihood of containing bugs.

The difference between some unit test for a function and a better one is that the better one looks into places where bugs may/are more common to occur (this is strictly from the point of view of what is being tested, there are other qualities such as readability). Usually, I like to see 3 things in a test (this is a bit opinionated):

  • Error conditions: Errors are not really easy to see, sometimes they occur very rarely in practice and might be hard to trace, so it would be nice to test for them.

  • One or 2 common (average) input. If tests fail, it is easy to see what happens in a common, dull input case, not having to worry about special conditions.

  • Special cases: this is where you have to get creative. For example, you are processing an array. What happens if the array is empty? Since you are taking both the minimum and the maximum of the array, what happens if the array contains just one element?

This is just my way of thinking about tests, and I am sure there are better ways out there. But the point is to have something in mind when writing a test. Don't simply throw some data and say "this is a test", but rather think carefully what you want to achieve with that test and whether if it adds any value to your test suite.

2
  • "test it with all possible inputs" - This example is of course trivial, but for real code I usually test boundaries to protect against dumb fencepost errors. So test with 0, 1 and n items in the array.
    – KlaymenDK
    Jan 12, 2017 at 9:25
  • @KlaymenDK proof by induction ftw. Also, it doesn't even make sense to "test with all inputs". With TDD you should follow "Red, Green, Go". Make a test for your functionality that fails, then make it pass in the simplest way possible to write the functionality.
    – unflores
    Jan 12, 2017 at 13:16
3

Generally speaking, as long as it makes sense to you, and if you feel reasonably certain that others who use your code and run your tests down the road will understand how your tests are written, then you should be able to write tests according to what feels intuitive. If you think a method is getting too messy, break it up. Too complex? Break it up. There are some discussions about how many lines of code you should limit your methods to, but most programmers will have a different opinion. If it's problematic to you or someone else on your team, that's an indicator your method is either too long or too short.

I get what you're saying about whether you should test built-in algorithms. Here's my take...Any code you write, you should test, within reason. I would also consider whether the code you write needs a unit test. @MainMa stated in the comments below:

TDD applies well to some code, and doesn't apply to other code. For instance, interfaces with the world won't be covered by unit tests; this is the goal of system tests. Also, there are situations where writing automated tests are simply prohibitively expensive.

Of course, it all depends on team size, budget, and complexity of your application. So you've got to feel it out. Bottom line, at the end of the day it's all about making sure the code you ship actually works, and that you sort out any kinks before your code goes to production.

I would advise that you try to write your tests somewhat generic, unless you are testing for something specific. For example, you're testing for specifically 5 items being put into a collection...but does it work with 6 items? 7? 8? If there's nothing specific to the number of items, consider writing a test that more generically tests passing arrays of different lengths in. In your case above, the 2nd test is really just testing that a particular field is getting set correctly, so I would consider naming that test more appropriate to your NumberOfElements field, like TestNumberOfElementsIsCorrect, then do a few checks to see what happens when null, new []{}, and new[]{...any number of items} are passed in.

I would recommend keeping your methods small. Smaller methods are easier to debug. I would also recommend making sure that each individual assertion within a particular test method are similar to one another. It's when you start adding multiple different assertions, that are not reasonably similar, that your code becomes hard to read and hard to manage. If you've got a large method full of assertions that are all similar, that's probably ok. Just make sure you comment what you do (comment the method and any funky-looking code internal to the method), and you should be fine.

Hope this helps!

5
  • 1
    "Generally speaking, as long as it makes sense to you, and if [...]": not really. Would you answer the same to a person asking what is the reasonable LOC of a method? I worked with colleagues who were happy to work with 4000+LOC methods and felt reasonable certain that it's a valid length. This doesn't make their opinion relevant. Oct 21, 2016 at 23:40
  • "Any code you write, you should test, within reason.": nope, given the scope of the question. TDD applies well to some code, and doesn't apply to other code. For instance, interfaces with the world won't be covered by unit tests; this is the goal of system tests. Also, there are situations where writing automated tests are simply prohibitively expensive. Oct 21, 2016 at 23:42
  • 1
    "It's when you start adding multiple different assertions, that are not reasonably similar, that your code becomes hard to read and hard to manage.": interesting observation, which, by the way, solves the question of how much assertions per test is too much. Some developers would claim that a unit test should have one and one only assertion, which may not work well in some cases, and lead to an exponential growth of unit tests (eventually accompanied by code duplication). Oct 21, 2016 at 23:45
  • @MainMa with regards to LOC, that is a very valid point, although I assumed the OP already has a sense that lengthy methods are obviously avoided, judging by the contents of their question. There are some tips out there about lines of code. And 4000 line methods!? What on earth. I feel for you o.O. As far as the out-of-scope aspects of unit tests, I would think tests more appropriate for unit or automation testing are not "within reason." I'll add your comments in with the next edit, those are some good points you've raised. Thanks! Oct 22, 2016 at 0:13
  • @MainMa regarding how many assertions per unit test method...I think it's largely personal preference (imho). I think only having one assertion per method might be a little strict, but it all depends on the scenario. I can see a larger project wanting to be more restrictive because of the likelihood that a programmer may abuse a unit test by tying in too many assertions in a hurry. Similarly, if I'm testing something that is in itself inherently more complex (such as a service request that has to touch some endpoint, mock, etc.) I may choose to keep that assertion alone. Oct 22, 2016 at 0:20
0

In Unit Testing (UT) and especially TDD, there is an emphasis on test coverage, but I think the first priority (not exclusively) should be taking advantage of the automation capabilities of UT.

Think about how you would test a particular method without writing a test. In some way, you're going to pass in the parameters and examine the output. It's all manual. You do it a few times and if you're happy with the results, you move on. The disadvantage, if we're honest with ourselves, is you're going to write more code that would fail this test if you were to actually do it. You don't do it, so you get to go through your code and find the problem child.

As you learn to create UT, operate in this similar fashion, but write actual tests that mimic how you would have tested it before you knew what a UT was. Don't write tests for the things you usually don't manually test. "If I set this public property to 2 will it really be 2?" Of course it will. I realize this is not TDD, but Rome wasn't built in a day. You're trying to grow as a programming professional and not become one over-night.

As you go through a particular project or projects, you're going to start finding problems that you conclude could have been avoided/quickly debugged if a test would have caught it. Not only should you create that test, but start taking these types of problems into consideration in your future coding. You may not be able to test this first; again, you're still learning, but would recognize the immediate benefit of having this type of test, so it comes very quickly to you.

This is how you learn something and develop fluency. For many programmers, something like following a formatting standard doesn't require any more additional time in their code because those indentations just become a habit.

You will continue to improve as a programmer because you'll quickly identify if a test is needed and how to write it. This is a tremendous improvement in your capacity to code (How do those 10X devs do it?) And if any junior developer questions why this is necessary, you'll have plenty of stories about the hardship you went through leaning about this the hard way. Once you convert them, you'll have leveraged your talents even more. Who knows, you may actually learn to not hate TDD or think it is a total waste of your time.

As a side note, I think developers who don't go through this natural process, try to jump into things like TDD before they've developed prerequisite skills and end up trashing many methodologies like TDD, agile, Scrum, OOP, or whatever. While we're still searching the web for a solution to make some basic functionality work in our program, are we really going to be able to do that all the time considering design, architecture, standards, performance, testing, and whatever methodology we're trying to adhere to all at once? Of course you can, but not until you've learned everything else along the way.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.