I would create a web test that uses only one login at a time to login. Then you can either create a small piece of code in the webtest to get a "random" login from the database a list in code to select the login.
If you require a unique login for each test, you will have to estimate how many logins are required and prepopulate that many.
The issue with selecting from a database is that virtual users share threads and any blocking code in a test will block more than one virtual user.
It is possible to create a list in memory that holds all the login details, but manageing a shared object in a highly multithreaded environment require care.
For us, we created a stored procedure that would get the next login password and username and then called the following method to get the next login.
public static bool GetNextLogin(out string userName, out string password)
{
bool result = false;
using (SqlConnection connection = new SqlConnection(loadTestLoginsConnection))
{
using (SqlCommand command = new SqlCommand("GetNextID", connection))
{
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
userName = reader["UserName"].ToString().Trim();
password = reader["Password"].ToString().Trim();
result = true;
}
}
}
}
return result;
}
This worked without problems for us and the returned values could be added to the correct form post parameter. If you are using normal authentication, the same code can be used in the load test constructor to change the WebTest.UserName and WebTest.Password properties.
Our stored procedure worked on a list of available logins and a table with one int field called CurrentLoginID
BEGIN TRANSACTION
BEGIN TRY
DECLARE @CurrentID AS INT
UPDATE CurrentLoginID SET Number = Number+1
SELECT @CurrentID = Number FROM CurrentLoginID
SELECT [Password], UserName FROM AvailableLogins WHERE AvailableLogins.ID = @CurrentID
COMMIT
END TRY
BEGIN CATCH
DECLARE @ErrorMessage NVARCHAR(4000);
DECLARE @ErrorSeverity INT;
DECLARE @ErrorState INT;
SELECT
@ErrorMessage = ERROR_MESSAGE(),
@ErrorSeverity = ERROR_SEVERITY(),
@ErrorState = ERROR_STATE();
-- Use RAISERROR inside the CATCH block to return error
-- information about the original error that caused
-- execution to jump to the CATCH block.
RAISERROR (@ErrorMessage, -- Message text.
@ErrorSeverity, -- Severity.
@ErrorState -- State.
);
ROLLBACK TRAN
END CATCH