visit
While working for different software houses, on more than one occasion, I had the chance to work on an application that is mainly based on I/O File operations.
The Data file the application deals with is a text file with the extension .zzz
Every entry in the data file is in the form of {name},{age},{profession} as follows:Mohamed,20,Accountant
Patrick,26,Mechanical Engineer
Sara,23,Software Tester
Note that the entries are separated by the new line character \r\n
.
Click the Browse button to open a .zzz
file. The path of the file would appear in the read-only text box above the Browse button.
Click the Get All button so that the application reads that data from the selected .zzz
file and present them into the Reach Text Box at the bottom of the UI.
Click the Add button so that the application adds a hardcoded entry to the file and updates the Reach Text Box at the bottom of the UI.
Click the Remove button so that the application removes the last entry in the file and updates the Reach Text Box at the bottom of the UI.
All the code could be found on
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace IOAbstraction
{
public partial class FrmMain : Form
{
public FrmMain()
{
InitializeComponent();
}
private void Btn_Browse_Click(object sender, EventArgs e)
{
if (Ofd_Browse.ShowDialog() == DialogResult.OK)
{
Txt_Path.Text = Ofd_Browse.FileName;
}
}
private void GetAll()
{
Rtb_AllResults.Clear();
var lines = File.ReadAllLines(Txt_Path.Text);
var builder = new StringBuilder();
foreach (var line in lines)
{
if (!string.IsNullOrEmpty(line) && line.Contains(","))
{
var parts = line.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var name = parts[0];
var age = parts[1];
var profession = parts[2];
var message = $"Name: {name}, Age: {age}, Profession: {profession}.";
builder.AppendLine(message);
}
}
Rtb_AllResults.Text = builder.ToString();
}
private void Btn_Get_Click(object sender, EventArgs e)
{
GetAll();
}
private void Btn_Add_Click(object sender, EventArgs e)
{
var line = Environment.NewLine + "Ahmed,36,Software Engineer";
var text = TrimEndNewLine(File.ReadAllText(Txt_Path.Text)) + line;
File.WriteAllText(Txt_Path.Text, text);
GetAll();
}
private void Btn_Remove_Click(object sender, EventArgs e)
{
var lines = File.ReadAllLines(Txt_Path.Text);
File.WriteAllLines(Txt_Path.Text, lines.Take(lines.Length - 1));
GetAll();
}
private string TrimEndNewLine(string str)
{
var result = str;
while (result.EndsWith(Environment.NewLine))
{
result = result.Substring(0, result.Length - Environment.NewLine.Length);
}
return result;
}
}
}
This creates many challenges like:
System.IO.File
.
using System.Collections.Generic;
namespace IOAbstraction.SystemFileOperationsManager
{
public interface ISystemFileOperationsManager
{
string[] ReadAllLines(string path);
string ReadAllText(string path);
void WriteAllText(string path, string contents);
void WriteAllLines(string path, IEnumerable<string> contents);
}
}
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
namespace IOAbstraction.SystemFileOperationsManager
{
[ExcludeFromCodeCoverage]
public class NtfsOperationsManager : ISystemFileOperationsManager
{
public string[] ReadAllLines(string path)
{
return File.ReadAllLines(path);
}
public string ReadAllText(string path)
{
return File.ReadAllText(path);
}
public void WriteAllLines(string path, IEnumerable<string> contents)
{
File.WriteAllLines(path, contents);
}
public void WriteAllText(string path, string contents)
{
File.WriteAllText(path, contents);
}
}
}
ISystemFileOperationsManager
interface.System.IO.File
class.
namespace IOAbstraction.DataFileRepository
{
public interface IDataFileRepository
{
string GetAllDataText();
void AddNewDataEntryText(string dataEntryLine);
void RemoveLastDataEntryText();
}
}
using System;
using System.Linq;
using IOAbstraction.SystemFileOperationsManager;
namespace IOAbstraction.DataFileRepository
{
public class DataFileRepository : IDataFileRepository
{
private readonly ISystemFileOperationsManager m_SystemFileOperationsManager;
private readonly string m_DataFileFullPath;
public DataFileRepository(ISystemFileOperationsManager systemFileOperationsManager, string dataFileFullPath)
{
m_SystemFileOperationsManager = systemFileOperationsManager;
m_DataFileFullPath = dataFileFullPath;
}
public string GetAllDataText()
{
return m_SystemFileOperationsManager.ReadAllText(m_DataFileFullPath);
}
public void AddNewDataEntryText(string dataEntryLine)
{
var line = Environment.NewLine + dataEntryLine;
var text = TrimEndNewLine(m_SystemFileOperationsManager.ReadAllText(m_DataFileFullPath)) + line;
m_SystemFileOperationsManager.WriteAllText(m_DataFileFullPath, text);
}
public void RemoveLastDataEntryText()
{
var lines = m_SystemFileOperationsManager.ReadAllLines(m_DataFileFullPath);
m_SystemFileOperationsManager.WriteAllLines(m_DataFileFullPath, lines.Take(lines.Length - 1));
}
private string TrimEndNewLine(string str)
{
var result = str;
while (result.EndsWith(Environment.NewLine))
{
result = result.Substring(0, result.Length - Environment.NewLine.Length);
}
return result;
}
}
}
IDataFileRepository
interface.ISystemFileOperationsManager
and uses it to do the I/O File operations.
namespace IOAbstraction.DataManager.Model
{
public class DataEntry
{
public string Name { get; }
public string Age { get; }
public string Profession { get; }
public DataEntry(string name, string age, string profession)
{
Name = name;
Age = age;
Profession = profession;
}
}
}
Age
property here is implemented as string for simplicity.IEquatable<DataEntry>
to make it easy to apply comparison operations on it. I would leave this part for you to implement.
using System.Collections.Generic;
using IOAbstraction.DataManager.Model;
namespace IOAbstraction.DataTransformer
{
public interface IDataTransformer
{
IEnumerable<DataEntry> CombinedTextToDataEntries(string combinedText);
DataEntry TextToDataEntry(string text);
string DataEntryToText(DataEntry dataEntry);
}
}
DataEntry
.
using System;
using System.Collections.Generic;
using System.Linq;
using IOAbstraction.DataManager.Model;
namespace IOAbstraction.DataTransformer
{
public class DataTransformer : IDataTransformer
{
public IEnumerable<DataEntry> CombinedTextToDataEntries(string combinedText)
{
var result = new List<DataEntry>();
var lines = combinedText.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (!string.IsNullOrEmpty(line) && line.Contains(","))
{
result.Add(TextToDataEntry(line));
}
}
return result.Where(r => r != null);
}
public DataEntry TextToDataEntry(string text)
{
DataEntry result = null;
if (!string.IsNullOrEmpty(text) && text.Contains(","))
{
var parts = text.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var name = parts[0];
var age = parts[1];
var profession = parts[2];
result = new DataEntry(name, age, profession);
}
return result;
}
public string DataEntryToText(DataEntry dataEntry)
{
return $"{dataEntry.Name},{dataEntry.Age},{dataEntry.Profession}";
}
}
}
IDataTransformer
interface.DataEntry
.
using System.Collections.Generic;
using IOAbstraction.DataManager.Model;
namespace IOAbstraction.DataManager
{
public interface IDataManager
{
IEnumerable<DataEntry> GetAllData();
void AddNewDataEntry(DataEntry newEntry);
void RemoveLastDataEntryText();
}
}
using System.Collections.Generic;
using IOAbstraction.DataFileRepository;
using IOAbstraction.DataManager.Model;
using IOAbstraction.DataTransformer;
namespace IOAbstraction.DataManager
{
public class DataManager : IDataManager
{
private readonly IDataFileRepository m_DataFileRepository;
private readonly IDataTransformer m_DataTransformer;
public DataManager(IDataFileRepository dataFileRepository, IDataTransformer dataTransformer)
{
m_DataFileRepository = dataFileRepository;
m_DataTransformer = dataTransformer;
}
public IEnumerable<DataEntry> GetAllData()
{
return m_DataTransformer.CombinedTextToDataEntries(m_DataFileRepository.GetAllDataText());
}
public void AddNewDataEntry(DataEntry newEntry)
{
m_DataFileRepository.AddNewDataEntryText(m_DataTransformer.DataEntryToText(newEntry));
}
public void RemoveLastDataEntryText()
{
m_DataFileRepository.RemoveLastDataEntryText();
}
}
}
IDataManager
interface.IDataFileRepository
and uses it to persist and retrieve data in and from Data files.IDataTransformer
and uses it to perform the required conversions.
using System.Collections.Generic;
using System.Linq;
using IOAbstraction.DataManager;
using IOAbstraction.DataManager.Model;
namespace IOAbstraction.MainApplication
{
public class MainApplication
{
private readonly IDataManager m_DataManager;
public MainApplication(IDataManager dataManager)
{
m_DataManager = dataManager;
}
public IEnumerable<string> GetAllToPresentInUi()
{
return m_DataManager
.GetAllData()
.Select(entry => $"Name: {entry.Name}, Age: {entry.Age}, Profession: {entry.Profession}")
.ToList();
}
public void Add(DataEntry entry)
{
m_DataManager.AddNewDataEntry(entry);
}
public void Remove()
{
m_DataManager.RemoveLastDataEntryText();
}
}
}
using System;
using System.Windows.Forms;
using IOAbstraction.DataManager.Model;
using IOAbstraction.SystemFileOperationsManager;
namespace IOAbstraction
{
public partial class FrmMain : Form
{
private MainApplication.MainApplication m_MainApplication;
public FrmMain()
{
InitializeComponent();
}
private void Btn_Browse_Click(object sender, EventArgs e)
{
if (Ofd_Browse.ShowDialog() == DialogResult.OK)
{
Txt_Path.Text = Ofd_Browse.FileName;
var ntfsOperationsManager = new NtfsOperationsManager();
var dataTransformer = new DataTransformer.DataTransformer();
var dataFileRepository =
new DataFileRepository.DataFileRepository(ntfsOperationsManager, Txt_Path.Text);
var dataManager = new DataManager.DataManager(dataFileRepository, dataTransformer);
m_MainApplication = new MainApplication.MainApplication(dataManager);
}
}
private void GetAll()
{
Rtb_AllResults.Clear();
var lines = m_MainApplication.GetAllToPresentInUi();
Rtb_AllResults.Text = String.Join(Environment.NewLine, lines);
}
private void Btn_Get_Click(object sender, EventArgs e)
{
GetAll();
}
private void Btn_Add_Click(object sender, EventArgs e)
{
m_MainApplication.Add(new DataEntry("Ahmed", "36", "Software Engineer"));
GetAll();
}
private void Btn_Remove_Click(object sender, EventArgs e)
{
m_MainApplication.Remove();
GetAll();
}
}
}
MainApplication
class and uses it to execute the main business logic of the application.
using System.Collections.Generic;
using System.Linq;
using IOAbstraction.SystemFileOperationsManager;
using Moq;
using NUnit.Framework;
namespace IOAbstraction.UnitTests
{
[TestFixture]
public class DataFileRepositoryTests
{
private const string DummyDataFilePath = "This is a dummy path for testing";
private Mock<ISystemFileOperationsManager> m_SystemFileOperationsManagerMock;
private DataFileRepository.DataFileRepository m_Sut;
[SetUp]
public void SetUp()
{
m_SystemFileOperationsManagerMock = new Mock<ISystemFileOperationsManager>();
m_Sut = new DataFileRepository.DataFileRepository(m_SystemFileOperationsManagerMock.Object,
DummyDataFilePath);
}
[TearDown]
public void TearDown()
{
m_Sut = null;
m_SystemFileOperationsManagerMock = null;
}
[Test]
public void GetAllDataText_ShouldReturnAllData()
{
// Arrange
var text = "This is the sample text";
m_SystemFileOperationsManagerMock
.Setup
(
m => m.ReadAllText(It.Is<string>(p => p == DummyDataFilePath))
)
.Returns(text)
.Verifiable();
// Act
var actual = m_Sut.GetAllDataText();
// Assert
m_SystemFileOperationsManagerMock
.Verify
(
m => m.ReadAllText(DummyDataFilePath)
);
Assert.AreEqual(text, actual);
}
[TestCase(
"Mohamed,20,Accountant",
"Ahmed,36,Software Engineer",
"Mohamed,20,Accountant" + "\r\n" + "Ahmed,36,Software Engineer",
TestName = "Test Case 01")]
[TestCase(
"Mohamed,20,Accountant\r\n",
"Ahmed,36,Software Engineer",
"Mohamed,20,Accountant" + "\r\n" + "Ahmed,36,Software Engineer",
TestName = "Test Case 02")]
public void AddNewDataEntryText_ShouldAddDataInCorrectFormat(string existingData, string input, string expected)
{
// Arrange
m_SystemFileOperationsManagerMock
.Setup
(
m => m.ReadAllText(It.Is<string>(p => p == DummyDataFilePath))
)
.Returns(existingData)
.Verifiable();
m_SystemFileOperationsManagerMock
.Setup
(
m => m.WriteAllText
(
It.Is<string>(p => p == DummyDataFilePath),
It.Is<string>(p => p == expected)
)
)
.Verifiable();
// Act
m_Sut.AddNewDataEntryText(input);
// Assert
m_SystemFileOperationsManagerMock
.Verify
(
m => m.ReadAllText(DummyDataFilePath)
);
m_SystemFileOperationsManagerMock
.Verify
(
m => m.WriteAllText
(
DummyDataFilePath,
expected
)
);
}
[Test]
public void RemoveLastDataEntryText_ShouldRemoveTheLastLine()
{
// Arrange
var lines = new[] { "Line 1", "Line 2", "Line 3" };
var expected = new[] { "Line 1", "Line 2" };
m_SystemFileOperationsManagerMock
.Setup
(
m => m.ReadAllLines(It.Is<string>(p => p == DummyDataFilePath))
)
.Returns(lines)
.Verifiable();
m_SystemFileOperationsManagerMock
.Setup
(
m => m.WriteAllLines
(
It.Is<string>(p => p == DummyDataFilePath),
It.Is<IEnumerable<string>>(
p => p.Count() == 2 &&
p.ElementAt(0) == expected[0] &&
p.ElementAt(1) == expected[1])
)
)
.Verifiable();
// Act
m_Sut.RemoveLastDataEntryText();
// Assert
m_SystemFileOperationsManagerMock
.Verify
(
m => m.ReadAllLines(DummyDataFilePath)
);
m_SystemFileOperationsManagerMock
.Verify
(
m => m.WriteAllLines
(
DummyDataFilePath,
It.Is<IEnumerable<string>>(
p => p.Count() == 2 &&
p.ElementAt(0) == expected[0] &&
p.ElementAt(1) == expected[1])
)
);
}
}
}
using System.Collections.Generic;
using System.Linq;
using IOAbstraction.DataFileRepository;
using IOAbstraction.DataManager.Model;
using IOAbstraction.DataTransformer;
using Moq;
using NUnit.Framework;
namespace IOAbstraction.UnitTests
{
[TestFixture]
public class DataManagerTests
{
private Mock<IDataFileRepository> m_DataFileRepositoryMock;
private Mock<IDataTransformer> m_DataTransformerMock;
private DataManager.DataManager m_Sut;
[SetUp]
public void SetUp()
{
m_DataFileRepositoryMock = new Mock<IDataFileRepository>();
m_DataTransformerMock = new Mock<IDataTransformer>();
m_Sut = new DataManager.DataManager(m_DataFileRepositoryMock.Object, m_DataTransformerMock.Object);
}
[TearDown]
public void TearDown()
{
m_Sut = null;
m_DataFileRepositoryMock = null;
m_DataTransformerMock = null;
}
[Test]
public void GetAllData_ShouldGetAllData()
{
// Arrange
var allDataText = "Mohamed,20,Accountant\r\nPatrick,26,Mechanical Engineer";
var allData = new List<DataEntry>
{
new DataEntry("Mohamed", "20", "Accountant"),
new DataEntry("Patrick", "26", "Mechanical Engineer")
};
m_DataFileRepositoryMock
.Setup
(
m => m.GetAllDataText()
)
.Returns(allDataText)
.Verifiable();
m_DataTransformerMock
.Setup
(
m => m.CombinedTextToDataEntries(
It.Is<string>(p => p == allDataText)
)
)
.Returns(allData)
.Verifiable();
// Act
var actual = m_Sut.GetAllData();
// Assert
m_DataFileRepositoryMock
.Verify
(
m => m.GetAllDataText()
);
m_DataTransformerMock
.Verify
(
m => m.CombinedTextToDataEntries(allDataText)
);
Assert.AreEqual(2, actual.Count());
Assert.AreEqual("Mohamed", actual.ElementAt(0).Name);
Assert.AreEqual("20", actual.ElementAt(0).Age);
Assert.AreEqual("Accountant", actual.ElementAt(0).Profession);
Assert.AreEqual("Patrick", actual.ElementAt(1).Name);
Assert.AreEqual("26", actual.ElementAt(1).Age);
Assert.AreEqual("Mechanical Engineer", actual.ElementAt(1).Profession);
}
[Test]
public void AddNewDataEntry_ShouldAddNewDataEntry()
{
// Arrange
var entry = new DataEntry("Mohamed", "20", "Accountant");
var entryText = "Mohamed,20,Accountant";
var allData = new List<DataEntry>
{
new DataEntry("Patrick", "26", "Mechanical Engineer")
};
m_DataTransformerMock
.Setup
(
m => m.DataEntryToText(
It.Is<DataEntry>(p => p == entry)
)
)
.Returns(entryText)
.Verifiable();
m_DataFileRepositoryMock
.Setup
(
m => m.AddNewDataEntryText
(
It.Is<string>(p => p == entryText)
)
)
.Verifiable();
// Act
m_Sut.AddNewDataEntry(entry);
// Assert
m_DataTransformerMock
.Verify
(
m => m.DataEntryToText(entry)
);
m_DataFileRepositoryMock
.Verify
(
m => m.AddNewDataEntryText(entryText)
);
}
[Test]
public void RemoveLastDataEntryText_RemoveLastDataEntry()
{
// Arrange
m_DataFileRepositoryMock
.Setup
(
m => m.RemoveLastDataEntryText()
)
.Verifiable();
// Act
m_Sut.RemoveLastDataEntryText();
// Assert
m_DataFileRepositoryMock
.Verify
(
m => m.RemoveLastDataEntryText()
);
}
}
}
using System.Linq;
using IOAbstraction.DataManager.Model;
using NUnit.Framework;
namespace IOAbstraction.UnitTests
{
[TestFixture]
public class DataTransformerTests
{
private DataTransformer.DataTransformer m_Sut;
[SetUp]
public void SetUp()
{
m_Sut = new DataTransformer.DataTransformer();
}
[TearDown]
public void TearDown()
{
m_Sut = null;
}
[Test]
public void CombinedTextToDataEntries_ShouldConvertCombinedEntriesTextIntoDataEntries01()
{
// Arrange
var combinedText = "Mohamed,20,Accountant\r\nPatrick,26,Mechanical Engineer";
// Act
var actual = m_Sut.CombinedTextToDataEntries(combinedText);
// Assert
Assert.AreEqual(2, actual.Count());
Assert.AreEqual("Mohamed", actual.ElementAt(0).Name);
Assert.AreEqual("20", actual.ElementAt(0).Age);
Assert.AreEqual("Accountant", actual.ElementAt(0).Profession);
Assert.AreEqual("Patrick", actual.ElementAt(1).Name);
Assert.AreEqual("26", actual.ElementAt(1).Age);
Assert.AreEqual("Mechanical Engineer", actual.ElementAt(1).Profession);
}
[Test]
public void CombinedTextToDataEntries_ShouldConvertCombinedEntriesTextIntoDataEntries02()
{
// Arrange
var combinedText = "Mohamed,20,Accountant\r\n";
// Act
var actual = m_Sut.CombinedTextToDataEntries(combinedText);
// Assert
Assert.AreEqual(1, actual.Count());
Assert.AreEqual("Mohamed", actual.ElementAt(0).Name);
Assert.AreEqual("20", actual.ElementAt(0).Age);
Assert.AreEqual("Accountant", actual.ElementAt(0).Profession);
}
[Test]
public void CombinedTextToDataEntries_ShouldConvertCombinedEntriesTextIntoDataEntries03()
{
// Arrange
var combinedText = "Mohamed,20,Accountant\r\n\r\nPatrick,26,Mechanical Engineer";
// Act
var actual = m_Sut.CombinedTextToDataEntries(combinedText);
// Assert
Assert.AreEqual(2, actual.Count());
Assert.AreEqual("Mohamed", actual.ElementAt(0).Name);
Assert.AreEqual("20", actual.ElementAt(0).Age);
Assert.AreEqual("Accountant", actual.ElementAt(0).Profession);
Assert.AreEqual("Patrick", actual.ElementAt(1).Name);
Assert.AreEqual("26", actual.ElementAt(1).Age);
Assert.AreEqual("Mechanical Engineer", actual.ElementAt(1).Profession);
}
[Test]
public void TextToDataEntry_ShouldConvertEntryTextToDataEntry01()
{
// Arrange
var combinedText = "Mohamed,20,Accountant";
// Act
var actual = m_Sut.TextToDataEntry(combinedText);
// Assert
Assert.AreEqual("Mohamed", actual.Name);
Assert.AreEqual("20", actual.Age);
Assert.AreEqual("Accountant", actual.Profession);
}
[Test]
public void TextToDataEntry_ShouldConvertEntryTextToDataEntry02()
{
// Arrange
var combinedText = "";
// Act
var actual = m_Sut.TextToDataEntry(combinedText);
// Assert
Assert.IsNull(actual);
}
[Test]
public void TextToDataEntry_ShouldConvertEntryTextToDataEntry03()
{
// Arrange
var combinedText = "Mohamed20Accountant";
// Act
var actual = m_Sut.TextToDataEntry(combinedText);
// Assert
Assert.IsNull(actual);
}
[Test]
public void DataEntryToText_ShouldConvertDataEntryToDataText()
{
// Arrange
var entry = new DataEntry("Mohamed", "20", "Accountant");
var expectedText = "Mohamed,20,Accountant";
// Act
var actual = m_Sut.DataEntryToText(entry);
// Assert
Assert.AreEqual(expectedText, actual);
}
}
}
using System.Collections.Generic;
using IOAbstraction.DataManager;
using IOAbstraction.DataManager.Model;
using Moq;
using NUnit.Framework;
namespace IOAbstraction.UnitTests
{
[TestFixture]
public class MainApplicationTests
{
private Mock<IDataManager> m_DataManagerMock;
private MainApplication.MainApplication m_Sut;
[SetUp]
public void SetUp()
{
m_DataManagerMock = new Mock<IDataManager>();
m_Sut = new MainApplication.MainApplication(m_DataManagerMock.Object);
}
[TearDown]
public void TearDown()
{
m_Sut = null;
m_DataManagerMock = null;
}
[Test]
public void GetAllToPresentInUi_ShouldGetAllDataIntoTextFormatToPresentInUi()
{
// Arrange
var entries = new List<DataEntry>
{
new DataEntry("Mohamed", "20", "Accountant"),
new DataEntry("Patrick", "26", "Mechanical Engineer")
};
var expected = new string[]
{
"Name: Mohamed, Age: 20, Profession: Accountant",
"Name: Patrick, Age: 26, Profession: Mechanical Engineer",
};
m_DataManagerMock
.Setup
(
m => m.GetAllData()
)
.Returns(entries)
.Verifiable();
// Act
var actual = m_Sut.GetAllToPresentInUi();
// Assert
CollectionAssert.AreEqual(expected, actual);
}
[Test]
public void Add_ShouldAddEntry()
{
// Arrange
var entry = new DataEntry("Mohamed", "20", "Accountant");
m_DataManagerMock
.Setup
(
m => m.AddNewDataEntry
(
It.Is<DataEntry>(p => p.Name == entry.Name && p.Age == entry.Age &&
p.Profession == entry.Profession)
)
)
.Verifiable();
// Act
m_Sut.Add(entry);
// Assert
m_DataManagerMock
.Verify
(
m => m.AddNewDataEntry
(
It.Is<DataEntry>(p => p.Name == entry.Name && p.Age == entry.Age &&
p.Profession == entry.Profession)
)
);
}
[Test]
public void Remove_ShouldRemoveLastEntry()
{
// Arrange
m_DataManagerMock
.Setup
(
m => m.RemoveLastDataEntryText()
)
.Verifiable();
// Act
m_Sut.Remove();
// Assert
m_DataManagerMock
.Verify
(
m => m.RemoveLastDataEntryText()
);
}
}
}
As you can notice from the screenshot, the only missing part from the coverage is the Form
code itself. Could it be covered?