51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1961|回复: 0
打印 上一主题 下一主题

Making your tests withstand design and interface changes

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2005-12-31 17:40:23 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
One of the biggest pitfalls developers writing unit tests face is in the face of a re-design. They write hundreds of unit tests for various classes, and suddenly a design change breaks many of them - a default constructor is removed, parameters are added or removed from interfaces, various default conditions change - havoc rears its ugly head.
The poor developer is then faced with "fixing" hundreds of tests just because a method was added to a constructor.

To remove this obstacle from the developer one must take the precaution in writing the tests. There are several rules you should abide by that will save your hiny on most events where such a change occurs:
here are some guidelines:

Encapsulate object creation code in helper methods inside your test fixture. If just your fixture is creating these objects - these methods should reside as private methods on the fixture. If more classes use them, they should be refactored out into a separate helper class with static helper methods for this creation (also known as the "Object Mother" pattern).

   for example:
      [Test]
      public void Sum_ReturnsZeroByDefault()
      {
         Calculator calc = new Calculator();
         int result = calc.Sum();
         ...
      }

should be refactored into:

        [Test]
      public void Sum_ReturnsZeroByDefault()
      {
         Calculator calc = setup_CreateCalculator();
         int result = calc.Sum();
         ...
      }

      private Calcualtor setup_CreateCalcualtor()
      {
         return new Calculator();
      }

notice the following:
After this change, any changes to the Calculator's public constructor should only propagate to a small "fix" in one private method in the fixture, used by many tests.
The helper method has a special prefix "setup_" to denote that it is a helper method used to setup object instances for test cases. It also makes your test more readable.
If multiple test fixture need to use the Calculator object, the code might turn into:

  [Test]
      public void Sum_ReturnsZeroByDefault()
      {
         Calculator calc = setup_CreateCalculator();
         int result = calc.Sum();
         ...
      }

      private Calcualtor setup_CreateCalcualtor()
      {
         return CalctulatorTestHelper.CreateDefaultCalculator();
      }

         public class CalctulatorTestHelper
      {
               public static Calculator CreateDefaultCalculator()
         {
                     return new Calculator();
         }
      }

notice the following:
We still keep the original setup_ helper method so that we change as little code as we need to in the old fixture. We just delegate the work into the CalculatorHelperobject. This level of indirection might not be needed but might help us in the future if we find we need to actually make a change to the CalculatorHelper class.
None of this code sits in production, but is only related to the testing project

Encapsulate complex or lengthy object initialization code  or complex interaction code into helper methods in your fixture or helper objects. If you find that you have two tests that initialize an object's state to the same state - refactor that code into a different method that returns the object with the known state. This helps future changes to the various object methods and default state behavior not break your tests and you bursting into tears.
for example:

      [Test]
      [ExpectedException(typeof(Exception),"User cannot be added twice")
      public void AddUser_AddingSameUserTwiceThrowsException()
      {
         LoginManager lm = setup_CreateDefaultLoginManager();
                  lm.AddUser("a","b");
         //exception should be thrown here
                  lm.AddUser("a","");
      }

      [Test]
      public void ChangePassword_AddingSameUserTwiceThrowsException()
      {
         LoginManager lm = setup_CreateDefaultLoginManager();
                  lm.AddUser("a","b");

         lm.ChangePassword("a","c");
                  bool isLoginOk = lm.IsLoginOK("a","c");
                  Assert.IsTrue(isLoginOk,"Login should have been accepted for user with the new password"
      }
      private LoginManager setup_CreateDefaultLoginManager()
      {
         return new LoginManager();
      }

   should be replaced with something along the lines of:

      [Test]
      [ExpectedException(typeof(Exception),"User cannot be added twice")
      public void AddUser_AddingSameUserTwiceThrowsException()
      {
         LoginManager lm = setup_CreateLoginManagerIwhtOneDefaultUser()
         
         //exception should be thrown here
                  lm.AddUser("a","");
      }

      [Test]
      public void ChangePassword_AddingSameUserTwiceThrowsException()
      {
         LoginManager lm = setup_CreateLoginManagerIwhtOneDefaultUser

         lm.ChangePassword("a","c");
                  bool isLoginOk = lm.IsLoginOK("a","c");
                  Assert.IsTrue(isLoginOk,"Login should have been accepted for user with the new password"
      }
      
      private LoginManager setup_CreateLoginManagerIwhtOneDefaultUser()
      {
                  LoginManager lm = setup_CreateDefaultLoginManager();
                  lm.AddUser("a","b");
         return lm;
      }

some points on this:
You could also use various helper methods that take parameters (for example, a login manager with N users already added to it to check various counts
You should use this method whenever you find duplication in your tests. Don't do it until you see duplication in your test code. Your primary concern is to make the test work, then refactor the test code as well as the production code.
If you continue on with this method your tests will become very robust and be able to adept to new requirements in your code with little effort.
Note that this method helps when the design change does not affect the logic behind your test. For example, the method of adding a user to LoginManager might change, but Changing a password for the user (as depicted above) should still be tested the same. If the design affects your test logic - there's no escape but changing the test. You should be very wary of changing your tests just to make them pass. Sometimes they break because they are telling you that you broke something in your logic.
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-27 03:11 , Processed in 0.065000 second(s), 28 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表