接口、依赖反转、测试
接口与单元测试
接口的需求
- 接口的产生:自底向上(重构),自顶向下(设计)
- C#中接口的实现(隐式、显示、多接口)
- 语言对面向对象设计的内建支持:依赖反转,接口隔离,开/闭原则…
抽象类的方法只要求不是private
的,也就是public
和protect
都可以。
但接口当中的成员方法必须是public,所以都没有public修饰符。
成员访问级别public决定了接口的本质,他是一种协约,双方都是透明。protect只能让自己的子类看到。internal出了程序集不能被看到。
协约使合作成为可能,同时约束供需双方。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int[] nums1 = new int[] { 1, 2, 3, 4, 5};
ArrayList nums2 = new ArrayList {1,2,3,4,5};
Console.WriteLine(Sum(nums1));
Console.WriteLine(Avg(nums1));
}
static int Sum(int[]nums) {
int sum = 0;
foreach (var n in nums) sum += n;
return sum;
}
static double Avg(int[] nums) {
int sum = 0;double count = 0;
foreach (var n in nums) { sum += n; count++; }
return sum / count;
}
}
}
但是,这个时候把nums2放进Sum()和Avg()里面就不行。因为Sum()和Avg()的类型是int[]。如果这个时候不用接口的话,就要再写两个函数,然后构成重载。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int[] nums1 = new int[] { 1, 2, 3, 4, 5};
ArrayList nums2 = new ArrayList {1,2,3,4,5};
Console.WriteLine(Sum(nums1));
Console.WriteLine(Avg(nums1));
Console.WriteLine(Sum(nums2));
Console.WriteLine(Avg(nums2));
}
static int Sum(int[] nums)
{
int sum = 0;
foreach (var n in nums) sum += n;
return sum;
}
static double Avg(int[] nums)
{
int sum = 0; double count = 0;
foreach (var n in nums) { sum += n; count++; }
return sum / count;
}
static int Sum(ArrayList nums)
{
int sum = 0;
foreach (var n in nums) sum += (int)n;
return sum;
}
static double Avg(ArrayList nums)
{
int sum = 0; double count = 0;
foreach (var n in nums) { sum += (int)n; count++; }
return sum / count;
}
}
}
回到接口
- 供方
int[]和ArrayList
- 需方
Sum()和Avg()
先看需求方:只要求传进来的对象,能够被迭代。
恰好,供应方的int[]和ArrayList都能被迭代。int[]和ArrayList都继承了接口IEnumerable。
因此
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
int[] nums1 = new int[] { 1, 2, 3, 4, 5};
ArrayList nums2 = new ArrayList {1,2,3,4,5};
Console.WriteLine(Sum(nums1));
Console.WriteLine(Avg(nums1));
Console.WriteLine(Sum(nums2));
Console.WriteLine(Avg(nums2));
}
static int Sum(IEnumerable nums)
{
int sum = 0;
foreach (var n in nums) sum += (int)n;
return sum;
}
static double Avg(IEnumerable nums)
{
int sum = 0; double count = 0;
foreach (var n in nums) { sum +=(int)n; count++; }
return sum / count;
}
}
}
依赖
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var engine = new Engine();
var car = new Car(engine);
car.Run(3);
Console.WriteLine(car.Speed);
}
}
class Engine {
public int RPM { get; private set; }
public void Work(int gas) {
this.RPM = 1000 * gas;
}
}
class Car {
private Engine _engine;
public Car(Engine engine) {
_engine = engine;
}
public int Speed { get;private set; }
public void Run(int gas) {
_engine.Work(gas);
this.Speed = _engine.RPM / 100;
}
}
}
截取上面得代码
class Engine {
public int RPM { get; private set; }
public void Work(int gas) {
this.RPM = 1000 * gas;
}
}
class Car {
private Engine _engine;
public Car(Engine engine) {
_engine = engine;
这里可以看出来,Car和Engine就耦合或者说依赖到一起了。
紧耦合
就是基础类Engine 出了问题的话,无论Car再怎么正确,代码都会有问题。
使用接口变为松耦合
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var user = new PhoneUser(new NokiaPhone());
user.UsePhone();
}
}
class PhoneUser {
private IPhone _phone;
public PhoneUser(IPhone phone)
{
_phone = phone;
}
public void UsePhone()
{
_phone.Dial();
_phone.PickUp();
_phone.Receive();
_phone.Send();
}
}
}
interface IPhone {
void Dial();
void PickUp();
void Send();
void Receive();
}
class NokiaPhone : IPhone
{
public void Dial()
{
Console.WriteLine("Nokia is calling……");
}
public void PickUp()
{
Console.WriteLine("Hello,this is Tim");
}
public void Receive()
{
Console.WriteLine("Nokia message is ring……");
}
public void Send()
{
Console.WriteLine("Hello!");
}
}
class Ericsson : IPhone
{
public void Dial()
{
Console.WriteLine( "Ericsson is calling");
}
public void PickUp()
{
Console.WriteLine("Hello,this is Tim");
}
public void Receive()
{
Console.WriteLine("Erisson message is ring");
}
public void Send()
{
Console.WriteLine("Hello!");
}
}
}
如果不是用借口,那么在main方法里面如果想要替换NokiaPhone(),调用其他的,比如Ericsson,那么就要在NokiaPhone里去修改,这样容易改错。
依赖反转
单元测试是依赖翻转的应用。
依赖:服务使用者依赖在服务的提供者**(必要条件)**之上。当服务的提供者出现问题,使用者无法使用。
反转
单元测试
电扇、电源。
电源是电扇运转的必要条件。
电扇依赖于电扇上的。
紧耦合
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var fan = new DeskFan(new PowerSupply());
Console.WriteLine(fan.Work()) ;
}
}
class PowerSupply{
public int GetPower() {
return 190;
}
}
class DeskFan {
private PowerSupply _powersupply;
public DeskFan(PowerSupply powerSupply)
{
_powersupply = powerSupply;
}
public string Work() {
int power = _powersupply.GetPower();
if (power <= 0)
{
return "No power";
}
else if (power < 100)
{
return "slow";
}
else if (power < 200) {
return "Work fine.";
}
else {
return "Warning.";
}
}
}
}
可以发现上面还是紧耦合的,这个时候引入一个借口IPowerSuplly
,使得耦合变松。然后再做测试,而测试要专门准备一个类。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var fan = new DeskFan(new PowerSupply());
Console.WriteLine(fan.Work()) ;
}
}
interface IPowerSupply {
int GetPower();
}
class PowerSupply:IPowerSupply{
public int GetPower() {
return 150;
}
}
class DeskFan {
private IPowerSupply _powersupply;
public DeskFan(IPowerSupply powerSupply)
{
_powersupply = powerSupply;
}
public string Work() {
int power = _powersupply.GetPower();
if (power <= 0)
{
return "No power";
}
else if (power < 100)
{
return "slow";
}
else if (power < 200) {
return "Work fine.";
}
else {
return "Warning.";
}
}
}
}
如果我要测试一个某一个条件能不能正常使用,不是去方法里面更改成员函数,而是应该创建一个单元测试。测试前注意把要测试的类设置全局访问。
如此一来在解决方案下面会多一个单元测试类。
using ConsoleApp1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace _02_Excise_Test
{
[TestClass]
public class DeskFanTests
{
[TestMethod]
public void PowerLowerThanZero_OK()
{
var fan = new DeskFan(new PowerSupplyLowerThanZero());
var expected = "No power";
var actual = fan.Work();
Assert.AreEqual(expected,actual);
}
}
class PowerSupplyLowerThanZero : IPowerSupply
{
public int GetPower()
{
return 0;
}
}
}
要测试的那个条件所在类进行重新继承,然后再管理器里面找到最后测试的单元,执行。
上一个能执行通过,那么我们再测试一个case .
using ConsoleApp1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace _02_Excise_Test
{
[TestClass]
public class DeskFanTests
{
[TestMethod]
public void PowerLowerThanZero_OK()
{
var fan = new DeskFan(new PowerSupplyLowerThanZero());
var expected = "No power";
var actual = fan.Work();
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void PowerHigherThan200_Warnig()
{
var fan = new DeskFan(new PowerSupplyHiggerThan200());
var expected = "Warning";
var actual = fan.Work();
Assert.AreEqual(expected,actual);
}
}
class PowerSupplyLowerThanZero : IPowerSupply
{
public int GetPower()
{
return 0;
}
}
class PowerSupplyHiggerThan200 : IPowerSupply
{
public int GetPower()
{
return 250;
}
}
}
PowerSupplyHiggerThan200
是测试失败的。
测试中的Debug
是哪一个Case有问题,就在那个case里面设置断点。
优化测试
可以看到我们用接口在测试单元里面创建类,名字很长很丑,那么可以用一个模拟的方式–在Nuget里面下载moq。
using ConsoleApp1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace _02_Excise_Test
{
[TestClass]
public class DeskFanTests
{
[TestMethod]
public void PowerLowerThanZero_OK()
{
var mock = new Mock<IPowerSupply>();
mock.Setup(ps => ps.GetPower()).Returns(() => 0);
var fan = new DeskFan(mock.Object);
var expected = "No power";
var actual = fan.Work();
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void PowerHigherThan200_Warnig()
{
var mock = new Mock<IPowerSupply>();
mock.Setup(ps => ps.GetPower()).Returns(() => 220);
var fan = new DeskFan(mock.Object);
var expected = "Warning";
var actual = fan.Work();
Assert.AreEqual(expected,actual);
}
}
}