First, an aside: is there an IRC channel to discuss DbLinq?
I'm gratified that DbLinq contains unit tests for all the providers.
However, as a developer I dislike any tests that require an actually
running database, particularly in the case of DbLinq which is trying to
write a LINQ provider for 7 different databases. I do not look forward
to installing and configuring 7 different database systems, much less
running tests for all of them...
For a project predicated upon databases, this does seem to be an odd
complaint. ;-)
There is a method to my madness, though: "removing" the database
requirement would make it easier to run unit tests on systems that don't
have any databases (most useful from my perspective, as I want to get
some form of unit tests within Mono for System.Data.Linq). This should
also make life easier for new developers -- less "grunt" work to do (in
the form of setting up a DB server) before working on more interesting
development, etc.
Note: I am NOT advocating that we drop the existing unit tests.
What I am advocating is a layered testing strategy; for DbLinq, there
are logically 3 layers (at least) that can be tested (more or less)
separately:
1. IQueryable -> SQL command (to send to backend provider)
2. IDataReader -> result sets
3. Database -> IDataReader
Strictly speaking, (3) is under the responsibility of the database
provider, not the LINQ provider, so can be ignored (e.g. there's no
reason for DbLinq to test SqlDataReader...).
The existing unit tests would remain as "end-to-end" integration tests,
ensuring that things actually work when the more targeted tests
suggested above work.
How would this work?
I haven't looked at layer (2) yet, but layer (1) can be solved through
string comparisons on two "well known" types: DataContext.Log and
DbCommand.CommandText (as returned from DataContext.GetCommand). For
example, this works quite nicely on current DbLinq w/o requiring any
database support:
DataContext context = new DataContext(new NullConnection());
var foos =
from p in context.GetTable<Person>()
where p.FirstName == "foo"
select p;
var cmd = context.GetCommand(foos);
Assert.AreEqual(ExpectedText, cmd.CommandText);
(NullConnection is a DbConnection subclass that does nothing on required
void-returning methods and throws an exception on all other methods.
It's enough to create a DataContext and elicit "interesting" behavior,
as seen above, without requiring a full-blown database.)
DataContext.Log can be used for checking the SQL generated for e.g.
DataContext.ExecuteCommand() and DataContext.ExecuteQuery() (along with
LINQ expressions that are immediately evaluated, such as .Count()).
A prototype of such an implementation is attached.
Is this testing strategy something the DbLinq team would be interested
in implementing?
Thanks,
- Jon
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"DbLinq" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/dblinq?hl=en
-~----------~----~----~----~------~----~------~--~---
#region MIT license
//
// MIT license
//
// Copyright (c) 2009 Novell, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
using System;
using System.Data;
using System.Data.Common;
using System.IO;
#if MONO_STRICT
using System.Data.Linq;
using System.Data.Linq.Mapping;
#else
using DbLinq.Data.Linq;
using DbLinq.Data.Linq.Mapping;
#endif
using System.Linq;
using NUnit.Framework;
using DbLinq.Null;
namespace DbLinqTests.DbLinq.Data.Linq {
class DummyConnection : IDbConnection
{
public IDbTransaction BeginTransaction() {return null;}
public IDbTransaction BeginTransaction(IsolationLevel il) {return null;}
public void ChangeDatabase(string databaseName) {}
public void Close() {}
public IDbCommand CreateCommand() {return null;}
public string ConnectionString{get; set;}
public int ConnectionTimeout{get {return 0;}}
public string Database{get {return null;}}
public void Dispose() {}
public void Open() {}
public ConnectionState State{get {return ConnectionState.Closed;}}
}
[Table(Name="people")]
class BadPerson
{
public string FirstName {get; set;}
public string LastName {get; set;}
}
[Table(Name="people")]
class Person
{
[Column(Name="first_name")]
public string FirstName {get; set;}
[Column(Name="last_name")]
public string LastName {get; set;}
}
public abstract class DataContextTest
{
DataContext context;
[SetUp]
public void SetUp()
{
context = CreateDataContext();
}
protected abstract DataContext CreateDataContext();
[TearDown]
public void TearDown()
{
context = null;
}
[Test, ExpectedException(typeof(ArgumentNullException))]
public void Ctor_ConnectionStringNull()
{
string connectionString = null;
new DataContext(connectionString);
}
[Test, ExpectedException(typeof(NullReferenceException))]
public void Ctor_ConnectionNull()
{
IDbConnection connection = null;
new DataContext(connection);
}
[Test]
public void Connection()
{
IDbConnection connection = new NullConnection();
DataContext dc = new DataContext(connection);
Assert.AreEqual(connection, dc.Connection);
dc = new DataContext (new DummyConnection());
Assert.AreEqual(null, dc.Connection);
}
[Test]
public void ExecuteCommand()
{
context.Log = new StringWriter ();
try
{
context.ExecuteCommand("SomeCommand", 1, 2, 3);
}
catch (NotSupportedException)
{
}
catch (Exception e)
{
Assert.Fail();
}
Console.WriteLine ("# ExecuteCommand: Log={0}", context.Log);
}
[Test/*, ExpectedException(typeof(ArgumentNullException))*/]
public void ExecuteQuery_ElementTypeNull()
{
Type elementType = null;
context.ExecuteQuery(elementType, "command");
}
[Test, ExpectedException(typeof(NullReferenceException))]
public void ExecuteQuery_QueryNull()
{
Type elementType = typeof(Person);
context.ExecuteQuery(elementType, null);
}
[Test]
public void ExecuteQuery()
{
context.Log = new StringWriter ();
try
{
context.ExecuteQuery(typeof(Person), "select * from people", 1, 2, 3);
}
catch (NotSupportedException)
{
}
catch (Exception)
{
Assert.Fail();
}
Console.WriteLine ("# ExecuteQuery: Log={0}", context.Log);
}
[Test /*, ExpectedException(typeof(ArgumentNullException))*/]
public void ExecuteQueryTResult_QueryNull()
{
context.ExecuteQuery<Person>(null);
}
[Test]
public void ExecuteQueryTResult()
{
context.Log = new StringWriter ();
try
{
context.ExecuteQuery<Person>("select * from people", 1, 2, 3);
}
catch (NotSupportedException)
{
}
catch (Exception)
{
Assert.Fail();
}
Console.WriteLine ("# ExecuteQueryTResult: Log={0}", context.Log);
}
[Test]
public void GetChangeSet()
{
// TODO
context.GetChangeSet();
}
[Test, ExpectedException(typeof(NullReferenceException))]
public void GetCommand_QueryNull()
{
IQueryable query = null;
context.GetCommand(query);
}
protected abstract string People(string firstName);
protected abstract string People(string firstName, string lastName);
[Test]
public void GetCommand()
{
var foos =
from p in context.GetTable<Person>()
where p.FirstName == "foo"
select p;
var cmd = context.GetCommand(foos);
Console.WriteLine ("# GetCommand: cmd={0}", cmd.CommandText);
// Assert.AreEqual(People("foo"), cmd.CommandText);
foos = foos.Where(p => p.LastName == "bar");
var cmd2 = context.GetCommand(foos);
Assert.IsFalse(object.ReferenceEquals(cmd, cmd2));
Console.WriteLine ("# GetCommand: cmd2={0}", cmd2.CommandText);
// Assert.AreEqual(People("foo", "bar"), cmd2.CommandText);
}
}
}
#region MIT license
//
// MIT license
//
// Copyright (c) 2009 Novell, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
#if MONO_STRICT
using System.Data.Linq;
using System.Data.Linq.Mapping;
#else
using DbLinq.Data.Linq;
using DbLinq.Data.Linq.Mapping;
#endif
using DbLinq.Null;
using NUnit.Framework;
using DbLinqTests.DbLinq.Data.Linq;
namespace DbLinqTests.DbLinq.Data.Linq {
[TestFixture]
public class MsSqlDataContextTest : DataContextTest
{
protected override DataContext CreateDataContext()
{
return new DataContext(new NullConnection(), new AttributeMappingSource());
}
protected override string People(string firstName)
{
return
"SELECT [first_name], [last_name]\n" +
"FROM [people]\n" +
"WHERE [first_name] = '" + firstName + "'";
}
protected override string People(string firstName, string lastName)
{
return People(firstName) + " AND [last_name] = '" + lastName + "'";
}
}
}
#region MIT license
//
// MIT license
//
// Copyright (c) 2009 Novell, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Linq;
using System.Linq;
namespace DbLinq.Null {
class NullConnection : DbConnection
{
public NullConnection ()
{
}
public override string ConnectionString {get; set;}
public override string Database {get {return "NullDatabase";}}
public override string DataSource {get {return "NullDataSource";}}
public override string ServerVersion {get {return "0.0";}}
public override ConnectionState State {get {return ConnectionState.Closed;}}
public override void ChangeDatabase (string databaseName)
{
throw new NotSupportedException ();
}
public override void Close ()
{
}
public override void Open ()
{
}
protected override DbTransaction BeginDbTransaction (IsolationLevel level)
{
throw new NotSupportedException ();
}
protected override DbCommand CreateDbCommand ()
{
return new NullCommand ();
}
}
class NullParameter : DbParameter
{
public override DbType DbType {get; set;}
public override ParameterDirection Direction {get; set;}
public override bool IsNullable {get; set;}
public override string ParameterName {get; set;}
public override int Size {get; set;}
public override string SourceColumn {get; set;}
public override bool SourceColumnNullMapping {get; set;}
public override DataRowVersion SourceVersion {get; set;}
public override object Value {get; set;}
public override void ResetDbType ()
{
throw new NotSupportedException ();
}
}
class DbParameterCollection<TParameter> : DbParameterCollection
where TParameter : DbParameter
{
List<TParameter> parameters = new List<TParameter> ();
public DbParameterCollection ()
{
}
public override int Count {get {return parameters.Count;}}
public override bool IsFixedSize {get {return false;}}
public override bool IsReadOnly {get {return false;}}
public override bool IsSynchronized {get {return false;}}
public override object SyncRoot {get {return parameters;}}
public override int Add (object value)
{
if (!(value is TParameter))
throw new ArgumentException ("wrong type", "value");
parameters.Add ((TParameter) value);
return parameters.Count-1;
}
public override void AddRange (Array values)
{
foreach (TParameter p in values)
Add (p);
}
public override void Clear ()
{
parameters.Clear ();
}
public override bool Contains (object value)
{
return parameters.Contains ((TParameter) value);
}
public override bool Contains (string value)
{
return parameters.Any (p => p.ParameterName == value);
}
public override void CopyTo (Array array, int index)
{
((ICollection) parameters).CopyTo (array, index);
}
public override IEnumerator GetEnumerator ()
{
return parameters.GetEnumerator ();
}
public override int IndexOf (object value)
{
return parameters.IndexOf ((TParameter) value);
}
public override int IndexOf (string value)
{
for (int i = 0; i < parameters.Count; ++i)
if (parameters [i].ParameterName == value)
return i;
return -1;
}
public override void Insert (int index, object value)
{
parameters.Insert (index, (TParameter) value);
}
public override void Remove (object value)
{
parameters.Remove ((TParameter) value);
}
public override void RemoveAt (int index)
{
parameters.RemoveAt (index);
}
public override void RemoveAt (string value)
{
int idx = IndexOf (value);
if (idx >= 0)
parameters.RemoveAt (idx);
}
protected override DbParameter GetParameter (int index)
{
return parameters [index];
}
protected override DbParameter GetParameter (string value)
{
return parameters.Where (p => p.ParameterName == value)
.FirstOrDefault ();
}
protected override void SetParameter (int index, DbParameter value)
{
parameters [index] = (TParameter) value;
}
protected override void SetParameter (string index, DbParameter value)
{
parameters [IndexOf (value)] = (TParameter) value;
}
}
class NullCommand : DbCommand
{
DbParameterCollection<NullParameter> parameters = new DbParameterCollection<NullParameter> ();
public NullCommand ()
{
}
public override string CommandText { get; set; }
public override int CommandTimeout { get; set; }
public override CommandType CommandType { get; set; }
public override bool DesignTimeVisible { get; set; }
public override UpdateRowSource UpdatedRowSource { get; set; }
protected override DbConnection DbConnection { get; set; }
protected override DbParameterCollection DbParameterCollection {get {return parameters;}}
protected override DbTransaction DbTransaction { get; set; }
public override void Cancel ()
{
}
public override int ExecuteNonQuery ()
{
throw new NotSupportedException ();
}
public override object ExecuteScalar ()
{
throw new NotSupportedException ();
}
public override void Prepare ()
{
}
protected override DbParameter CreateDbParameter ()
{
return new NullParameter ();
}
protected override DbDataReader ExecuteDbDataReader (CommandBehavior behavior)
{
throw new NotSupportedException ();
}
}
}