返回首页

简介
在这篇文章中,我将描述一个可伸缩的{A},数据库驱动的。NET应用程序。该套件将定义用于轻松创建虚拟测试数据样本发电机,它会使用提高可扩展性测试夹具的继承和共同的功能,让简单的​​测试。
我将重点放在{A2}一个示例应用程序的测试。这通常是位于应用程序的"中间",通常被称为业务逻辑层(BLL)。它使用的数据访问层(DAL)进行调解,并从数据库中的数据传输和驱动器的一个或多个用户界面(UI)与用户交互或显示在其中的数据的行为。
本文假设。NET和C#的知识,但并不需要单元测试或NUnit的特别经验。BLL的实施
在此示例应用程序,在BLL中实现数据库操作的类称为持久性对象的基类继承。这个类定义如下界面[{A3}]:

public abstract class PersistentObject {

    protected long _uid = long.MinValue;



    /// <summary>

    /// The unique identifier of this object 

    ///  in the database

    /// </summary>

    /// <remarks>

    /// Set when Fill() is called

    /// </remarks>

    public long UID {

        get { return _uid; }

    }



    /// <summary>

    /// Save this object's data to the database. 

    /// </summary>

    public abstract void Save();



    /// <summary>

    /// Fill this object with data fetched from the



    ///  database for the given UID

    /// </summary>

    /// <param name="uid">The unique identifier of

    /// the record to fetch from the database</param>

    public abstract void Fill(long uid);



    /// <summary>

    /// Remove this object from the database

    /// </summary>

    public abstract void Delete();



    /// <summary>

    /// Fetches an object of the given type and with the

    ///  given UID from the database

    /// </summary>

    /// <typeparam name="ConcreteType">

    /// The type of object to fetch

    /// </typeparam>

    /// <param name="uid">

    /// The unique identifier of the object in the database

    /// </param>

    public static ConcreteType Fetch<ConcreteType>(long uid) 

      where ConcreteType : PersistentObject, new() {

        ConcreteType toReturn = new ConcreteType();

        toReturn.Fill(uid);

        return toReturn;

    }

}

说,例如,应用程序必须保存一些客户数据和客户端的地址,可用于在应用程序。 BLL的,因此需要包含从持久性对象的地址和客户端类。{C}
客户端是相似的,但它包含了一个属性,它返回的客户端的地址对象。
public class Client : PersistentObject {

    private string _firstName = null;

    private string _lastName = null;

    private string _middleName = null;

    private long _addressUID = long.MinValue;



    private Address _addressObject;



    // ...



    public long AddressUID {

        get { return _addressUID; }

        set { _addressUID = value; }

    }



    /// <summary>

    /// On-demand property that returns this Client's 

    ///  Address based on the current value of AddressUID<

/span>

    /// </summary>

    public Address Address {

        get {

            if (AddressUID == long.MinValue) {

                _addressObject = null;

            }

            else if (_addressObject == null 

              || AddressUID != _addressObject.UID) {

                _addressObject = new Address();

                _addressObject.Fill(AddressUID);

            }

            return _addressObject;

        }

    }



    // ...



}

要保存新的客户端数据,用户将做类似以下内容:
// Create the address that the client will link to



Address newAddress = new Address();

newAddress.StreetAddress = StreetAddressInput.Text;

newAddress.City = CityInput.Text;

newAddress.State = StateInput.Text;

newAddress.Zip = ZipInput.Text;

// Save the address to the database

newAddress.Save();



// Create the client

Client newClient = new Client();

newClient.FirstName = FirstNameInput.Text;

newClient.MiddleName = MiddleNameInput.Text;

newClient.LastName = LastNameInput.Text;

// Link to the address

newClient.AddressUID = newAddress.UID;

// Save the client to the database

newClient.Save();

和检索客户端数据应用程序,用户会做类似以下内容:
Client existingClient = Client.Fetch(clientUID);

Address clientAddress = existingClient.Address;
单元测试背景
上文所述的BLL实施是比较标准的。一个可以验证其行为在任何数量的方式。最简单的,但至少强大的测试UI。由于UI BLL的依赖,可以想见,验证通过网页或对话框手工运行的应用程序。但是,如果应用程序有多个用户界面?显然,这种方法很慢,难以重复,容易出现人为错误,并可能会错过错误。此外,它可促进在一个殿的编码器可固定在一种症状,而不是在BLL基础事业的UI不好的编程习惯。这是不是说,我们应该省略的UI测试,只是我们不应该依靠它来验证业务逻辑。
一个更好的选择将创建一个简单的驱动程序正在开发的程序调用BLL方法。此选项将肯定会更容易重复,但可能很难保存司机稍后或运行所有现有的驱动程序,以验证没有被打破。
这是{A4}进来一个人,可以看作是一个单元测试一个简单的驱动程序,可能会写反正。单元测试框架组织的测试,提供工具,使编写测试变得更容易,并允许一个总运行测试。测试套件实现
由于本文的讨论。NET应用程序,我将使用{A5},使得很容易编写和运行测试。
这是最直观的BLL中的每个类创建一个测试夹具(即,一个类包含了一系列的测试)。因此,在我们的例子,例如测试套件ClientTest和AddressTest类。这些基本的测试夹具将需要验证数据添加到数据库中,检索,编辑,并正确地删除。我们经常需要创建虚拟对象,因此,这些测试装置还包括一些示例发电机。最后,我们不希望重复在许多不同的测试夹具的共同的测试代码,因此我们将测试在PersistentObjectTest类ClientTest和AddressTest都继承了常见的数据库操作。
我会解释的PersistentObjectTest的部分建设。首先,在类声明:
/// <summary>

/// Abstract base class for test fixtures that test 

///  classes derived from BLL.PersistentObject

/// </summary><

/span>

/// <typeparam name="PersistentObjectType">

/// The type of BLL.PersistentObject that the derived 

///  class tests

/// </typeparam>

public abstract class PersistentObjectTest<PersistentObjectType> 

  where PersistentObjectType : PersistentObject, new() {

这表明PersistentObjectTest接受对象类型的泛型类型,其派生类测试。这种类型来自持久性对象和一个空的构造。这让我们创建一个类型安全的,通用的方式样品发电机和其他公用事业:
#region Sample Generators



/// <summary>

/// Returns a dummy object

/// </summary><

/span>

/// <param name="type">

/// Indicates whether the returned dummy object should

///  be saved to the database or not

/// </param>

public PersistentObjectType GetSample(SampleSaveStatus saveStatus) {

    PersistentObjectType toReturn = new PersistentObjectType();

    FillSample(toReturn);

    if (saveStatus == SampleSaveStatus.SAVED_SAMPLE) {

        toReturn.Save();

        // Check Save() postconditions...

    }

    return toReturn;

}



/// <summary>

/// Fills the given object with random data

/// </summary><

/span>

/// <param name="sample">

/// The sample object whose fields to fill

/// </param>

/// <remarks>

/// Should be overridden and extended in 

///  derived classes

/// </remarks><

/span>

public virtual void FillSample(PersistentObjectType sample) {

    // nothing to fill in the base class

}



/// <summary>

/// Asserts that all fields in the given objects match

/// </summary><

/span>

/// <param name="expected">

///  The object whose data to check against

/// </param>

/// <param name="actual">

/// The object whose fields to test

/// </param>

/// <remarks>

/// Should be overridden and extended in 

///  derived classes

/// </remarks><

/span>

public virtual void AssertIdentical

 (PersistentObjectType expected, PersistentObjectType actual) {

    Assert.AreEqual(expected.UID, actual.UID, 

      "UID does not match");

}



#endregion

GetSample()只是简单地返回一个虚拟的对象。 FillSample()和AssertIdentical()的实现委托给派生类。这三种方法用于其他测试夹具,创建和测试样品对象。基类使用它们来验证在下面的测试方法的基本的数据库操作:
#region Data Tests



/// <summary>

/// Tests that data is sent to and retrieved from 

///  the database correctly

/// </summary><

/span>

[Test()]

public virtual void SaveAndFetch() {

    PersistentObjectType original = 

      GetSample(SampleSaveStatus.SAVED_SAMPLE);

    PersistentObjectType fetched = 

      PersistentObject.Fetch<PersistentObjectType>(original.UID);

    // verify that the objects are identical

    AssertIdentical(original, fetched);

}



/// <summary>

/// Tests that editing an existing object works correctly<

/span>

/// </summary><

/span>

[Test()]

public virtual void EditAndFetch() {

    PersistentObjectType modified = 

      GetSample(SampleSaveStatus.SAVED_SAMPLE);

    // edit fields 

    FillSample(modified);

    // save edits

    modified.Save();

    // make sure edits were reflected in the database

    PersistentObjectType fetched = 

      PersistentObject.Fetch<PersistentObjectType>(modified.UID);

    AssertIdentical(modified, fetched);

}



/// <summary>

/// Tests that deletion works correctly.

/// </summary><

/span>

/// <remarks>

/// Expects data retrieval to fail

/// </remarks><

/span>

[Test(), 

ExpectedException(typeof(DataNotFoundException))]

public virtual void Delete() {

    PersistentObjectType toDelete = 

      GetSample(SampleSaveStatus.SAVED_SAMPLE);

    long originalUID = toDelete.UID;

    toDelete.Delete();

    // expect failure because the object does not exist

    PersistentObject.Fetch<PersistentObjectType>(originalUID);

}



#endregion

PersistentObjectTest做繁重的,具体的测试类只需要定义如何填写样本对象,以及如何检查,如果两个样本对象是相同的。他们还可以定义额外的样品发电机,实用功能和测试方法,根据需要。
[TestFixture()]

public class AddressTest : PersistentObjectTest<Address> {



    public override void FillSample(Address sample) {

        base.FillSample(sample);

        Random r = new Random();

        string[] states = {"IL", "IN", "KY", "MI"};



        sample.City = "CITY" + DateTime.Now.Ticks.ToString();

        sample.State = states[r.Next(0, states.Length)];

        sample.StreetAddress = r.Next().ToString() + " Anywhere Street";

        sample.Zip = r.Next(0, 100000).ToString("00000");

    }



    public override void AssertIdentical(Address expected, Address actual) {

        base.AssertIdentical(expected, actual);

        Assert.AreEqual(expected.City, actual.City, 

          "City does not match");

        Assert.AreEqual(expected.State, actual.State,

          "State does not match");

        Assert.AreEqual(expected.StreetAddress, actual.StreetAddress, 

          "StreetAddress does not match");

        Assert.AreEqual(expected.Zip, actual.Zip, 

          "Zip does not match");

    }



}

[TestFixture()]

public class ClientTest : PersistentObjectTest<Client> {



    public override void FillSample(Client sample) {

        base.FillSample(sample);

        sample.FirstName = "FIRST" + DateTime.Now.Ticks.ToString();

        sample.MiddleName = "MIDDLE" + DateTime.Now.Ticks.ToString();

        sample.LastName = "LAST" + DateTime.Now.Ticks.ToString();

        sample.AddressUID = new AddressTest().GetSample

         (SampleSaveStatus.SAVED_SAMPLE).UID;

    }



    public override void AssertIdentical(Client expected, Client actual) {

        base.AssertIdentical(expected, actual);

        Assert.AreEqual(expected.FirstName, actual.FirstName, 

          "FirstName does not match");

        Assert.AreEqual(expected.MiddleName, actual.MiddleName, 

          "MiddleName does not match");

        Assert.AreEqual(expected.LastName, actual.LastName,

          "LastName does not match");

        Assert.AreEqual(expected.AddressUID, actual.AddressUID, 

          "AddressUID does not match");

    }

}

ClientTest的样品发电机使用AddressTest.GetSample()创建一个虚拟地址时,填写一个虚拟的样本客户机。在这种类型的测试套件,这是一般的模式是经常使用的。任何测试需要一个虚拟的对象,只是简单地调用相应的样品发电机。
运行测试时,看起来与属性标记的任何类的NUnit的[TestFixture()]。它创建了一个类的实例并运行属性任何标记方法[试验(+)]。 [ExpectedException()]属性告诉NUnit的,给定的方法应该抛出异常。测试代码本身使用NUnit的的断言对象,以验证预期的属性。
从一个抽象基类继承的任何测试夹具也"继承"[{A6}]任何的测试方法。因此,AddressTest,一个具体的测试夹具,继承SaveAndFetch(),EditAndFetch(),和Delete()从PersistentObjectTest测试方法。请注意,派生类可以覆盖这些测试方法,例如,如果其相应的BLL类不支持删除:
[Test()]

public override void Delete() {

    Assert.Ignore("This object does not support deleting");

}
继承
现在,我们已经基本测试套件实施,说需求的变化,我们需要添加一个类代表一个首选的客户端收到折扣和特别信贷。首先,我们将创建一个PreferredClient从客户端的类:
public class PreferredClient : Client {

    private double _discountRate = 1;

    private decimal _accountCredit = 0.00M;



    //...



    public override void Save() {

        base.Save();

        // call DAL to save this object's fields

    }



    //...



}

接下来,我们必须创建一个PreferredClientTest ClientTest派生的测试夹具。但是,这会导致一个问题:从PersistentObjectTestlt ClientTest继承; Clientgt;,但我们需要PreferredClientTest从PersistentObjectTestlt间接继承; PreferredClientgt; PersistentObjectTest的方法使使用正确类型的对象。该解决方案是移动"上下层次"ClientTest通用的签名:
/// <summary>

/// Generic tester for classes derived from Client

/// </summary><

/span>

public class ClientTest<DerivedClientType> 

: PersistentObjectTest<DerivedClientType> 

    where DerivedClientType : Client, new() {



    public override void FillSample(DerivedClientType sample) {

        base.FillSample(sample);

        sample.FirstName = "FIRST" + DateTime.Now.Ticks.ToString();

        sample.MiddleName = "MIDDLE" + DateTime.Now.Ticks.ToString();

        sample.LastName = "LAST" + DateTime.Now.Ticks.ToString();

        sample.AddressUID = new AddressTest().GetSample

          (SampleSaveStatus.SAVED_SAMPLE).UID;

    }



    public override void AssertIdentical

      (DerivedClientType expected, DerivedClientType actual) {

        base.AssertIdentical(expected, actual);

        Assert.AreEqual(expected.FirstName, actual.FirstName, 

          "FirstName does not match");

        Assert.AreEqual(expected.MiddleName, actual.MiddleName, 

          "MiddleName does not match");

        Assert.AreEqual(expected.LastName, actual.LastName,

          "LastName does not match");

        Assert.AreEqual(expected.AddressUID, actual.AddressUID, 

          "AddressUID does not match");

    }



}

但是,我们需要保持非泛型测试仪,使客户的测试仍将运行:
/// <summary>

/// Non-generic tester for base Client type

/// </summary><

/span>

[TestFixture()]

public class ClientTest : ClientTest<Client> {

    // add Client-specific tests as needed

}

最后,我们在ClientTest通用版本定义PreferredClientTest:
[TestFixture()]

public class PreferredClientTest : ClientTest<PreferredClient> {



    public override void FillSample(PreferredClient sample) {

        base.FillSample(sample);

        Random r = new Random();

        // some random dollars and cents

        sample.AccountCredit = ((Decimal)r.Next()) + .25M; 

        sample.DiscountRate = r.NextDouble();

    }



    public override void AssertIdentical

     (PreferredClient expected, PreferredClient actual) {

        base.AssertIdentical(expected, actual);

        Assert.AreEqual(expected.AccountCredit, actual.AccountCredit, 

          "AccountCredit does not match");

        Assert.AreEqual(expected.DiscountRate, actual.DiscountRate, 

          "DiscountRate does not match");

    }

}

注意FillSample()和AssertIdentical()方法简单地延长它们的基类的同行。人们可以很容易看到这种类型的扩展如何可以继续作为应用程序的增长,它是一个简单的添加一个子类,并实施适当的方法问题。缺点主键
这个假设的测试套件,一个明显的假设:它假定持久性对象是现实世界中的类的一个有效的基类。这种假设成为最明显的FETCH /填充方法,始终以一个长作为一个独特的数据库标识符。通常,一个真正的世界的数据库不会被标准化,这样所有的数据有一个bigint的主键(如果只!)。一个可以解决这个问题,通过扩大PersistentObjectTest和PersistentObject.Fetch通用()的签名,包括派生类的唯一标识符的类型。虚拟数据过载
由于其对样本发电机的依赖,测试套件的形式创建了一个虚拟的数据在数据库中的大量。这是可以接受的,因为一个测试数据库驱动的应用程序,核实数据的保存和检索正确的很大一部分。但是,这意味着,开发应用程序必须有一个专门的测试的数据库服务器,定期重置一些已知的状态,以防止虚拟数据盖过了有效的数据。此外,样本发生器的递归性质,可能使人们有可能进入一个永无止境的样本生成周期,可以非常快速地将一个数据库(更不用提的堆栈帧)一蹶不振。随机性
我所阐述的实现假定随机虚拟数据往往会足以满足大多数测试使用生成的对象。换句话说,消费者的样本对象必须确保生成的对象是否符合所需的先决条件。上随意性的界限,往往可以取得与参数,如以下示例发电机:
/// <summary>

/// Return a client with one of the given first names

/// </summary><

/span>

/// <param name="firstNames">

/// The list of possible first names

/// </param>

public static Client GetBoundedSample

  (string[] firstNames, SampleSaveStatus saveStatus) {

    Client toReturn = new ClientTest().GetSample(SampleSaveStatus.UNSAVED_SAMPLE);

    Random r = new Random();

    toReturn.FirstName = firstNames[r.Next(0, firstNames.Length)];

    if (saveStatus == SampleSaveStatus.SAVED_SAMPLE) {

        toReturn.Save();

    }

    return toReturn;

}

然而,有没有一般情况下,容易实施的样品发电机的方式来控制的随意性或返回一个详尽的清单范围内的所有可能的样品。事实上,详尽的测试生成{A7}。结论
我所概述的是假设的测试套件架构,测试分层,合理,随机抽样数据往往需要数据库驱动的应用非常有用。通过使用测试夹具的继承和样品发电机,它变得非常容易扩大测试套件,作为应用程序的增长。它也减少了代码量需要测试一个数据库驱动的应用程序的最重要的方面:传输数据,并从数据库中正确。这个测试执行上的变化有好几个。几十到几千类NET应用程序。脚注在现实中,保存,填写,并删除通常会包装DoSave就会DoFill,DoDelete保护重写的方法。这将允许基类定义共同的前和后的数据库的操作步骤,同时离开的派生类来处理自己的数据。另外,删除通常会设置一个"忽略"的旗帜,而不是完全从数据库中删除数据。无论如何,我们在这篇文章中可以忽略这些并发症。只是假设,派生类会覆盖在明显的方式储存,填充和删除类的支持,如果相应的数据库操作。 这是不是真正的继承。 NUnit的使用{A8}找到属性[测试()]的方法在类的层次结构发生任何标记方法。此外,覆盖测试方法不保留[TEST()]属性。|布雷特丹尼尔

回答

评论会员:游客 时间:2011/12/06
喜布雷特我一直在阅读了很多有关使用NUnit的单元测试应用程序我目前的情况是:我通过与开发基于Web的应用程序使用VisualStudio2005和Oracle10g。在此之后,我想创建通过NUnit的"测试",以检查我的应用程序的三个层次,即业务逻辑,数据库和用户界面。我的理解NUnit测试框架下工作的基本思路,但我现在的问题是"要测试什么"和"什么不能测试"。如果一开始写作,我在我的BLL定义的所有方法的测试,那么我可能使数据库调用以及运行我的测试[PS:我认为嘲讽数据库的想法。可能需要更多的关于这一点!]这将最终使本身就是一个很笨重的类太多断言等功能。光根据上述情况,可以请你的任何一个建议几个指针,以回答我的问题。任何有关这方面的帮助表示高度赞赏。Kuldeep
布雷特丹尼尔
评论会员:游客 时间:2011/12/06
本文主要是测试关注的对象-关系映射是健全的。测试的业务逻辑是正确的,是另一回事,但像你观察,它可能取决于数据库。我猜测,充分测试你的BLL,你几乎肯定会要求数据库调用,但最好是尽量减少数据库的交互。也就是说,你应该从数据库代码分开的BLL的代码,以便您可以测试尽可能多的业务逻辑而不触及数据库。自从我最初写这篇文章,对象-关系映射库,如{A9}得到普及,几乎可以肯定的首选方式,坚持对象的数据。这些库不删除需要测试的对象关系映射(因为还必须验证映射是正确的),但它们消除样板代码。他们也更容易测试,因为1)他们可以自动检查数据库架构相匹配的一流的结构,和2),可以轻松地配置应用程序使用单独的数据库,测试和生产的持久性数据。网上有很多文章描述这个过程中,只需搜索"测试休眠"。这一切都不会回答你的问题,"(不)来测试?"我一直采取下列务实,也许是巧舌如簧,方法进行测试:测试,你可以尽可能多,什么是最有可能打破开始。这意味着你应该花大部分的时间验证复杂的业务逻辑,而不是说你行的getter属性返回相同的值,你给他们制定者(这是数据库持久性的本质)。因此,你应该创建一个测试架构,最大限度地减少所需的时间来验证映射是正确的。这是一个持久性库,大致是这样,我在文章中描述的体系结构变得非常有用。有了这样的架构到位,你可以集中测试更重要的代码
Kuldeep Vijaykumar
评论会员:游客 时间:2011/12/06
在此之前,感谢您的时间,并给我一些很好的建议很多布雷特就关闭油烟机,我还有一个问题,麻烦你在这里,比方说,我建立一个全新的Web应用程序,并说我不是很热衷沿测试驱动开发(TDD)方法。在这样的情况下下,应用程序的这部分应该是"单元测试"?应该如何能够决定什么样的测试将是最适合在心中重要的3层的单元测试的最有效保持整个演习,即用户界面,BLL和DAL一些真正的好切实可行的单位测试样品通过NUnit的任何信息/链接必将有助于我最再次感谢,Kuldeep
baruchl
评论会员:游客 时间:2011/12/06
我是一个大风扇和TDD的医生,但据我所知,它可能不适合所有的开发和应用。不过,你可以从什么可致电发展为导向的测试或开发并行测试了很多好处。这是很难进行单元测试的UI,但也有一些工具,允许一个记录/重放UI交互。我没有任何伟大的程度,所以我不能评论其效用。我很少发现有效的直接测试DAL。首先,测试对象持久性(如在文章中),验证对象-关系映射,最有可能失败,因为应用程序方面的发展。第二,我发现,从测试的创建/读取/更新/删除操作,因为它们通常会自动生成或由第三方库处理,就像我在我以前的答复描述由DAL提供什么好处。剩下的BLL,特别是域模型。就像我说我以前的答复中,测试你可以尽可能的,什么是最有可能打破开始。什么是最有可能打破在域模型中?在我的经验,域对象之间复杂的相互作用隐藏的最错误。因此,当我需要测试现有的代码(而不是当我做TDD),我发现最大的效益与高层次的调解对象开始。我写了一些测试验证的基本功能和许多边境和特殊情况下,尽可能。如果测试的休息,我调试找到问题的根源,然后写一个最小的测试,揭示了错误。通常情况下,这个最小的测试验证一个或两个域对象的一些低级行为。因此,测试套件的发展有机地成为一个有用的资源库的回归测试。它也是有用的问自己,"我想获得这些测试什么?"一个回答说:"找到尽可能多的错误",会导致你写的更彻底的测试,比回答"验证系统的基本功能的80%"。覆盖标准,它也是很好的决定。也就是说,什么样的条件下,必须测试满足您认为他们完成的呢?例如,你可以说你的测试应涵盖GT;中最常用的80%域对象的基本块的80%。在实践中,覆盖准则往往是确切的要少得多。例如,您可能需要为每一个足够复杂的方法测试的情况下,你写的,在过去​​的几类。不幸的是,我没有任何手头上的小试样品,但也有许多优秀的书籍和网站,回答你的问题,"要测试什么?"特别是:*{A10}安迪亨特,戴夫托马斯和马特Hargett*{A11}由KentBeck(一个有用的阅读,甚至如果你不这样做TDD)
达伦 - 普鲁特
评论会员:游客 时间:2011/12/06
。再次感谢你分享你的思想,这是相当综合性的布雷特你有共同的关于"要测试什么,什么不可以"是挺有意思的,我相信他们会播下一些种子,在我的理解更加清晰。不朽的问题的想法根据您的建议,我设法得到"#与安迪亨特NUnit的在C务实的单元测试"浏览通过几个章节和我必须说,这是一个相当不错的书这能最终回答我的问题,电子图书的格式可能在一个更详细的的方式。Kuldeep
布雷特丹尼尔
评论会员:游客 时间:2011/12/06
好文章。几点意见在现实中,与持久性数据处理时,并不总是作为一个模型中使用的持久性对象的一种很好的做法,因为如果你是处理大量数据,它通常是昂贵的跨层移动的所有对象,并保存其中之一一个数据库/持久性存储。相反,你应该考虑这样的对象的集合转变成一个数据集,然后使用批量更新,保存到数据库中的数据。2。一个重要的事情,你忘了提,是,有时你也想在这种情况下,你的BLL和不仅是公共方法的内部测试,使用的NUnit的方法,你不能这样做,只是因为方法是不暴露。我在这种情况下,这样做,是增加一个预处理条件下,无论是在DLL和NUnit的代码,所以在调试的时间,而不是在生产中暴露的方法和测试。例如:在BLL:#如果DEBUG公共无效JustAMethodToTest()#ELSE私人无效JustAMethodToTest()#ENDIF{...}{BR}在NUnit的代码:#如果DEBUG公共无效TestTheMethod(){...}{BR}#ENDIF快乐编码,康布斯
DuncanJSmith
评论会员:游客 时间:2011/12/06
添加这样的条件,使得代码非常难以阅读和维护。这将更好地提取到自己的类的所有私有方法,这样你可以单元测试。正如他们所说,只是一个私有方法是一个类,尖叫被释放{S0}