How specific do unit tests need to be?

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

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

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

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.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *