I do not have much experience with unit testing. From what I learned, the code should be untied, and I should not try to test closed source code, just public methods, setters, etc. Etc.
Now I understand some basic concepts of testing, but I have problems with the use of more advanced materials in this case ... Injection of dependencies, inversion of the control, mock objects, etc. - still does not hug him: (
Before moving on to the code, here are the questions.
- In this class, what exactly should I try to check?
- How can I complete these test tasks?
- Is there anything serious about class design that prevents testing from running correctly (or is something just wrong even outside the testing context)?
- What design patterns are useful for testing network code in general?
Also, I tried to obey "write tests first, and then write code to pass the tests," so I wrote the first two tests that simply create an instance of the class and run it, but then when the server managed to start and receive the packets, I didn’t knew what to test next ...
Ok, here comes the code snippet. (note: the source code is divided into several namespaces, so it may seem a little out of order)
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace MyProject1
{
public class UDPPacketBuffer
{
public const int BUFFER_SIZE = 4096;
private byte[] _data;
public byte[] Data { get { return _data; } set { _data = value; DataLength = value.Length; } }
public int DataLength;
public EndPoint RemoteEndPoint;
public UDPPacketBuffer()
{
this.Data = new byte[BUFFER_SIZE];
RemoteEndPoint = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
}
public byte[] ByteContent
{
get
{
if (DataLength > 0)
{
byte[] content = new byte[DataLength];
for (int i = 0; i < DataLength; i++)
content[i] = Data[i];
return content;
}
else
{
return Data;
}
}
}
public string StringContent { get { return Encoding.ASCII.GetString(ByteContent); } }
}
public class UDPPacketEventArgs : EventArgs
{
public UDPPacketBuffer buffer { get; private set; }
public int sent { get; private set; }
public UDPPacketEventArgs(UDPPacketBuffer buffer)
{
this.buffer = buffer;
}
public UDPPacketEventArgs(UDPPacketBuffer buffer, int sent)
{
this.buffer = buffer;
this.sent = sent;
}
}
public class AsyncUdp : ServerBase
{
private const int _defaultPort = 45112;
private int _udpPort;
public int udpPort { get { return _udpPort; } private set { _udpPort = value; } }
private bool broadcast = false;
private Socket udpSocket;
private ReaderWriterLock rwLock = new ReaderWriterLock();
private int rwOperationCount = 0;
private bool shutdownFlag = true;
public bool IsRunning
{
get { return !shutdownFlag; }
}
public AsyncUdp()
{
this.udpPort = _defaultPort;
}
public AsyncUdp(int port)
{
this.udpPort = port;
}
public AsyncUdp(int port, bool broadcast)
{
this.udpPort = port;
this.broadcast = broadcast;
}
public event EventHandler PacketReceived;
public event EventHandler PacketSent;
public override void Start()
{
if (! IsRunning)
{
IPEndPoint ipep = new IPEndPoint(IPAddress.Any, udpPort);
udpSocket = new Socket(
AddressFamily.InterNetwork,
SocketType.Dgram,
ProtocolType.Udp);
udpSocket.EnableBroadcast = broadcast;
if (broadcast)
udpSocket.MulticastLoopback = false;
udpSocket.Bind(ipep);
shutdownFlag = false;
AsyncBeginReceive();
}
}
public override void Stop()
{
if (IsRunning)
{
rwLock.AcquireWriterLock(-1);
shutdownFlag = true;
udpSocket.Close();
rwLock.ReleaseWriterLock();
while (rwOperationCount > 0)
Thread.Sleep(1);
}
}
public override void Dispose()
{
if (IsRunning == true)
this.Stop();
}
private void AsyncBeginReceive()
{
rwLock.AcquireReaderLock(-1);
if (!shutdownFlag)
{
Interlocked.Increment(ref rwOperationCount);
UDPPacketBuffer buf = new UDPPacketBuffer();
try
{
udpSocket.BeginReceiveFrom(
buf.Data,
0,
UDPPacketBuffer.BUFFER_SIZE,
SocketFlags.None,
ref buf.RemoteEndPoint,
new AsyncCallback(AsyncEndReceive),
buf);
}
catch (SocketException)
{
Interlocked.Decrement(ref rwOperationCount);
}
}
rwLock.ReleaseReaderLock();
}
private void AsyncEndReceive(IAsyncResult iar)
{
rwLock.AcquireReaderLock(-1);
if (!shutdownFlag)
{
AsyncBeginReceive();
UDPPacketBuffer buffer = (UDPPacketBuffer)iar.AsyncState;
try
{
buffer.DataLength = udpSocket.EndReceiveFrom(iar, ref buffer.RemoteEndPoint);
Interlocked.Decrement(ref rwOperationCount);
rwLock.ReleaseReaderLock();
if (PacketReceived != null)
PacketReceived(this, new UDPPacketEventArgs(buffer));
}
catch (SocketException)
{
Interlocked.Decrement(ref rwOperationCount);
rwLock.ReleaseReaderLock();
}
}
else
{
Interlocked.Decrement(ref rwOperationCount);
rwLock.ReleaseReaderLock();
}
}
public void AsyncBeginSend(UDPPacketBuffer buf)
{
rwLock.AcquireReaderLock(-1);
if (!shutdownFlag)
{
try
{
Interlocked.Increment(ref rwOperationCount);
udpSocket.BeginSendTo(
buf.Data,
0,
buf.DataLength,
SocketFlags.None,
buf.RemoteEndPoint,
new AsyncCallback(AsyncEndSend),
buf);
}
catch (SocketException)
{
throw new NotImplementedException();
}
}
rwLock.ReleaseReaderLock();
}
private void AsyncEndSend(IAsyncResult iar)
{
rwLock.AcquireReaderLock(-1);
if (!shutdownFlag)
{
UDPPacketBuffer buffer = (UDPPacketBuffer)iar.AsyncState;
try
{
int bytesSent = udpSocket.EndSendTo(iar);
if (PacketSent != null)
PacketSent(this, new UDPPacketEventArgs(buffer, bytesSent));
}
catch (SocketException)
{
throw new NotImplementedException();
}
}
Interlocked.Decrement(ref rwOperationCount);
rwLock.ReleaseReaderLock();
}
}
abstract public class ServerBase : IDisposable
{
abstract public void Start();
abstract public void Stop();
#region IDisposable Members
public abstract void Dispose();
#endregion
}
}
The following is a test code.
namespace MyProject1
{
class AsyncUdpTest
{
[Fact]
public void UdpServerInstance()
{
AsyncUdp udp = new AsyncUdp();
Assert.True(udp is AsyncUdp);
udp.Dispose();
}
[Fact]
public void StartStopUdpServer()
{
using (AsyncUdp udp = new AsyncUdp(5000))
{
udp.Start();
Thread.Sleep(5000);
}
}
string udpReceiveMessageSend = "This is a test message";
byte[] udpReceiveData = new byte[4096];
bool udpReceivePacketMatches = false;
[Fact]
public void UdpServerReceive()
{
using (AsyncUdp udp = new AsyncUdp(5000))
{
udp.Start();
udp.PacketReceived += new EventHandler(delegate(object sender, EventArgs e)
{
UDPPacketEventArgs ea = e as UDPPacketEventArgs;
if (this.udpReceiveMessageSend.Equals(ea.buffer.StringContent))
{
udpReceivePacketMatches = true;
}
});
Thread.Sleep(20);
UdpClient sock = new UdpClient();
IPEndPoint iep = new IPEndPoint(IPAddress.Loopback, 5000);
this.udpReceiveData = Encoding.ASCII.GetBytes(this.udpReceiveMessageSend);
sock.Send(this.udpReceiveData, this.udpReceiveData.Length, iep);
sock.Close();
Thread.Sleep(20);
Assert.True(udpReceivePacketMatches);
}
}
}
}
Note: code is C #, xUnit testing environment
Many thanks to everyone who takes time to go through my question and answer it!