有人可以帮助我如何对从另一个单元测试中调用的方法进行单元测试
以下是示例代码
public class Order
{
public bool SaveOrder(OrderInfo orderInfo)
{
float discountAmt = GetDiscount(orderInfo);
return true;
}
public float GetDiscount(OrderInfo orderInfo)
{
//based on orderInfo.couponCode, apply discount
return 0;
}
}
[Test]
public void SaveOrderTest()
{
Order orderObj = new Order();
OrderInfo orderInfo = new OrderInfo();
orderInfo.couponCode = "Save20";
orderInfo.orderAmount = "15000";
orderInfo.orderID = "3";
Assert.AreEqual(true, orderObj.SaveOrder(orderInfo));
}
[Test]
public void GetDiscountTest()
{
Order orderObj = new Order();
OrderInfo orderInfo = new OrderInfo();
orderInfo.couponCode = "Save20";
orderInfo.orderAmount = "15000";
orderInfo.orderID = "3";
Assert.AreEqual(20, orderObj.GetDiscount(orderInfo));
}
是否有办法有条件地运行测试用例,在上述情况下,我们希望在SaveOrderTest()
中有代码更改时运行GetDiscount()
。因为目前它正在执行代码构建之后的所有测试用例,这需要很长时间。相反,我们希望执行因当前代码更改或构建而受影响的测试用例。
我们使用TFS作为我们的CI工具,它正在执行所有测试用例作为构建配置的一部分,因此我们希望运行受当前代码构建影响的测试用例。请建议我如何实现这一目标。
答案 0 :(得分:0)
问题1 :从GetDiscount()
调用SaveOrder()
时没有错,因为此链代表您的业务逻辑。请记住,您希望测试代码单元,并且在您提供的示例中,单元SaveOrder()
包含GetDiscount()
。这并不意味着您必须再次测试GetDiscount()
的所有不同情况,只需提供OrderInfo
的 happy-case对象即可传递GetDiscount()
。<登记/>
如果GetDiscount()
做了像I / O,数据库事务或调用Web API那样昂贵的事情,那么你应该考虑像依赖注入这样的模式。
问题2 :是的,您可以有条件地运行测试。在NUnit中,您可以使用Category Attribute来实现此目的。有了这个,你用属性装饰某些测试然后你可以自由地说:亲爱的NUnit,请只执行所有类别'LongRunningTests'的测试。
答案 1 :(得分:0)
我能看到的最简单方法是将确定折扣的责任从您的订单类中分离出来。这个例子使用nunit和mock来创建适当的假货。
首先,我假设您的OrderInfo对象是一个dto或其他一些Lite类,可以在测试中实例化而无需进一步耦合。如果没有,你可以随时模拟IOrderInfo并改为传递它。
以下是我将要使用的接口。
public interface IOrderInfo
{
string CouponCode
{
get;
set;
}
decimal OrderAmount
{
get;
set;
}
int OrderId
{
get;
set;
}
}
public interface IDiscountApplicator
{
float GetDiscount(IOrderInfo orderInfo);
}
IDiscountApplicator是将要执行GetDiscount逻辑而非Order的事物的接口。
我们需要实现这些接口的实际类,所以它们是:
public class OrderInfo : IOrderInfo
{
public string CouponCode
{
get;
set;
}
public decimal OrderAmount
{
get;
set;
}
public int OrderId
{
get;
set;
}
}
public class DiscountApplicator : IDiscountApplicator
{
public float GetDiscount(IOrderInfo orderInfo)
{
if (orderInfo.CouponCode == "Save20")
return 20;
return 0;
}
}
接下来,我们需要一个Order类。我已经使用构造函数注入为它提供了一个IDiscountApplicator实例,但是任何DI框架都可以为您提供在运行时选择所需的具体类的替代方法。
public class Order
{
private readonly IDiscountApplicator discountApplicator;
public Order(IDiscountApplicator discountApplicator)
{
this.discountApplicator = discountApplicator;
}
public bool SaveOrder(IOrderInfo orderInfo)
{
float discountAmt = this.discountApplicator.GetDiscount(orderInfo);
return true;
}
}
最后,我们的测试用例。这些可能会进入不同的测试装置。
关注SaveOrder,您不再需要关心GetDiscount是否在这里做正确的工作,您关心的是它使用正确的参数调用GetDiscount正确的次数,并且它响应结果无论该对象何种正确返回
模拟的设置只是告诉它无论调用什么都返回42。验证是我们检查SaveOrder实际将orderInfo对象传递给IDiscountApplicator的地方。
[Test]
public void SaveOrderTest()
{
Mock<IDiscountApplicator> discounterMock = new Mock<IDiscountApplicator>();
discounterMock.Setup(s => s.GetDiscount(It.IsAny<IOrderInfo>())).Returns(42);
Order orderObj = new Order(discounterMock.Object);
OrderInfo orderInfo = new OrderInfo();
orderInfo.CouponCode = "Save20";
orderInfo.OrderAmount = 15000;
orderInfo.OrderId = 3;
// SaveOrder is of course always returning true in your example, so not much of a test
Assert.AreEqual(true, orderObj.SaveOrder(orderInfo));
// We can check that we passed the orderObj to the IDiscountApplicator
discounterMock.Verify(v => v.GetDiscount(orderInfo), Times.Once());
}
GetDiscount测试更简单。我把它伪装成包含假代码和两者的测试。
[Test]
[TestCase("Save20", 20)]
[TestCase("Save90", 0)]
public void GetDiscountTest(string couponCode, float expectedDiscount)
{
OrderInfo orderInfo = new OrderInfo();
orderInfo.CouponCode = couponCode;
orderInfo.OrderAmount = 15000;
orderInfo.OrderId = 3;
DiscountApplicator discountApplicator = new DiscountApplicator();
Assert.AreEqual(expectedDiscount, discountApplicator.GetDiscount(orderInfo));
}
我希望您能看到如何使用接口,模拟和注入具体类型,当您只是想测试一个小东西时,您不再需要爱丽丝走下那个兔子洞。
祝你好运。