views:

244

answers:

3

I have never written unit tests before, for various reasons. I have a chance to write tests now, comfortably, because I have a small app to make from scratch.

However, I'm a bit puzzled. The application is supposed to use a printer with a smart card reader to program data on a smart card. So here's the sequence of actions: Create device context, set printer mode, initialize a document, feed a card into the printer, connect to the card with a reader, write something to the card, move the card out, end document, dispose of device context.

Okay, unit tests are supposed to test one function for each test, and each test is supposed to run independently of the result of other tests. But let's see - I can not test writing to smart card if I did not position it properly in the printer and if I have not connected to it. And I can not mock this by software - I can only test if writing actually happened if the real card is positioned properly and connected to. And if connecting to card will fail, there's no way to test writing to card - so the test independence principle is broken.

So far I came up with a test like this (there are also other test which are 'proper' and test other things too)

[Test]
public void _WriteToSmartCard()
{
 //start print job
 printer = new DataCardPrinter();
 reader = new SCMSmartCardReader();
 di = DataCardPrinter.InitializeDI();
 printer.CreateHDC();
 Assert.AreNotEqual(printer.Hdc, 0, "Creating HDC Failed");
 Assert.Greater(di.cbSize, 0);

 int res = ICE_API.SetInteractiveMode(printer.Hdc, true);
 Assert.Greater(res, 0, "Interactive Mode Failed");

 res = ICE_API.StartDoc(printer.Hdc, ref di);
 Assert.Greater(res, 0, "Start Document Failed");

 res = ICE_API.StartPage(printer.Hdc);
 Assert.Greater(res, 0, "Start Page Failed");

 res = ICE_API.RotateCardSide(printer.Hdc, 1);
 Assert.Greater(res, 0, "RotateCardSide Failed");

 res = ICE_API.FeedCard(printer.Hdc, ICE_API.ICE_SMARTCARD_FRONT + ICE_API.ICE_GRAPHICS_FRONT);
 Assert.Greater(res, 0, "FeedCard Failed");

 bool bRes = reader.EstablishContext();
 Assert.True(bRes, "EstablishContext Failed");

 bRes = reader.ConnectToCard();
 Assert.True(bRes, "Connect Failed");

 bRes = reader.WriteToCard("123456");
 Assert.True(bRes, "Write To Card Failed");

 string read = reader.ReadFromCard();
 Assert.AreEqual("123456", read, "Read From Card Failed");

 bRes = reader.DisconnectFromCard();
 Assert.True(bRes, "Disconnect Failde");

 res = ICE_API.SmartCardContinue(printer.Hdc, ICE_API.ICE_SMART_CARD_GOOD);
 Assert.Greater(res, 0, "SmartCardContinue Failed");

 res = ICE_API.EndPage(printer.Hdc);
 Assert.Greater(res, 0, "End Page Failed");

 res = ICE_API.EndDoc(printer.Hdc);
 Assert.Greater(res, 0, "End Document Failed");
}

The test is working, but the principles are broken - it tests multiple functions, and a lot of them. And each following function depends on the outcome of the previous one. Now, we come to the question: How should I approach unit testing in these circumstances?

+3  A: 

There isn't anything inherently wrong with a series of tests that depend on eachother, other than that you will not get a complete list of failures if multiple things are broken, because the first test to fail will be the one reported.

One way you could fix this though is by creating a test-initialization routine (using the [SetUp] attribute in your [TestFixture] class) that brings the system into a known state before doing a single test.

Note also though that this scenario is not entirely suited to unit testing, because it requires potential manual steps outisde of software. Unit tests are inherently better suited to testing software modules that do not interact with anything non-reproducible. You may want to make the operations on the reader API abstract (by creating an interface for the operations you need, and a class that passes these calls through to the actual API), and then you can use a mock object to pretend to be the reader so you can test the main logic of your class(es) without having to rely on hardware.

Then you can implement the testing of the actual real API, either into a unit test, or into something else that requires some minimal human interaction to do... basically you'd be encapsulating the human in your test process ;)

jerryjvl
+4  A: 

This doesn't look like an unit test. Unit test should be fast and assertive, i.e., you should not need to check if an operation acctually happened in a hardware. I would classify this code as "test automatization", as you need to execute this task and be sure that something happened.

The code is also procedural and looks hard to test. The use of several assertions in the same test method indicates that it should be divided.

My prefered reference for unit testing is Misko Hevery's site. Hope it helps!

David Reis
Well that's what my question was about - it is not really a unit test! I have other tests that look more like unit tests, i.e. [Test] public void ConverLongStringToHexArray() { byte[] expected = new byte[] { 0x31, 0x32, 0x33, 0x34, 0x35, 0x31, 0x32, 0x33, 0x34, 0x35, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; byte[] result = reader.StringToHexArray("1234512345123456zzzzzzzzzzzzzzzzzzzzzzzzz"); Assert.AreEqual(expected, result, "Error Converting Long String To Byte Array"); }But the big one is not easy to divideThanks for the link btw, very useful
Evgeny
Placing code in comments is a bad idea ...
Evgeny
+4  A: 

Your test code is what is often referred to as an integration test. In short, integration tests are often defined as tests that check the integration between components of a system. While, as David Reis mentions, unit tests will often test individual methods.

Both classes of tests are useful. Integration tests, like yours, exercise the system from start to finish making sure that everything is working together nicely. But they are slow and often have outside dependencies (like a card reader). Unit tests are smaller, faster and highly focused but it's hard to see the forest for the trees if all you have are unit tests.

Place your unit tests in a separate directory from your integration tests. Use continuous integration. Run your integration tests maybe only a few times a day because they are slower and require more setup/deployment. Run your unit tests all the time.

Now, how do you unit test your particular situation where methods depend on other methods? It's unclear how much code you control vs how much is in the libraries, but in your code, learn to use Dependency Injection (DI) as much as possible.

Suppose your reader method looks something like this (in pseudocode)

boolean WriteToCard(String data){
   // do something to data here
   return ICE_API.WriteToCard(ICE_API.SOME_FLAG, data)
}

Well you ought to be able to change this to something like :

    ICE_API api = null

    ICE_API setApi(ICE_API api) {
         this.api = api
    }

    ICE_API getApi() {
      if (api == null) {
        api = new ICE_API()
      }
    }

    boolean WriteToCard(String data){
        // do something to data here    
        return getApi().WriteToCard(ICE_API.SOME_FLAG, data)
    }

Then in your test for WriteToCard in the setup you would do

void setup()
  _mockAPI = new Mock(ICE_API)
  reader.setApi(_mockAPI)

void testWriteToCard()
  reader.writeToCard("12345")
   // assert _mockAPI.writeToCard was called with expected data and flags.
CNelson
Sorry, I don't know C# it may have better ways to do DI. Also, in java, doing DI with static methods kind of sucks, you can wrap the static calls in a method then in your test stub out those wrapper methods to do your assertions.
CNelson
Mocking things does not seem to be useful here: I can not test writing to the card if I 'mock' positioning the card. The card has to be there physically. So I depend on printer being on, card being properly fed etc. before I can really test 'WriteToCard'.I guess I'll go with calling this test an integration test and separating it from the rest.
Evgeny