Build Query to Database to Validate Username and Password Task The Login user story has one business class User.cs and one data class UserData.cs that need unit tests.. public void Init{
Trang 1Developing the Login User Story
The first user story that we are going to develop is Login In iteration planning (Chapter 12),
this story was broken down into the following tasks:
• Create login screen
• Create text entry fields for username and password
• Build query to database to validate username and password
• Determine login request success or failureBefore you begin, you need to clean up the Northwind solution you created in Appendix
A Delete any empty (default) classes that were autogenerated by the IDE (Class1.cs) or web
pages (WebPage1.aspx or Default.aspx) and any test classes you created (CategoryTests.cs
and the associated Category.cs class) If you did not create the Northwind solution in
Appendix A, do that now
The first task that you are going to work on is the one to validate the username and word against the database
application The source code is intentionally kept as basic and simple as possible, so that you can focus on
the XP technique of developing software in a NET environment Apress has many great books on C# and the
.NET Framework that you can refer to for thorough coverage of those topics
Build Query to Database to Validate Username and Password Task
The Login user story has one business class (User.cs) and one data class (UserData.cs) that
need unit tests You are going to code iteratively, so you will start with the smallest unit test
possible
Using the test-driven development approach, you start with the UserTests.cs file shown
in Listing 13-1, which you need to add to the TestLayer project You will need to add a
refer-ence to the BusinessLayer and DataLayer projects on the TestLayer project, if you have not
done so already
Listing 13-1.UserTests.cs File
#region Using directives
Trang 2public void Init(){
}[TearDown]
public void Destroy(){
}[Test]
public void TestGetUser(){
UserData userData = new UserData();
Assert.IsNotNull(userData.GetUser("bogususer", "password"),
"GetUser returned a null value, gasp!");
}}}
If you build the solution now, you will get several errors because your web applicationdoes not have a concept of a UserData class To address that issue, you will need to define aminimal UserData class so you can successfully build but not pass the test Listing 13-2 showsthe minimal UserData.cs file that needs to be added to the DataLayer project You will need toadd a reference to the BusinessLayer project on the DataLayer project, if you have not done soalready
Listing 13-2.Minimal UserData.cs File
#region Using directives
Trang 3}public User GetUser(string username, string password){
User user = null;
return user;
}}}
Set the TestLayer project as the startup project and build the solution You still get piler errors—although the UserData class is now defined, you introduced another class (User)
com-that is yet to be defined
Listing 13-3 shows the minimal source for the User.cs class that needs to be added to theBusinessLayer project
Listing 13-3.Minimal User.cs File
#region Using directive
}}}
Trang 4USING A MOCK OBJECT
If the database had not been ready when you started coding this portion of the user story, you could haveused a mock object here instead To do that, you would first add a reference to the NMock DLL (nmock.dll)
to the TestLayer project Next, you would create an interface class called IUserData.cs that looks likethe following
#region Using directivesusing System;
using System.Collections.Generic;
using System.Text;
using BusinessLayer;
#endregionnamespace DataLayer{
interface IuserData{
User GetUser(string username, string password);
}}Then you would make the UserTests.cs class look like the following
#region Using directivesusing System;
[TestFixture]
public class UserTests{
public UserTests(){
}[SetUp]
public void Init(){
}
Trang 5public void Destroy(){
}[Test]
public void TestGetUser(){
DynamicMock userData = new DynamicMock (typeof(IUserData));
Assert.IsNotNull(userData.GetUser("bogususer", "password"),
"GetUser returned a null value, gasp!");
}}}When the database became available, you would implement the UserData.cs class as shown inListing 13-2 and have the UserData class inherit (implement) the IUserData interface At that time, youwould also update the UserTests.cs class to use the UserData.cs class instead of the mock object youimplemented
Now rebuild and run the solution You will not get any compiler errors, but when the testexecutes, the test fails That’s because you are simply returning a null value for the user Let’s
fix that first Start by modifying the UserData.cs file as shown in Listing 13-4
Listing 13-4.Modified UserData.cs File
#region Using directives
}
Trang 6public User GetUser(string username, string password){
User user = null;
user = new User();
return user;
}}}
Notice that you just wrote a test, coded a little, and then refactored This is the codinghabit you want to develop Once you have adopted this style of coding, you will find that youwill produce fewer bugs and have a greater sense that the quality of the code you are creating
is continually getting better
Now when you rebuild the solution, it builds just fine and your test passes, but nothing ofany significance is really happening To take the next step, you need to modify your UserDataclass to connect to the Northwind database to get the user’s role, along with the username andpassword, using the username and password passed to the UserData class from the UserTestsclass Listing 13-5 shows the UserData.cs file with these changes
Listing 13-5.UserData.cs File Modified to Connect to the Database
#region Using directives
private static string connectionString =
"Driver={Microsoft Access Driver (*.mdb)};" +
"DBQ=c:\\xpnet\\database\\Northwind.mdb";
public UserData(){
}public User GetUser(string username, string password){
User user = null;
Trang 7try{OdbcConnection dataConnection = new OdbcConnection();
OdbcDataReader dataReader = dataCommand.ExecuteReader();
// Make sure that we found our user
if ( dataReader.Read() ){
user = new User(dataReader.GetString(0),dataReader.GetString(1));
}dataConnection.Close();
}catch(Exception e){
Console.WriteLine("Error: " + e.Message);
}return user;
}}}
When you rebuild the solution now, you have a compile error because you don’t have aUser class that has a constructor that takes arguments Listing 13-6 shows the change to the
User class
Trang 8Listing 13-6.Modified User.cs File
#region Using directives
private string userName;
private string password;
public User(){
}public User(string userName, string password){
this.userName = userName;
this.password = password;
}}}
Lastly, you need to enhance the UserTests class to pass a username and password to theUserData class Since this is a test, you need to create test data that you feel confident will notexist in the database That way, you can set up the data and remove it when your test has com-pleted safely You will refactor the database connections better later, but for now, you will takethe simplest approach possible Listing 13-7 shows the modifications to the UserTests.cs file
Listing 13-7.Modified UserTests.cs File
#region Using directives
Trang 9// Build connection stringconnectionString =
new StringBuilder("Driver={Microsoft Access Driver (*.mdb)}");
connectionString.Append(";DBQ=c:\\xpnet\\database\Northwind.mdb");
}[SetUp]
public void Init(){
try{OdbcConnection dataConnection = new OdbcConnection();
commandText.Append(" VALUES ('bogususer', 'password')");
dataCommand.CommandText = commandText.ToString();
int rows = dataCommand.ExecuteNonQuery();
// Make sure that the INSERT workedAssert.AreEqual(1, rows, "Unexpected row count returned.");
dataConnection.Close();
}catch(Exception e){
Assert.Fail("Error: " + e.Message);
}}
Trang 10public void Destroy(){
try{OdbcConnection dataConnection = new OdbcConnection();
int rows = dataCommand.ExecuteNonQuery();
// Make sure that the DELETE worked Assert.AreEqual(1, rows, "Unexpected row count returned");
dataConnection.Close();
}catch(Exception e){
Assert.Fail("Error: " + e.Message);
}}[Test]
public void TestGetUser(){
UserData userData = new UserData();
Assert.IsNotNull(userData.GetUser("bogususer", "password"),
"GetUser returned a null value, gasp!");
}}}
Rebuild the solution again and run your tests If you get any errors, look at the build orruntime output to see where the error occurred
Don’t forget that testing is not just all “happy day” scenarios Add a negative test whereyou pass in a bad username and password, as shown in Listing 13-8 In this test, you shouldexpect to get a null user back, since the user should not exist in the database
Trang 11Listing 13-8.Negative Test for UserTests.cs
#region Using directives
// Build connection stringconnectionString =
new StringBuilder("Driver={Microsoft Access Driver (*.mdb)}");
connectionString.Append(";DBQ=c:\\xpnet\\database\Northwind.mdb");
}[SetUp]
public void Init(){
try{OdbcConnection dataConnection = new OdbcConnection();
commandText.Append(" VALUES ('bogususer', 'password')");
dataCommand.CommandText = commandText.ToString();
Trang 12int rows = dataCommand.ExecuteNonQuery();
// Make sure that the INSERT workedAssert.AreEqual(1, rows, "Unexpected row count returned.");
dataConnection.Close();
}catch(Exception e){
Assert.Fail("Error: " + e.Message);
}}[TearDown]
public void Destroy(){
try{OdbcConnection dataConnection = new OdbcConnection();
dataConnection.ConnectionString = connectionString.ToString();dataConnection.Open();
OdbcCommand dataCommand = new OdbcCommand();
dataCommand.Connection = dataConnection;
// Build command stringStringBuilder commandText =new StringBuilder("DELETE FROM Users WHERE username='bogususer'");dataCommand.CommandText = commandText.ToString();
int rows = dataCommand.ExecuteNonQuery();
// Make sure that the DELETE worked Assert.AreEqual(1, rows, "Unexpected row count returned");
dataConnection.Close();
}catch(Exception e){
Assert.Fail("Error: " + e.Message);
}}
Trang 13public void NegativeTestGetUser(){
UserData userData = new UserData();
Assert.IsNull(userData.GetUser("", ""),
"GetUser did not return a null value, gasp!");
}}}
Rebuild the solution and run the test again This time two tests should run, and bothshould pass successfully
Let’s move on to the Create Login Screen task for the Login user story
Create Login Screen Task
For the Create Login Screen task, you are going to add a new web form (.aspx) to the
NorthwindWeb project Name that web page Login.aspx Switch to the Source view of the file
and make it look like the code in Listing 13-9
Listing 13-9.Login.aspx File
<%@ Page language="C#" CodeFile="login.aspx.cs" Inherits=”Login_aspx %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
<asp:Label ID="titleLabel" style="z-index: 104;
left: 427px; position: absolute; top: 56px"
Runat="server">Northwind Login</asp:Label>
Trang 14<asp:Button ID="submitButton" OnClick="SubmitButton_Click"
Next, let’s tackle creating the text-entry fields for the login screen
Create Text Entry Fields for Username and Password Task
Now you need to add the data-entry fields to the login screen You will pass the entered name and password to the UserData.cs class for validation To accomplish this, you need tomake the Login.aspx file (Source view) look like Listing 13-11
user-Listing 13-11.Modified Login.aspx File
<%@ Page language="C#" CodeFile="login.aspx.cs" Inherits=”Login_aspx %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
<asp:Label ID="titleLabel" style="z-index: 104;
left: 427px; position: absolute; top: 56px"
Runat="server">Northwind Login</asp:Label>
Trang 15<asp:Label ID="usernameLabel" style="z-index: 101;
left: 362px; position: absolute; top: 126px"
Runat="server">Username:</asp:Label>
<asp:Label ID="passwordLabel" style="z-index: 102;
left: 364px; position: absolute; top: 184px"
Runat="server">Password:</asp:Label>
<asp:TextBox ID="usernameTextBox" style="z-index: 103;
left: 452px; position: absolute; top: 121px" TabIndex="1"
Runat="server" Width="145px" Height="22px">
</asp:TextBox>
<input style="z-index: 106; left: 451px; width: 145px;
position: absolute; top: 181px; height: 22px" tabindex="2"
type="password" name="passwordTextBox" id="passwordTextBox" />
<asp:Button ID="submitButton" OnClick="SubmitButton_Click"
Then enhance the Login.aspx.cs file as shown in Listing 13-12
Listing 13-12.Modified Login.aspx.cs File
string passwordText = Request.Params["passwordTextBox"];
UserData userData = new UserData();
User user = userData.GetUser(usernameTextBox.Text, passwordText);
}}
That’s it for this task Let’s move to the last one for this user story
Determine Login Request Success or Failure Task
Lastly, you need to give the users an indication of whether or not they successfully logged in
First, enhance the Login.aspx file as shown in Listing 13-13
Trang 16Listing 13-13.Further Modifications to Login.aspx
<%@ Page language="C#" CodeFile="login.aspx.cs" Inherits="Login_aspx %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
<asp:Label ID="titleLabel" style="z-index: 104;
left: 427px; position: absolute; top: 56px"
Runat="server">Northwind Login</asp:Label>
<asp:Label id="usernameLabel" style="z-index: 101;
left: 362px; position: absolute; top: 126px"
Runat="server">Username:</asp:Label>
<asp:Label ID="passwordLabel" style="z-index: 102;
left: 364px; position: absolute; top: 184px"
Runat="server">Password:</asp:Label>
<asp:TextBox ID="usernameTextBox" style="z-index: 103;
left: 452px; position: absolute; top: 121px" TabIndex="1"
Runat="server" Width="145px" Height="22px">
</asp:TextBox>
<input style="z-index: 106; left: 451px; width: 145px;
position: absolute; top: 181px; height: 22px" tabindex="2"
type="password" name="passwordTextBox" id="passwordTextBox" />
<asp:Button ID="submitButton" OnClick="SubmitButton_Click"
style="z-index: 105;
left: 576px; position: absolute; top: 231px"TabIndex="3"
Runat="server" Text="Login">
</asp:Button>
<asp:Label ID="successLabel" style="z-index: 107;
left: 332px; position: absolute; top: 311px"
Runat="server" Width="389px" Visible="False">
Trang 17Listing 13-14.Further Modifications to Login.aspx.cs
string passwordText = Request.Params["passwordTextBox"];
UserData userData = new UserData();
User user = userData.GetUser(usernameTextBox.Text, passwordText);
successLabel.Visible = true;
if (user != null){
// Go to main NorthwindWeb page eventually// But for now, just display a success messagesuccessLabel.Text = "User login succeeded, woohoo!";
}else{// Go back to this page to let the user try againsuccessLabel.Text = "User login failed, gasp!";
}}}
Now run your unit test to see if everything is passing Make sure that the TestLayer project
is set as the startup project Then rebuild the solution If everything built correctly, start the
TestLayer project by selecting Build ➤Start (or by pressing F5) Then click the Run button to
execute the unit tests You should get a green bar
Next, set the startup project to the NorthwindWeb project and Login.aspx as the start page
This will allow you to see your code in action, as a user would If all goes well, your web
browser should pop up and display the login web page
So, you have completed all the defined tasks for the Login user story You created a loginscreen with text-entry fields for the username and password, and you processed the login
request by verifying the username and password against the database You then determined if
the user successfully logged in and displayed either a success or failure message You also
cre-ated both positive and negative tests against the business layer of the web application You
have successfully completed your part of the user story as far as you know, but the user story is
not completed until the user story has been accepted by the customer For acceptance of the
user story, the customer must define the acceptance criteria with the help of the acceptance
tester, as discussed in the “Other Team Members’ Duties” section later in the chapter
Now let’s work on another user story