开发者

unit testing, how far to take it when black box testing?

Greetings,

I ran into a odd question.

I believe when testing code, you drop in parameters x, y, z into a function, you need to test the output to insure that the work was done properly. But what if your code uses a facade pattern or contacts an "internal" source that you can query, but that would quickly causes tight coupling issues in the test code.

Here is a example:

class Notifier

  def send_notification action_string
    # contact messaging server and deliver message
  end

开发者_如何学JAVAend

class DoSpecialStuff

  def my_method
    code.. code..
    n = Notifier.new
    n.send_notification "WOOT"
  end

end

Now, DoSpecialStuff.my_method doens't have any output, but the Notifier which is meant to abstract the "complexity" of where the output goes! Thus resulting in an untestable function!

Do we test this code by doing this?

def test
  assert_no_execption_raised do # hurm... a test in name only?
    o = DoSpecialStuff.new
    o.my_method
  end
end

Now, we can check where this message goes, but that takes away from the point of the facade object, we're trying to hide complexity.

We can test the output, but should we? What is right and proper?

-daniel


It helps to always keep in mind that the point of testing is to give you confidence that your code works, and will continue to work in the future.

This looks like the sort of place where interaction testing would be useful. Mock out the Notifier in DoSpecialStuff's test so you can be sure it's using the Notifier correctly. Higher up you may need to mock out DoSpecialStuff to test code that sits on top of it.


to allow your tests/code to scale neatly you should decouple your code using an interface, this makes updating tests after refactoring much simpler, otherwise by coupling your code you incur technical debt you could potentially add

  1. interface to decouple the code
  2. a mock for DoSpecialStuff to test that the decoupled code behaves as you expect

if you where writing the code in C++ it would look something like

Notifiers Code

Notifier.h
class Notifier : public I_Notifier
{
    void _sendNotification( NotificationInstance * notification );
};

Notifier.cpp
void Notifier::_sendNotification( NotificationInstance * notification ) 
{
  //do some stuff that needs testing 
}

Interface to the Notifier, this is what gets added to the DoSpecialStuff class to decouple the code I_Notifier.h

class I_Notifier
{
    void sendNotification( NotificationInstance * notification ) { _sendNotification(notification); }
    virtual void _sendNotification( NotificationInstance * notification )=0;
}

Mock used for testing aspects of DoSpecialStuff, our mock assumes that the notifiers code works our unit tests for DoSpecialStuff just want to make sure sendNotification got called, so our test can just check the state of send_notification_called to see if it was successful or not.

Mock_Notifier.h
struct Test_Notifier : public I_Notifier
{
  Test_Notifier() : send_notification_called(false)

  virtual void _sendNotification( NotificationInstance * instance ) 
  {
    send_notification_called = true;
  }
  bool send_notification_called;
};

DoSpecialStuff code Note that this class now has an interface to the notifier class which is what decouples the code (so our test no longer needs to include the actual class unless we are using calls from it

DoSpecialStuff.cpp
class DoSpecialStuff
{
  DoSpecialStuff( I_Notifier * n ) : notifier_(n) {}
  void DoSpecialStuff::_myMethod( NotificationInstance * notification ) 
  I_Notifier * notifier_;
}

void DoSpecialStuff::_myMethod( NotificationInstance * notification ) 
{
  //do some stuff that needs testing
  n.send_notification (notification )
}

Interface

I_DoSpecialStuff.h
class I_DoSpecialStuff
{
    void myMethod( NotificationInstance * notification ) { _myMethod(notification); }
    virtual void _myMethod( NotificationInstance * notification )=0;
}

Where the functionality is

  • contact messaging server and deliver message

on top of your unit tests you would also have a set of functional tests, so the unit tests would test each method of the classes (where appropriate), the functional test suite would start up an instance of your server and then the functional tests would call the functions that would contact the messaging server, then check the output (be it a database state or returned signal)


Another approach that may better suit your situation is move instantiation, and in your example possibly the method call, of the collaborating class to another method as such:

class DoSpecialStuff

  def my_method
    code.. code..
    send_notification        
  end

  def send_notifier
    n = Notifier.new
    n.send_notification "WOOT"
  end

end

Forgive me if my syntax is wrong, whatever language this is (Ruby?), I am not familiar.

Now to test, extend DoSpecialStuff and override send_notification so that it either does nothing or you can verify it was called with your test (I'm assuming there's some way of throwing a global variable around or some such).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜