views:

4243

answers:

35

Using C#, I need a class called User that has a username, password, active flag, first name, last name, full name, etc. There should be methods to Authenticate and Save. Do I just write a test for the methods and do I even need to worry about testing the properties since they are .net getter & setters?

A: 

I can't speak for C# specificly, but when I write unit tests I test EVERY input, even those the user does not do, that way I know how to prevent my own mistakes.

Unkwntech
+8  A: 

The rule is that you have to test every piece of logic you write. If you implemented some specific functionality in the getters and setters I think they are worth testing. If they only assign values to some private fields, don't bother.

Slavo
A: 

As I understand unit tests in the context of agile development, Mike, yes, you need to test the getters and setters (assuming they're publicly visible). The whole concept of unit testing is to test the software unit, which is a class in this case, as a black box. Since the getters and setters are externally visible you need to test them along with Authenticate and Save.

Onorio Catenacci
A: 

It doesn't hurt to write unit tests for your getters and setters. Right now, they may just be doing field get/sets under the hood, but in the future you may have validation logic, or inter-property dependencies that need to be tested. It's easier to write it now while you're thinking about it then remembering to retrofit it if that time ever comes.

Bob King
well, if your getters/setters need unit tests, there must be some logic associated with them, so that means logic must be written inside them, if they don't have any logic, no unit tests need to be written.
Pop Catalin
His point is that logic may be added to them later.
A: 

If the Authenticate and Save methods use the properties, then your tests will indirectly touch the properties. As long as the properties are just providing access to data, then explicit testing should not be necessary (unless you are going for 100% coverage).

Tom
+23  A: 

Test your code, not the language.

A unit test like:

Integer i = new Integer(7);
assert (i.instanceOf(integer));

is only useful if you are writing a compiler and there is a non-zero chance that your instanceof method is working.

Don't test stuff that you can rely on the language to enforce. In your case, I'd focus on your authenticate and save methods- and I'd write tests that made sure they could handle null values in any or all of those fields gracefully.

Tim Howland
Good Point on "Dont test the framework" - Something I got on too when new to this. +1'ed :)
Rob Cooper
A: 

I would test your getters and setters. Depending on who's writing the code, some people change the meaning of the getter/setter methods. I've seen variable initialization and other validation as part of getter methods. In order to test this sort of thing, you'd want unit tests covering that code explicitly.

Peter Bernier
A: 

Personally I would "test anything that can break" and simple getter (or even better auto properties) will not break. I have never had a simple return statement fail and therefor never have test for them. If the getters have calculation within them or some other form of statements, I would certainly add tests for them.

Personally I use Moq as a mock object framework and then verify that my object calls the surrounding objects the way it should.

tronda
A: 

You have to cover the execution of every method of the class with UT and check the method return value. This includes getters and setters, especially in case the members(properties) are complex classes, which requires large memory allocation during their initialization. Call the setter with some very large string for example (or something with greek symbols) and check the result is correct (not truncated, encoding is good e.t.c.)

In case of simple integers that also applies - what happens if you pass long instead of integer? That's the reason you write UT for :)

m_pGladiator
+9  A: 

If they really are trivial, then don't bother testing. Eg, if they are implemented like this;

public class User
{
    public string Username { get; set; }
    public string Password { get; set; }
}

If, on the other hand, you are doing something clever, (like encrypting and decrypting the password in the getter/setter) then give it a test.

Steve Cooper
A: 

Testing of a class should verify that: 1. methods and properties return expected values
2. Appropriate excepts are thrown when an invalid argument is supplied
3. Interactions between the class and other objects occur as expected when a given method is called

Of course if the getters and setters have no special logic then the tests of the Authenticate andSave methods should cover them, but otherwise an explict test should be written

Crippledsmurf
A: 

I wouldn't test the actual setting of properties. I would be more concerned about how those properties get populated by the consumer, and what they populate them with. With any testing, you have to weigh the risks with the time/cost of testing.

Bloodhound
+1  A: 

You should test "every non-trivial block of code" using unit tests as far as possible.

If your properties are trivial and its unlikely that someone will introduce a bug in it, then it should be safe to not unit test them.

Your Authenticate() and Save() methods look like good candidates for testing.

+3  A: 

Testing boilerplate code is a waste of time, but as Slavo says, if you add a side effect to your getters/setters, then you should write a test to accompany that functionality.

If you're doing test-driven development, you should write the contract (eg interface) first, then write the test(s) to exercise that interface which document the expected results/behaviour. Then write your methods themselves, without touching the code in your unit tests. Finally, grab a code coverage tool and make sure your tests exercise all the logic paths in your code.

warren_s
A: 

Ideally, you would have done your unit tests as you were writing the class. This is how you're meant to do it when using Test Driven Development. You add the tests as you implement each function point, making sure that you cover the edge-cases with test too.

Writing the tests afterwards is much more painful, but doable.

Here's what I'd do in your position:

  1. Write a basic set of tests that test the core function.
  2. Get NCover and run it on your tests. Your test coverage will probably be around 50% at this point.
  3. Keep adding tests that cover your edge-cases until you get coverage of around 80%-90%

This should give you a nice working set of unit tests that will act as a good buffer against regressions.

The only problem with this approach is that code has to be designed to be testable in this fashion. If you made any coupling mistakes early on, you won't be able to get high coverage very easily.

This is why it is really important to write the tests before you write the code. It forces you to write code that is loosely coupled.

Simon Johnson
A: 

Don't test obviously working (boilerplate) code. So if your setters and getters are just "propertyvalue = value" and "return propertyvalue" it makes no sense to test it.

erlando
A: 

Even get / set can have odd consequences, depending upon how they have been implemented, so they should be treated as methods.

Each test of these will need to specify sets of parameters for the properties, defining both acceptable and unacceptable properties to ensure the calls return / fail in the expected manner.

You also need to be aware of security gotchas, as an example SQL injection, and test for these.

So yes, you do need to worry about testing the properties.

GalleySlave
+3  A: 

Really trivial code like getters and setters that have no extra behaviour than setting a private field are overkill to test. In 3.0 C# even has some syntactic sugar where the compiler takes care of the private field so you don't have to program that.

I usually write lots of very simple tests verifying behaviour I expect from my classes. Even if it's simple stuff like adding two numbers. I switch a lot between writing a simple test and writing some lines of code. The reason for this is that I then can change around code without being afraid I broke things I didn't think about.

Mendelt
Glad you made a good point of the KISS principle.. I often have tests that are literally like 2-3 lines of code, real small, simple tests. Easy to grok and hard to break :) +1'ed
Rob Cooper
+1  A: 

in general, when a method is only defined for certain values, test for values on and over the border of what is acceptable. In other words, make sure your method does what it's supposed to do, but nothing more. This is important, because when you're going to fail, you want to fail early.

In inheritance hierarchies, make sure to test for LSP compliance.

Testing default getters and setters doesn't seem very useful to me, unless you're planning to do some validation later on.

Rik
A: 

I believe it's silly to test getters & setters when they only make a simple operation. Personally I don't write complex unit tests to cover any usage pattern. I try to write enough tests to ensure I have handled the normal execution behavior and as much error cases I can think of. I will write more unit tests as a response to bug reports. I use unit test to ensure the code meets the requirements and to make future modification easier. I feel a lot more willing to change code when I know that if I break something a test will fail.

Andrei Savu
+36  A: 

Many great responses to this are also on my question: "Beginning TDD - Challenges? Solutions? Recommendations?"

May I also recommend taking a look at my blog post (which was partly inspired by my question), I have got some good feedback on that. Namely:

I Don’t Know Where to Start?

  • Start afresh. Only think about writing tests when you are writing new code. This can be re-working of old code, or a completely new feature.
  • Start simple. Don’t go running off and trying to get your head round a testing framework as well as being TDD-esque. Debug.Assert works fine. Use it as a starting point. It doesn’t mess with your project or create dependencies.
  • Start positive. You are trying to improve your craft, feel good about it. I have seen plenty of developers out there that are happy to stagnate and not try new things to better themselves. You are doing the right thing, remember this and it will help stop you from giving up.
  • Start ready for a challenge. It is quite hard to start getting into testing. Expect a challenge, but remember – challenges can be overcome.

Only Test For What You Expect

I had real problems when I first started because I was constantly sat there trying to figure out every possible problem that could occur and then trying to test for it and fix. This is a quick way to a headache. Testing should be a real YAGNI process. If you know there is a problem, then write a test for it. Otherwise, don’t bother.

Only Test One Thing

Each test case should only ever test one thing. If you ever find yourself putting “and” in the test case name, you’re doing something wrong.

I hope this means we can move on from "getters and setters" :)

Rob Cooper
"If you know there is a problem, then write a test for it. Otherwise, don’t bother."I would disagree with the way this is worded. I was under the impression that unit tests should cover all possible execution paths.
Corin
While some may advocate such things, I personally don't. A good 90% of my headache came from simply trying to do "everything". I say test for what you expect to happen (so you know you are getting the right values back) but don't try and figure it all out. YAGNI.
Rob Cooper
I, too, advocate the "test your bugs" approach. If we all had infinite time and patience, we'd test every possible execution path. But we don't, so you have to spend your effort where it's going to have the greatest effect.
Schwern
A: 

I would write a test for anything that you are writing code for that is testable outside of the GUI interface.

Typically, any logic that I write that has any business logic I place inside another tier or business logic layer.

Then writing tests for anything that does something is easy to do.

First pass, write a unit test for each public method in your "Business Logic Layer".

If I had a class like this:

   public class AccountService
    {
        public void DebitAccount(int accountNumber, double amount)
        {

        }

        public void CreditAccount(int accountNumber, double amount)
        {

        }

        public void CloseAccount(int accountNumber)
        {

        }
    }

The first thing I would do before I wrote any code knowing that I had these actions to perform would be to start writing unit tests.

   [TestFixture]
    public class AccountServiceTests
    {
        [Test]
        public void DebitAccountTest()
        {

        }

        [Test]
        public void CreditAccountTest()
        {

        }

        [Test]
        public void CloseAccountTest()
        {

        }
    }

Write your tests to validate the code you've written to do something. If you iterating over a collection of things, and changing something about each of them, write a test that does the same thing and Assert that actually happened.

There's a lot of other approaches you can take, namely Behavoir Driven Development (BDD), that's more involved and not a great place to start with your unit testing skills.

So, the moral of the story is, test anything that does anything you might be worried about, keep the unit tests testing specific things that are small in size, a lot of tests are good.

Keep your business logic outside of the User Interface layer so that you can easily write tests for them, and you'll be good.

I recommend TestDriven.Net or ReSharper as both easily integrate into Visual Studio.

Code Monkey
A: 

well if you think it can break, write a test for it. I usually don't test setter/getter, but lets says you make one for User.Name, which concatenate first and last name, I would write a test so if someone change the order for last and first name, at least he would know he changed something that was tested.

pmlarocque
A: 

The canonical answer is "test anything that can possibly break." If you are sure the properties won't break, don't test them.

And once something is found to have broken (you find a bug), obviously it means you need to test it. Write a test to reproduce the bug, watch it fail, then fix the bug, then watch the test pass.

Eric Normand
A: 

You should test everything. Right now you have getters and setters, but one day you might change them somewhat, maybe to do validation or something else. The tests you write today will be used tomorrow to make sure everything keeps on working as usual. When you write test, you should forget considerations like "right now it's trivial". In an agile or test-driven context you should test assuming future refactoring. Also, did you try putting in really weird values like extremely long strings, or other "bad" content? Well you should... never assume how badly your code can be abused in the future.

Generally I find that writing extensive user tests is on one side, exhausting. On the other side, though it always gives you invaluable insight on how your application should work and helps you throw away easy (and false) assumptions (like: the user name will always be less than 1000 characters in length).

Sklivvz
A: 

I would recommend writing multiple tests for your Authenticate and Save methods. In addition to the success case (where all parameters are provided, everything is correctly spelled, etc), it's good to have tests for various failure cases (incorrect or missing parameters, unavailable database connections if applicable, etc). I recommend Pragmatic Unit Testing in C# with NUnit as a reference.

As others have stated, unit tests for getters and setters are overkill, unless there's conditional logic in your getters and setters.

Scott A. Lawrence
A: 

For simple modules that may end up in a toolkit, or in an open source type of project, you should test as much as possible including the trivial getters and setters. The thing you want to keep in mind is that generating a unit test as you write a particular module is fairly simple and straight forward. Adding getters and setters is minimal code and can be handled without much thought. However, once your code is placed in a larger system, this extra effort can protect you against changes in the underlying system, such as type changes in a base class. Testing everthing is the best way to have a regression that is complete.

Dirigible
+1  A: 

Whilst it is possible to correctly guess where your code needs testing, I generally think you need metrics to back up this guess. Unit testing in my view goes hand in hand with code-coverage metrics.

Code with lots of tests but a small coverage hasn't been well tested. That said, code with 100% coverage but not testing the boundry and error cases is also not great.

You want a balance between high coverage (90% minimum) and variable input data.

Remember to test for "garbage in"!

Also, a unit-test is not a unit-test unless it checks for a failure. Unit-tests that don't have asserts or are marked with known exceptions will simply test that the code doesn't die when run!

You need to design your tests so that they always report failures or unexpected/unwanted data!

Ray Hayes
+1  A: 

It makes our code better... period!

One thing us software developers forget about when doing test driven development is the purpose behind our actions. If a unit test is being written after the production code is already in place, the value of the test goes way down (but is not completely lost).

In the true spirit for unit testing, these tests are not primarily there to "test" more of our code; or to get 90%-100% better code coverage. These are all fringe benefits of writing the tests first. The big payoff is that our production code ends be be written much better due to the natural process of TDD.

To help better communicate this idea, the following may be helpful in reading:

The Flawed Theory of Unit Tests
Purposeful Software Development

If we feel that the act of writing more unit tests is what helps us gain a higher quality product, then we may be suffering from a Cargo Cult of Test Driven Development.

Scott Saad
I disagree with the assertion that unit tests don't have value after production code is already in place. Such assertions don't account for their utility in replicating error conditions found in production, or in the understanding of code inherited from a previous developer or team.
Scott A. Lawrence
I may have come across incorrectly. I didn't mean that unit tests don't have value after production code is in place. However, their value goes down. The biggest benefit to unit testing comes from the inherent magic that occurs when we let them drive our production development.
Scott Saad
A: 

I second test anything that can possibly break and don't write silly tests. But the most important tenet is test anything that you find is broken: if some method behaves oddly write a test to outline the data set that makes it fail, then correct the bug and watch the bar go green. Also test the "boundary" data values (null, 0, MAX_INT, empty lists, whatever).

Manrico Corazzi
A: 

When writing unit tests, or really any test, you determine what to test by looking at the boundary conditions of what you're testing. For example, you have a function called is_prime. Fortunately, it does what it's name implies and tells you whether the integer object is prime or not. For this I am assuming you are using objects. Now, we would need to check that valid results occurred for a known range of prime and non-prime objects. That's your starting point.

Basically, look at what should happen with a function, method, program, or script, and then at what should definitely not happen with that same code. That's the basis for your test. Just be prepared to modify your tests as you become more knowledgeable on what should be happening with your code.

Dan
+2  A: 

Another canonical answer. This, I believe, from Ron Jeffries: Only test the code that you want to work.

A: 

Writing code that has no value is always a bad idea. Since the proposed test adds no value to your project (or very close to it). Then your are wasting valuable time that you could spend writing code that actually brings value.

pjesi
+10  A: 

This got me into unit testing and it made me very happy

We just started to do unit testing. For a long time I knew it would be good to start doing it but I had no idea how to start and more importantly what to test.

Then we had to rewrite an important piece of code in our accounting program. This part was very complex as it involved a lot of different scenarios. The part I'm talking about is a method to pay sales and/or purchase invoices already entered into the accounting system.

I just didn't know how to start coding it, as there were so many different payment options. An invoice could be $100 but the customer only transferred $99. Maybe you have sent sales invoices to a customer but you have also purchased from that customer. So you sold him for $300 but you bought for $100. You can expect your customer to pay you $200 to settle the balance. And what if you sold for $500 but the customer pays you only $250?

So I had a very complex problem to solve with many possibilities that one scenario would work perfectly but would be wrong on an other type of invocie/payment combination.

This is where unit testing came to the rescue.

I started to write (inside the test code) a method to create a list of invoices, both for sales and purchases. Then I wrote a second method to create the actual payment. Normally a user would enter that information through a user interface.

Then I created the first TestMethod, testing a very simple payment of a single invoice without any payment discounts. All the action in the system would happen when a bankpayment would be saved to the database. As you can see I created an invoice, created a payment (a bank transaction) and saved the transaction to disk. In my asserts I put what should be the correct numbers ending up in the Bank transaction and in the linked Invoice. I check for the number of payments, the payment amounts, the discount amount and the balance of the invoice after the transaction.

After the test ran I would go to the database and double check if what I expected was there.

After I wrote the test, I started coding the payment method (part of the BankHeader class). In the coding I only bothered with code to make the first test pass. I did not yet think about the other, more complex, scenarios.

I ran the first test, fixed a small bug until my test would pass.

Then I started to write the second test, this time working with a payment discount. After I wrote the test I modified the payment method to support discounts.

While testing for correctness with a payment discount, I also tested the simple payment. Both tests should pass of course.

Then I worked my way down to the more complex scenarios.

1) Think of a new scenario

2) Write a test for that scenario

3) Run that single test to see if it would pass

4) If it didn't I'd debug and modify the code until it would pass.

5) While modifying code I kept on running all tests

This is how I managed to create my very complex payment method. Without unit testing I did not know how to start coding, the problem seemed overwhelming. With testing I could start with a simple method and extend it step by step with the assurance that the simpler scenarios would still work.

I'm sure that using unit testing saved me a few days (or weeks) of coding and is more or less guaranteeing the correctness of my method.

If I later think of a new scenario, I can just add it to the tests to see if it is working or not. If not I can modify the code but still be sure the other scenarios are still working correctly. This will save days and days in the maintenance and bug fixing phase.

Yes, even tested code can still have bugs if a user does things you did not think of or prevented him from doing

Below are just some of tests I created to test my payment method.

public class TestPayments
{
    InvoiceDiaryHeader invoiceHeader = null;
    InvoiceDiaryDetail invoiceDetail = null;
    BankCashDiaryHeader bankHeader = null;
    BankCashDiaryDetail bankDetail = null;



    public InvoiceDiaryHeader CreateSales(string amountIncVat, bool sales, int invoiceNumber, string date)
    {
        ......
        ......
    }

    public BankCashDiaryHeader CreateMultiplePayments(IList<InvoiceDiaryHeader> invoices, int headerNumber, decimal amount, decimal discount)
    {
       ......
       ......
    }


    [TestMethod]
    public void TestSingleSalesPaymentNoDiscount()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 1, "01-09-2008"));
        bankHeader = CreateMultiplePayments(list, 1, 119.00M, 0);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(1, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(119M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(0M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
    }

    [TestMethod]
    public void TestSingleSalesPaymentDiscount()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 2, "01-09-2008"));
        bankHeader = CreateMultiplePayments(list, 2, 118.00M, 1M);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(1, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(118M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(1M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
    }

    [TestMethod]
    [ExpectedException(typeof(ApplicationException))]
    public void TestDuplicateInvoiceNumber()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("100", true, 2, "01-09-2008"));
        list.Add(CreateSales("200", true, 2, "01-09-2008"));

        bankHeader = CreateMultiplePayments(list, 3, 300, 0);
        bankHeader.Save();
        Assert.Fail("expected an ApplicationException");
    }

    [TestMethod]
    public void TestMultipleSalesPaymentWithPaymentDiscount()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("119", true, 11, "01-09-2008"));
        list.Add(CreateSales("400", true, 12, "02-09-2008"));
        list.Add(CreateSales("600", true, 13, "03-09-2008"));
        list.Add(CreateSales("25,40", true, 14, "04-09-2008"));

        bankHeader = CreateMultiplePayments(list, 5, 1144.00M, 0.40M);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(4, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(118.60M, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(400, bankHeader.BankCashDetails[0].Payments[1].PaymentAmount);
        Assert.AreEqual(600, bankHeader.BankCashDetails[0].Payments[2].PaymentAmount);
        Assert.AreEqual(25.40M, bankHeader.BankCashDetails[0].Payments[3].PaymentAmount);

        Assert.AreEqual(0.40M, bankHeader.BankCashDetails[0].Payments[0].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[2].PaymentDiscount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].PaymentDiscount);

        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[3].InvoiceHeader.Balance);
    }

    [TestMethod]
    public void TestSettlement()
    {
        IList<InvoiceDiaryHeader> list = new List<InvoiceDiaryHeader>();
        list.Add(CreateSales("300", true, 43, "01-09-2008")); //Sales
        list.Add(CreateSales("100", false, 6453, "02-09-2008")); //Purchase

        bankHeader = CreateMultiplePayments(list, 22, 200, 0);
        bankHeader.Save();

        Assert.AreEqual(1, bankHeader.BankCashDetails.Count);
        Assert.AreEqual(2, bankHeader.BankCashDetails[0].Payments.Count);
        Assert.AreEqual(300, bankHeader.BankCashDetails[0].Payments[0].PaymentAmount);
        Assert.AreEqual(-100, bankHeader.BankCashDetails[0].Payments[1].PaymentAmount);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[0].InvoiceHeader.Balance);
        Assert.AreEqual(0, bankHeader.BankCashDetails[0].Payments[1].InvoiceHeader.Balance);
    }
eroijen
A: 

The best rule of thumb I've seen is to test everything that you can't tell at a glance, for certain, will work properly. Anything more and you wind up testing the language/environment.

Dave Falkner