Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would you apply unit testing in this situation?

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?

like image 509
Evgeny Avatar asked Dec 23 '22 10:12

Evgeny


1 Answers

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.
like image 115
case nelson Avatar answered Jan 05 '23 12:01

case nelson