代码设计/可测试性如何?

时间:2009-08-22 18:28:57

标签: unit-testing tdd

我的团队正在设计一个包含对Active Directory的调用以搜索并返回人员列表的库。

我们有一个人员课程,为找到的人提供信息。然后我们使用List来包装它们。当我们调用搜索时,它使用内部System.Directory库并返回一个SearchResultCollection对象。然后我们遍历它以构建列表<>并返回。

我们已将person类设计为仅具有只读(get)属性,因为我们不希望被调用者更改人员信息。我们从人员构造函数的System.Directory库中传入SearchResult对象。

我的问题是我们无法轻易测试。

到目前为止,我的想法是:

  1. 将变量传递给需要设置的每个属性的人员构造函数。

    不幸的是,这将构成一个非常长的构造函数参数列表....对我不好。

  2. 允许person类在属性上设置setter。

    再次,对我来说闻起来很糟糕,因为我们无法控制被叫者使用它。

  3. 重构:

    我已经查看了提取界面和适应参数技术。似乎适应参数最有希望? Adapt参数似乎很好,因为它确实有助于打破我对目录库的SearchResult对象的依赖性。因此,如果将来我想进行其他类型的搜索,我们就会处于良好状态。至少我认为我们是?

  4. 子类对象对象并使用setter ....创建测试人员。

    似乎它会起作用,但不确定它是否是正确的方法?

  5. 嘲笑

    还没有做过任何嘲弄,所以再也不确定这个。

  6. 编辑:如果嘲笑是最好的主意,请让我知道......但是,我有兴趣知道如何在没有嘲笑的情况下完成这项工作(或者如果没有嘲笑它真的不能做到)....

    我很感激这方面的指导。

    以下是代码片段:

        public class PeopleSearcher
    {
       .... declarations left out....
    
        public List<Person> FindPerson(string FirstName, string LastName, string Login)
        {
             ...filter setup left out for brevity....
    
             _peopleFound = _directoryToSearch.FindAll();
            //Convert to list of persons....
                int cnt = 0;
                _listOfPeople = new List<Person>();
                while (cnt < _peopleFound.Count)
                {
                    Person p = new Person(_peopleFound[0]);
                    _listOfPeople.Add(p);
                    cnt++;
                }
                return _listOfPeople;
            }
    
        }
    
        public class Person
        {
            private string sn;
            ....further declarations left out for brevity....
    
            public Person(SearchResult PersonFound)
            {
                sn = PersonFound.Properties["sn"].Count == 0 ? string.Empty : PersonFound.Properties["sn"][0].ToString();
                givenName = PersonFound.Properties["givenName"].Count == 0 ? string.Empty : PersonFound.Properties["givenName"][0].ToString();
                sAMAccountName = PersonFound.Properties["sAMAccountName"].Count == 0 ? string.Empty : PersonFound.Properties["sAMAccountName"][0].ToString();
                adsPath = PersonFound.Path == null ? string.Empty : PersonFound.Path;
    
            }
    
            public string LastName
            {
                get
                {
                    return sn;
                }
            }
    
            .... more getters...
         }
    }
    

3 个答案:

答案 0 :(得分:1)

“Mocking”这个词通常用于各种test doubles。大多数时候人们或者不“嘲笑”,他们是假装或捏造。无论如何,你的第四个选项(子类和添加setter)听起来像是给出代码库的最简单方法,假设你希望Person对象传递给其他方法。因为我不认为你在讨论测试人物对象是否被构造函数设置为正确的属性,对吧?

答案 1 :(得分:0)

嘲笑它。这就是嘲弄发明的那种情况。我只用Ruby做过模拟,所以我不确定.net的最新技术,但它应该运行良好。

在嘲笑它时你可能会意识到一些应该重构的区域。这也是一个很好的计划。

答案 2 :(得分:0)

在你的模拟中(通过框架或其他方式),你仍然需要创建具有值的Person对象,这会导致你回到原来的问题。

幸运的是,有两个很好的解决方案:

1)继续向Person类添加setter,但要保护它们。这意味着您的模拟和测试代码必须位于同一个包中,但会阻止其他用户改变您的人员。 (而且我们不想让突变体四处奔波 - 最近电影中已经足够了)。

2)使用Builder类(如Joshua Bloch在Effective Java中所述)。您将在Person中创建一个公共静态PersonBuilder类,它将导出构建方法和可链接的参数说明符(如setter,但不能单独调用):

public class Person ....
   public static class PersonBuilder {
      public PersonBuilder (String firstName, String lastName) {...} // my sample has two required values
      public Person build() { ... }
      public PersonBuilder ssn (String value) { ... }
      public PersonBuilder adsPath (String value) { ... }
      ...
   }
   ...
}

可链式值说明符如下所示:

      public PersonBuilder ssn (String value) { 
         this.sn = value;
         return this;
      }

然后调用创建Person看起来像这样:

   Person thisPerson = new Person.PersonBuilder ("John", "Smith").ssn("123-45-6789").adsPath("whatever");

这方法完全隐藏,可设置的值(实际上,你有没有“二传手”)的方法,而是让你不必应付长构造函数的参数列表(这使得它更容易处理的可选值)。< / p>

顺便说一句,你也可能想让Person的构造函数变为私有。