一个类 A,我要对它某方法(取名为x)的代码进行测试,在x中会创建 窗口 B,并且执行B中的y方法。
代码如下:
ClsA.cs
    class ClsA
    {
        public void x()
        {
            FrmB oF = new FrmB();
            oF.y();            int j = 0;
            int k = 5 / j;
        }
    }FrmB.cs
    public partial class FrmB : Form
    {
        public FrmB()
        {
            InitializeComponent();
        }        public void y()
        {
            MessageBox.Show("运行了很多复杂代码!");
        }
    }测试代码ClsATest.cs
        /// <summary>
        /// x 的测试
        /// </summary>
        [TestMethod()]
        public void xTest()
        {
            ClsA target = new ClsA();
            target.x();
        }这个测试存在的问题:在ClsA.x()中,启动了 FrmB ,并且“运行了很多复杂代码!”。

解决方案 »

  1.   

    如果参考《单元测试之道C#版-使用Nunit》第6章对Now的做法,我要修改 ClsA ,让其使用接口 IfFrmB,代码改为如下:
    ClsA.cs
        class ClsA
        {
            IfFrmB oF;        public ClsA(IfFrmB pF)
            {
                this.oF = pF;
            }        public void x()
            {
                ////FrmB oF = new FrmB();
                this.oF.y();            int j = 0;
                int k = 5 / j;
            }
        }
    MockFrmB.cs
        class MockFrmB : IfFrmB
        {
            public void y()
            {
                // 啥事都不干,直接返回即可
                return;
            }
        }测试代码ClsATest.cs:
            /// <summary>
            /// x 的测试
            /// </summary>
            [TestMethod()]
            public void xTest()
            {
                ClsA target = new ClsA(new MockFrmB()); 
                target.x();
            }
    想问各位大师,这种做法对吗?如果对,那这种做法有几个明显问题,如何避免?
    比如,ClsA 就会多一个 IfFrmB 变量(并且我这一次是模仿书中代码,在类构造函数就引入),对较大的系统来说,不见得是个好事。
      

  2.   

    还有的问题,我为了上个代码,还增加了两个文件:
        interface IfFrmB
        {
            void y();
        }

        class MockFrmB : IfFrmB当然,我也可以对void x() 进行改变:
    ClsA.cs
        class ClsA
        {
            public void x(IfFrmB oF)
            {
                oF.y();            int j = 0;
                int k = 5 / j;
            }
        }测试代码ClsATest.cs:
            /// <summary>
            /// x 的测试
            /// </summary>
            [TestMethod()]
            public void xTest()
            {
                ClsA target = new ClsA(); 
                target.x(new MockFrmB());
            }这是不是已经足够好了?是不是还有更好的方案更简洁地“自己”建立mock?
      

  3.   

    回复的人太少,估计:一、大家对编程兴趣,对测试不太注意;二、大家使用现成的Mock框架,所以不必自己建立Mock;三、可能我发错版块了,后面才发现有“质量管理/软件测试”版块。
    我主要是对 《单元测试之道C#版-使用Nunit》第6章对Now的做法的疑问。我本来是想按它的建议进行调整,但实际尝试中,按它的做法将对接口进行改变,很多编程习惯都不能遵守。这个应该不是理想的。我这几天调整出来的实际的结构,仍然按我对 MessageBox 的处理那样。先按几个原则:一、代码测试覆盖率追求100%;二、测试一个方法,不测试它调用的“复杂”方法(例如会产生用户操作的,弹出界面的,代码较多的,相关类较多的)。
    用一个静态测试属性来表明当前处于测试状态,对于复杂方法,直接使用测试员预计的值返回(预计一个方法返回值应该不是困难的事情)。这样测试某个“方法A”时,测试员列出对复杂方法的预计值,这样测试代码跳过这些复杂方法直接返回预计值,我们就可以达到仅测试“方法A”本身。
    存在的问题就是,因为判断是否在测试状态,所以代码覆盖率就不可能达到100%。希望能引起大家的一些讨论。
      

  4.   

    如果你注重测试的话,应该先写测试代码,再写实际程序,这样就会强迫你在写实际程序时为测试留好接口。在ClsA中用new FrmB()这样的写法,就表示ClsA和FrmB是紧密联系在一起的。FrmB的改变可能会影响ClsA,如果想复用ClsA的话,ClsA也必须和FrmB一起作为一个整体被引用。而通过构造函数传递IfFrmB接口这种IoC/DI方式,则ClsA和FrmB没有直接联系。只要IfFrmB接口稳定,对FrmB的修改不影响ClsA。
      

  5.   

    如果你测试的方法位于业务层,它本来就应该和界面是分离的。不应该在测试时用开关来跳过这些UI方法的调用,而是应该设计时就采用依赖注入的方式,把UI作为一个服务接口传到这个方法所在的类,测试时也很容易伪造一个ui的mock对象,提供一些假的输入,来测试这个方法的各种分支。
    而UI本身的测试,通常是用ui automation工具,来模拟鼠标、键盘的操作,看界面的反应是否正常,这个和一般的代码测试是不同的。
      

  6.   

    TO:caozhy 
    "只因为你是在重复造轮子。"
    这是一个度的问题(或平衡问题)。
    我的经验是,如果有能力自己造轮子的话,尽量自己造,这样你造车子时,就不会受限于轮子厂商。因为在VB阶段,我建立了自己的控件体系(文本框、按钮、工具栏……甚至整个界面),它们内部能协调配合,才能让我快速地完成企业中需要的系统。(两个例子:一是郭台铭发展过程中,就是因为有自己的模具加工厂,所以让他能比其它小工厂提供更客制化的产品。二是我们公司沙滩车的发动机就只能使用某公司的几款型号,我们公司没有能力制造发动机,研发人员都在说笑“如果对方不卖发动机给我们,我们就白开发了”)当然如果超出能力之外的,那才是依依不舍地放弃,比如我在VB中就没建立出UI的自动测试体系,原因是我对系统的API层,GUI相关API就没有深入掌握过,所以我的软件连产品终检都没有,质量保证连永远是我心中的痛。
      

  7.   

    TO:jshi123 
    “如果你注重测试的话,应该先写测试代码,再写实际程序”
    惭愧,我还没有这种意识,更没有实际操作的经验。目前我看到VS2008能对单元测试提供这么方便的操作,就已经很欣喜若狂,要发展到那一层次估计还要一段时间。
      

  8.   

    “通过构造函数传递IfFrmB接口这种IoC/DI方式”
    虽然它有了灵活性,但站在程序员的角度上看,它让代码增加复杂性,而且增加的还不少。为了解耦,我要加入这么多代码,我感觉有些不划算。
    而目前,我只是因为测试,所以对解耦有一定的需求,如果抛开自动化测试,这两个类基本上就是“同生共死”。所以我想的是,为了解耦,有没有一种方案,即能让代码复杂性不会提高很多,又能实现ClsB的替身注入,以我目前水平,就只有想到通过状态判断来进行分支。
      

  9.   

    CSDN同一帐号不能连续回复3次?换个马甲继续。
    我对业务层、用户层还是无法在实际中进行区分、应用。理论上讲都很简单,但当我面对几万行的代码中的时候,根本不会想到这是业务层还是用户层,让我与我的部下轻松地开发出用户可用的系统为首要任务。比如报表打印界面,这是业务层还是用户层,我就区分不出来,我们目前的做法只要求程序员先在一个自制的工具上配置报表(如报表所在的界面,报表模板的文件名,报表所用的SQL语句,报表长,报表宽……),然后在程序中写上 ClsA.DispReport(); 剩下就是ClsA的事情了,程序员可以不关心。ClsA甚至可以完成:如果操作用户点击报表预览时出错,可以让用户决定是否要把当前屏幕抓图发送给程序员。
    当把这个ClsA做好,自己高兴都来不及了,就没有动力想着去区分业务层、用户层。
      

  10.   

    谢谢 sp1234 ,caozhy ,jshi123 的回复,这贴先结了,后面还会继续麻烦各位各类问题。(CSDN贴子结了好像也可以继续回复)