# 202002 TrendMicro
# Code Commits
## Create Budgets
### CreateBudgets.feature
Feature: CreateBudgets
In order to manage budget of department
As a department manager
I want to set budget amount of specific year month
Scenario: create a budget
Given budget for setting
| YearMonth | Amount |
| 202003 | 31 |
When I create
Then it should be created successfully
And there should be budgets existed
| YearMonth | Amount |
| 202003 | 31 |
### CreateBudgetsSteps.cs
[Binding]
public class CreateBudgetsSteps : Steps
{
private static CreateBudgetsPage _createBudgetsPage;
private CreateBudgetResultPage _createBudgetResultPage;
[BeforeTestRun]
public static void SetUpTestRun()
{
AtataContext.GlobalConfiguration.UseChrome().
//WithArguments("start-maximized").
WithLocalDriverPath().UseBaseUrl("http://localhost:50564/").UseCulture("en-us")
.UseAllNUnitFeatures();
}
[BeforeScenario]
public static void SetUpScenario()
{
AtataContext.Configure().Build();
_createBudgetsPage = Go.To<CreateBudgetsPage>();
}
[AfterScenario]
public static void TearDownScenario()
{
AtataContext.Current?.CleanUp();
}
[Given(@"budget for setting")]
public void GivenBudgetForSetting(Table table)
{
var budget = table.CreateInstance<Budget>();
ScenarioContext.Set(budget);
}
[When(@"I create")]
public void WhenICreate()
{
var budget = ScenarioContext.Get<Budget>();
_createBudgetResultPage = _createBudgetsPage.Create(budget);
}
[Then(@"it should be created succeed")]
public void ThenItShouldBeCreatedSuccessfully()
{
_createBudgetResultPage.Status.Should.ContainAll("created", "succeed");
}
[Then(@"there should be budgets existed")]
public void ThenThereShouldBeBudgetsExisted(Table table)
{
using (var dbContext = new AccountingEntitiesForTest())
{
var budgets = dbContext.Budgets.ToList();
table.CompareToSet(budgets);
}
}
}
### CreateBudgetsPage.cs
using _ = CreateBudgetsPage;
[Url("budgets/create")]
public class CreateBudgetsPage : Page<_>
{
public CreateBudgetResultPage Create(Budget budget)
{
return this
.YearMonth.Set(budget.YearMonth)
.Amount.Set(budget.Amount)
.CreateBudget.ClickAndGo();
}
[FindByName("Create")] public Button<CreateBudgetResultPage, _> CreateBudget { get; set; }
[FindByName("Amount")] public NumberInput<_> Amount { get; set; }
[FindByName("YearMonth")] public TextInput<_> YearMonth { get; set; }
}
### CreateBudgetResultPage.cs
using _ = CreateBudgetResultPage;
public class CreateBudgetResultPage : Page<_>
{
[FindByName("Status")]
public Text<_> Status { get; set; }
}
### BudgetsController.cs
public class BudgetsController : Controller
{
[HttpGet]
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult CreateBudgets()
{
return View();
}
}
### Create.cshtml
@{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Create</h2>
@using (Html.BeginForm("CreateBudgets", "Budgets", FormMethod.Post))
{
<div>
YearMonth: <input type="text" name="YearMonth"/>
</div>
<div>
Amount: <input type="number" name="Amount"/>
</div>
<input type="submit" value="Create" name="Create"/>
}
### CreateBudgets.cshtml
@{
ViewBag.Title = "CreateBudgets";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>CreateBudgets</h2>
<p name="Status">budget created succeed!</p>
## Add Unit Test Project for TDD
Install nuget packages
- NSubstitute
- FluentAssertions
### BudgetsControllerTests.cs
[TestFixture()]
public class BudgetsControllerTests
{
[Test()]
public void create_a_budget_should_invoke_budgetService_save()
{
var budgetService = Substitute.For<IBudgetService>();
var budgetsController = new BudgetsController(budgetService);
budgetsController.CreateBudgets("202003", 31m);
budgetService.Received().Save("202003", 31m);
}
}
### BudgetController.cs CreateBudgets()
public class BudgetsController : Controller
{
private readonly IBudgetService _budgetService;
public BudgetsController(IBudgetService budgetService)
{
_budgetService = budgetService;
}
[HttpPost]
public ActionResult CreateBudgets(string yearMonth, decimal amount)
{
_budgetService.Save(yearMonth, amount);
return View();
}
}
### Add a Failed Unit Test: create a budget succeed
[Test]
public void create_a_budget_succeed()
{
var viewResult = _budgetsController.CreateBudgets("202003", 31m) as ViewResult;
(viewResult.ViewBag.Status as string).Should().ContainAll("created", "succeed");
}
### Add a Constructor of BudgetsController to Initialize BudgetService
public class BudgetsController : Controller
{
private readonly IBudgetService _budgetService;
public BudgetsController()
{
_budgetService = new BudgetService();
}
public BudgetsController(IBudgetService budgetService)
{
_budgetService = budgetService;
}
}
### BudgetService.cs
public class BudgetService : IBudgetService
{
public void Save(string yearMonth, decimal amount)
{
Console.WriteLine("todo");
}
}
### Add Entities from EF in Web Application

### BudgetService.Save() using DbContext to Insert a Budget
public class BudgetService : IBudgetService
{
public void Save(string yearMonth, decimal amount)
{
using (var dbContext = new AccountingEntities())
{
dbContext.Budgets.Add(new Budget() { YearMonth = yearMonth, Amount = amount });
dbContext.SaveChanges();
}
}
}
### Can’t Repeat Acceptance Test Because of Insert Duplicate PKey Budget

### Clean Budgets Table Data Before Scenario Running
[BeforeScenario]
public static void SetUpScenario()
{
AtataContext.Configure().Build();
using (var dbContext = new AccountingEntitiesForTest())
{
dbContext.Database.ExecuteSqlCommand("TRUNCATE TABLE [Budgets]");
}
_createBudgetsPage = Go.To<CreateBudgetsPage>();
}
## Update the Budget When Budget Existed
### Scenario: update the budget when budget existed
Scenario: update the budget when budget existed
Given budget for setting
| YearMonth | Amount |
| 202003 | 310 |
And there are budgets
| YearMonth | Amount |
| 202003 | 31 |
When I create
Then it should be updated succeed
And there should be budgets existed
| YearMonth | Amount |
| 202003 | 310 |
### New Steps for Initialized Budget Data and Assertion for Updated Status
[Given(@"there are budgets")]
public void GivenThereAreBudgets(Table table)
{
using (var dbContext = new AccountingEntitiesForTest())
{
var budgets = table.CreateSet<Budget>();
dbContext.Budgets.AddRange(budgets);
dbContext.SaveChanges();
}
}
[Then(@"it should be updated succeed")]
public void ThenItShouldBeUpdatedSucceed()
{
_createBudgetResultPage.Status.Should.ContainAll("updated", "succeed");
}
### Add a Unit Test for BudgetsController: update the budget when budget existed
[Test]
public void update_the_budget_when_budget_existed()
{
_budgetService.Save(Arg.Any<string>(), Arg.Any<decimal>())
.ReturnsForAnyArgs(true);
var viewResult = _budgetsController.CreateBudgets("202003", 31m) as ViewResult;
(viewResult.ViewBag.Status as string).Should().ContainAll("updated", "succeed");
}
### Complete BudgetsController.CreateBudgets() when update the budget
[HttpPost]
public ActionResult CreateBudgets(string yearMonth, decimal amount)
{
var isUpdated = _budgetService.Save(yearMonth, amount);
ViewBag.Status = isUpdated ? "budget updated succeed!" : "budget created succeed!";
return View();
}
### Complete BudgetService.Save() when update and insert budget
public class BudgetService : IBudgetService
{
public bool Save(string yearMonth, decimal amount)
{
using (var dbContext = new AccountingEntities())
{
var budget = dbContext.Budgets.Find(yearMonth);
var isBudgetExisted = budget != null;
if (isBudgetExisted)
{
budget.Amount = amount;
}
else
{
dbContext.Budgets.Add(new Budget() { YearMonth = yearMonth, Amount = amount });
}
dbContext.SaveChanges();
return isBudgetExisted;
}
}
}
## Query Budgets
### QueryBudgets.Feature
Feature: QueryBudgets
In order to control budgets
As a manager
I want to query total amount of a period
Scenario: no budgets
When I query between "20200401" and "20200401"
Then the total amount should be 0.00
### QueryBudgetSteps
```
[Binding]
[Scope(Feature = "QueryBudgets")]
public class QueryBudgetsSteps : Steps
{
private QueryBudgetPage _queryBudgetPage;
[BeforeScenario()]
public void BeforeScenario()
{
AtataContext.Configure()
.UseChrome()
.UseBaseUrl("http://localhost:50564/")
.UseCulture("en-us").UseNUnitTestName()
.AddNUnitTestContextLogging().LogNUnitError()
.UseAssertionExceptionType<NUnit.Framework.AssertionException>()
.UseNUnitAggregateAssertionStrategy().Build();
_queryBudgetPage = Go.To<QueryBudgetPage>();
}
[AfterScenario()]
public void AfterScenario()
{
AtataContext.Current?.CleanUp();
}
[When(@"I query between ""(.*)"" and ""(.*)""")]
public void WhenIQueryBetweenAnd(string start, string end)
{
_queryBudgetPage.Query(start, end);
}
[Then(@"the total amount should be (.*)")]
public void ThenTheTotalAmountShouldBe(string expectedAmount)
{
_queryBudgetPage.TotalAmount.Should.Equal(expectedAmount);
}
}
```
### QueryBudgetPage.cs
```
using _ = QueryBudgetPage;
[Url("budgets/query")]
public class QueryBudgetPage : Page<_>
{
public void Query(string start, string end)
{
this
.Start.Set(start)
.End.Set(end)
.QueryBudget.Click();
}
[FindByName("Query")] public Button<_> QueryBudget { get; set; }
[FindByName("End")] public TextInput<_> End { get; set; }
[FindByName("Start")] public TextInput<_> Start { get; set; }
[FindByName("TotalAmount")] public Text<_> TotalAmount { get; set; }
}
```
### BudgetsController.cs
```
public class BudgetsController : Controller
{
private readonly IBudgetManager _budgetManager;
public BudgetsController()
{
_budgetManager = new BudgetManager();
}
public BudgetsController(IBudgetManager budgetManager)
{
_budgetManager = budgetManager;
}
// GET: Budgets
public ActionResult Create()
{
return View();
}
[HttpPost]
public ActionResult CreateBudget(string yearMonth, decimal amount)
{
var isUpdated = _budgetManager.Save(yearMonth, amount);
if (isUpdated)
{
ViewBag.Status = "budget updated succeed";
}
else
{
ViewBag.Status = "budget created succeed";
}
return View();
}
[HttpGet]
public ActionResult Query()
{
return View();
}
[HttpPost]
public ActionResult Query(QueryBudgetViewModel queryBudgetViewModel)
{
return View(queryBudgetViewModel);
}
}
```
### [View] Query.cshtml
```
@model AccountingWeb.ViewModels.QueryBudgetViewModel
@{
ViewBag.Title = "Query";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Query</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>QueryBudgetViewModel</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.Start, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Start, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.Start, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.End, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.End, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.End, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<p name="TotalAmount">
@if (Model != null)
{
@Model.TotalAmount.ToString("F2")
}
</p>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Query" name="Query" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
```
### QueryBudgetViewModel.cs
```
public class QueryBudgetViewModel
{
public string Start { get; set; }
public string End { get; set; }
public decimal TotalAmount { get; set; }
}
```
### BudgetsControllerTests.cs
```
[Test]
public void query_budgets()
{
_budgetManager.TotalAmount(
new DateTime(2020, 4, 1),
new DateTime(2020, 4, 1)
).Returns(91m);
var viewResult = _budgetsController.Query(new QueryBudgetViewModel
{
Start = "20200401",
End = "20200401"
}) as ViewResult;
(viewResult.Model as QueryBudgetViewModel)
.TotalAmount.Should().Be(91m);
}
```
### BudgetsController.cs
```
[HttpPost]
public ActionResult Query(QueryBudgetViewModel queryBudgetViewModel)
{
var start = DateTime.ParseExact( queryBudgetViewModel.Start,
"yyyyMMdd", null);
var end = DateTime.ParseExact( queryBudgetViewModel.End,
"yyyyMMdd", null);
var totalAmount = _budgetManager.TotalAmount(start, end);
queryBudgetViewModel.TotalAmount = totalAmount;
return View(queryBudgetViewModel);
}
```
### BudgetManager.cs
```
public decimal TotalAmount(DateTime start, DateTime end)
{
throw new NotImplementedException();
}
```