Spiga

与protected成员有关的单元测试方式

2009-08-28 17:33 by Jeffrey Zhao, 4343 visits, 收藏, 编辑

这是一篇简单的文章,讨论了单元测试中遇到protected成员的应对方案。此外,在文章最后也希望和大家讨论一下某个特殊的情况下的处理方法。

protected是一个有趣而有用的修饰符,它把方法的访问成员严格限制在自身或自己的子类身上。换句话说,在使用过程中,protected成员对外部是开放的(因为其他类可以通过继承来使用该成员),又是封闭的(不是自身或子类的一切成员都无法访问)。而对于单元测试来说,protected成员又是尴尬的,因为它的“开放”意味着我们必须对它进行单元测试,而“封闭”又阻碍了我们在单元测试中涉及protected成员。

测试protected方法

现在有一个类,其中包含一个protected方法:

public class SomeClass
{
    protected int SomeMethod(string arg) { ... }
}

如果我们需要对这个protected方法进行单元测试,可以在测试代码中准备一个辅助类型:

public class SomeClassForTest : SomeClass
{
    public int PublicSomeMethod(string arg)
    {
        return this.SomeMethod(arg);
    }
}

于是在单元测试中,便可以通过调用PublicSomeMethod来测试基类的SomeMethod方法:

var testClass = new SomeClassForTest();
var result = testClass.PublicSomeMethod(null);
Assert.Equal(0, result);

非常简单。

如果您觉得麻烦,也可以将SomeClass类中的SomeMethod方法改为protected internal,这样便可以在InternalVisibleTo的测试程序集中使用了。不过,我觉得为单元测试而改变成员的访问级别不是一个合适的做法。

对protected方法进行Mock

现在有一个类,其中有一个protected方法:

public class SomeClass
{
    protected virtual int SomeMethod(string arg) { ... }
}

并且,某个被测试的方法接受SomeClass作为参数。虽然被测试的方法不会直接调用SomeMethod方法,但是SomeMethod的实现会影响到公开接口的表现形式。于是,我们需要对SomeMethod进行Mock或Stub。为此,我们同样需要准备一个辅助类型:

public class MockSomeClass : SomeClass
{
    protected override int SomeMethod(string arg)
    {
        return this.PublicSomeMethod(arg);
    }

    public virtual int PublicSomeMethod(string arg)
    {
        return base.SomeMethod(arg);
    }
}

在MockSomeClass中,我们覆盖了基类的SomeMethod实现,使它调用了子类中公开的PublicSomeMethod方法,而PublicSomeMethod内部又调用了基类的SomeMethod方法。因此,如果您不去进行任何处理,那么MockSomeClass会保持SomeMethod的实现不变。而如果您需要对SomeMethod进行Mock或Stub的时候,便可以从PublicSomeMethod下手:

Mock<MockSomeClass> mockSomeClass = new Mock<MockSomeClass>() { CallBase = true };
mockSomeClass.Setup(c => c.PublicSomeMethod("123")).Returns(123);

DoSomeTest(mockSomeClass.Object); // use the mock object

也很容易。

为了可测试性

值得注意的是,为了“可测试性”,第二部分中的protected方法必须是virtual的,因为我们需要在子类中进行override。同理,Mock框架能够辅助的方法也必须是virtual的,即使是一个public方法。那么,您觉得这是为了可测试性而做出的让步吗?或者换句话说,您觉得,一个不可以override的protected方法,但是会影响到其他公开接口的功能,这是不是一个合理的设计呢?如果这是一个合理的设计,又不想作出这样的让步……我们又该怎么做呢?

关于这点,我有自己的想法,不过还是想先听一下您的意见。

Add your comment

39 条回复

  1. #1楼 Jooly      2009-08-28 17:40
    高产。。(我是沙发)
     回复 引用 查看   
  2. #2楼 DiggingDeeply      2009-08-28 17:40
    big chair
    Mock的谓词真的很可怕。
    老赵最近在整什么啊?测试?
     回复 引用 查看   
  3. #3楼 craboYang      2009-08-28 18:06
    您的测试需要覆盖这么细?
    我通常只在Facade层确认I/O就罢了。
    我只用NUnit,连Mock都没到用过,因为居然没需要-_-!
     回复 引用 查看   
  4. #4楼 singleView      2009-08-28 18:15
    赵老大,最近活跃啊,哪个IE6.0的提示是不是考虑给撤消了
     回复 引用 查看   
  5. #5楼 egmkang      2009-08-28 18:34
    @singleView
    除了网银,其他都不用IE,就别说6了
     回复 引用 查看   
  6. #6楼[楼主] Jeffrey Zhao      2009-08-28 18:48
    @craboYang
    测试覆盖率就70-80%总归会有的啊。
    不过现在只是谈了测试组件会遇到的情况,总归有用的,呵呵。
     回复 引用 查看   
  7. #7楼[楼主] Jeffrey Zhao      2009-08-28 18:49
    引用DiggingDeeply:
    老赵最近在整什么啊?测试?

    老老实实为一个ASP.NET MVC项目打基础。
     回复 引用 查看   
  8. #8楼[楼主] Jeffrey Zhao      2009-08-28 19:02
    @singleView
    不,严格抵制IE6。
     回复 引用 查看   
  9. #9楼 xiaotie      2009-08-28 19:46
    俺一般是提供一个测试桩:

    protected void SomeMethod()

    #if debug
    public void SomeMethodStub()
    {
    SomeMethod();
    }
    #endif

    testcase:

    #if debug
    public void TestSomeMethod()
    {
    ......
    }
    #endif
     回复 引用 查看   
  10. #10楼[楼主] Jeffrey Zhao      2009-08-28 19:57
    @xiaotie
    没看懂啊,这是什么意思?
     回复 引用 查看   
  11. #11楼 xiaotie      2009-08-28 20:15
    @Jeffrey Zhao
    把要测试的方法简单的放到另一个public方法之中暴露出来,然后外面围上
    #if DEBUG
    #endif
    确保不会再release中被暴露。
    然后把测试项目中的测试方法也用 #if DEBUG #endif 围起来。
    俺经常这样测试private方法。
     回复 引用 查看   
  12. #12楼 Arison[未注册用户]2009-08-28 20:35
    不是看不起你,除了写鸡毛蒜皮的东西,小儿科的理论,

    还能整除什么名堂,也就一个实习生的水平线,

    学了这么多东西到处去忽悠这么好那么好,

    根本不知道企业开发要用到什么,实际需求是什么,

    一纯拿技术取乐的人,一天到晚在这里反复着,为什么

     回复 引用   
  13. #13楼 vczh[未注册用户]2009-08-28 21:18
    譬如说partial class
     回复 引用   
  14. #14楼 vczh[未注册用户]2009-08-28 21:20
    @Arison
    难道平时可以打球,就不能折腾点代码?
     回复 引用   
  15. #15楼[楼主] Jeffrey Zhao      2009-08-28 21:59
    @Arison
    哎,其实我也挺可怜您的,您说您一个看到技术就苦大仇深的人,怎么能体会到我这个拿技术取乐的人的幸福呢?
    有一点您说的没错,我是不知道企业开发要用什么,因为我是做互联网的。信不信由您,不少公司都挺看得上我的呢。
     回复 引用 查看   
  16. #16楼[楼主] Jeffrey Zhao      2009-08-28 21:59
    @vczh
    什么partial class?
     回复 引用 查看   
  17. #17楼 王德水      2009-08-28 22:20
    我们现在只要求开发人员能保证自己写的代码还有是要有单元测试,要求代码没有bug,修改时用测试做保障,至于需不需要公开,那是第二步的,如果有好的架构师设计出可测试行高的代码,,但没有的时侯,public是解决可测试行更快的办法
     回复 引用 查看   
  18. #18楼[楼主] Jeffrey Zhao      2009-08-28 22:43
    @王德水
    嗯,用public是最快最方便的。
    不过其实,有时候只是在某些情况下“不知道怎么设计成可测试性”。
    我这些东西算是一种总结,也就是说,提供一种特定情况下的重构方式。
    Kent Beck帮这些东西取了个好名字,叫做“实现模式”,赫赫。
     回复 引用 查看   
  19. #19楼 Arison[未注册用户]2009-08-28 22:48
    引用Jeffrey Zhao:
    @Arison
    哎,其实我也挺可怜您的,您说您一个看到技术就苦大仇深的人,怎么能体会到我这个拿技术取乐的人的幸福呢?
    有一点您说的没错,我是不知道企业开发要用什么,因为我是做互联网的。信不信由您,不少公司都挺看得上我的呢。


    为什么你会知道我怎么看待技术,又何来苦大仇深,其实你根本就是猜的,无中

    生有,无疑你确实异想天开的那头了

    为什么你就一定知道一个企业开发就是和网络无关,这也是你凭空捏赵,又有谁

    会去相信一个一厢情愿,本来就没有什么干戏


     回复 引用   
  20. #20楼 Colin Han      2009-08-28 22:56
    引用Jeffrey Zhao:
    @vczh
    什么partial class?

    就是把一个类型写在两个文件里面。一个是产品用的,该私有的就私有。另外一个,包了一组供测试用的Wrapper方法。
     回复 引用 查看   
  21. #21楼[楼主] Jeffrey Zhao      2009-08-28 22:59
    @Arison
    我说的是“互联网应用”,不是“和网络有关的应用”,赫赫,我们还是明确一下定义比较好。
    至于我对您的猜测,嗯嗯,的确是我错了,希望您不要介意。
    不过我还是推测,您一定是个成熟的科学家,所以对于我搞这些工程上的,或者趣味性的东西不屑一顾,委屈您了。
    不过,您能否给我一些指点,让我知道我该努力的方向呢?
     回复 引用 查看   
  22. #22楼[楼主] Jeffrey Zhao      2009-08-28 23:00
    @Colin Han
    partial class的概念和作用我清楚,但是partial class只是编辑上的概念,最终编译之后就和写在一个文件里没有区别了,这和单元测试有什么关系呢?
     回复 引用 查看   
  23. #23楼 Arison[未注册用户]2009-08-28 23:13
    引用Jeffrey Zhao:
    @Arison
    我说的是“互联网应用”,不是“和网络有关的应用”,赫赫,我们还是明确一下定义比较好。
    至于我对您的猜测,嗯嗯,的确是我错了,希望您不要介意。
    不过我还是推测,您一定是个成熟的科学家,所以对于我搞这些工程上的,或者趣味性的东西不屑一顾,委屈您了。
    不过,您能否给我一些指点,让我知道我该努力的方向呢?


    “互联网应用” 不是 “和网络有关的应用”,这就是你要说的吧

    为什么会介意,就为几句话不是很不值得,况且又是出自这么样的一个人

    不要把你的意愿强加给别人,没有人有你要得那义务



     回复 引用   
  24. #24楼[楼主] Jeffrey Zhao      2009-08-28 23:17
    @Arison
    我是说,我搞得是“互联网应用”,不是说我在搞“和网络有关的应用”。
    好吧好吧,咱们还是别讨论这个定义了。您能否给我指明一下,我现在做错了什么,又该朝什么方向努力呢?
     回复 引用 查看   
  25. #25楼 Colin Han      2009-08-28 23:38
    @Jeffrey Zhao
    可以建两个项目,一个给单元测试用,其中包含那个给测试用的Partial文件。另外一个是产品用的。不包含那个文件。
     回复 引用 查看   
  26. #26楼[楼主] Jeffrey Zhao      2009-08-28 23:42
    @Colin Han
    这个想法挺奇特的,不过需要在两个项目之间作同步,这点需要自己准备额外的工具或程序或机制什么的吧。
    你有没有这样做过?
     回复 引用 查看   
  27. #27楼 Nick Wang (懒人王)      2009-08-28 23:47
    @Arison
    首页帖子放眼望去,可以说有90%都是和我想法不一致的,可是不是不一致的就一定要批呢?

    都是有脑子的成年人,没人要你一定如何如何,你也没必要让别人如何。
     回复 引用 查看   
  28. #28楼 dali[未注册用户]2009-08-29 08:16
    private 怎么测试
     回复 引用   
  29. #29楼 韦恩卑鄙      2009-08-29 09:14
    @Arison
    不要自取其辱
    “王二用自己的脑袋和石头做了一个些微的较量”
    “王二输了”
     回复 引用 查看   
  30. #30楼 韦恩卑鄙      2009-08-29 09:29
    我觉得 为了测试把protected 增加virtual 也有坏味道
    理由也很简单 确定的逻辑对修改关闭

    public class MockSomeClass : SomeClass
    {
    
    
        public int SomeMethod(string arg, bool meaningLessParam)
        {
            return base.SomeMethod(arg);
        }
    }
    
    


    这样测试行不行呢 反正多个无用参数而已
     回复 引用 查看   
  31. #31楼[楼主] Jeffrey Zhao      2009-08-29 13:30
    @韦恩卑鄙
    真要这样用,总归还是可以的,不过我不太喜欢这个方式。
     回复 引用 查看   
  32. #32楼[楼主] Jeffrey Zhao      2009-08-29 13:31
    @dali
    下次谈,我倾向于不测试,把要测试的逻辑提取出去,因为要测试,往往意味着较为复杂。
     回复 引用 查看   
  33. #33楼 Nosira[未注册用户]2009-08-29 20:19
    @Arison
    最看不惯的就是你这种人,装得自己多高深似的
     回复 引用   
  34. #34楼 xiaowen[未注册用户]2009-08-30 16:09
    引用Nosira:
    @Arison
    最看不惯的就是你这种人,装得自己多高深似的


    就像在吃饭的时候听到嗡嗡嗡的声音...

     回复 引用   
  35. #35楼 vczh[未注册用户]2009-08-30 22:19
    @Jeffrey Zhao
    Colin Han同学充分了解了我的意思啊。你只要把一个class标记成partial,在test project里面写另一个partial与之配对就可以了。你一个Class一般都会有一个Test Class的:

    project:
    class A;
    test project:
    class ATest;

    ---->

    project:
    partial class A;
    test project:
    partial class A;这里暴露一些非公开的东西
    class ATest;

    所以不怎么需要维护。而且你接口变了,原本需要的修改只会变多一点点。
     回复 引用   
  36. #36楼[楼主] Jeffrey Zhao      2009-08-30 22:26
    @vczh
    partial class怎么可以跨project的?
    Colin Han说的只是在test时多编译一些partial class进去吧。
     回复 引用 查看   
  37. #37楼 vczh[未注册用户]2009-08-31 09:41
    @Jeffrey Zhao
    写prebuild event将代码复制过来就行了
     回复 引用   
  38. #38楼[楼主] Jeffrey Zhao      2009-08-31 09:45
    @vczh
    这和我之前想象地就比较类似了……不过实际操作起来,你能再仔细谈谈吗?
     回复 引用 查看   
  39. #39楼 似水之心      2009-08-31 15:54
    细到这种程度了啊

    仰慕一下
     回复 引用 查看   
发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 1555923 0IgWEaUi2Uw=